diff --git a/.changeset/mcp-agent-ref-fields.md b/.changeset/mcp-agent-ref-fields.md deleted file mode 100644 index 3e9b248..0000000 --- a/.changeset/mcp-agent-ref-fields.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@refkit/core': minor -'@refkit/mcp': minor ---- - -`search_references` results now carry what an agent needs to judge a match and handle the file: the source's `description` (capped at 280 characters), its first 8 `tags`, the `author`, the 0..1 cross-source `relevance`, and `rights.rehostPolicy`. The output schema and the tool description explain each `rehostPolicy` value, so agents know whether they may download, cache or only hotlink a result's media. The per-source `sourceScore` stays out because it is not comparable across sources. Core exports the policy values as `REHOST_POLICIES`, which its own schema now uses as well. diff --git a/.changeset/modality-union-and-source-naming.md b/.changeset/modality-union-and-source-naming.md deleted file mode 100644 index 9e89ef5..0000000 --- a/.changeset/modality-union-and-source-naming.md +++ /dev/null @@ -1,201 +0,0 @@ ---- -'@refkit/core': minor -'@refkit/mcp': minor -'@refkit/provider-artic': minor -'@refkit/provider-brave': minor -'@refkit/provider-europeana': minor -'@refkit/provider-flickr': minor -'@refkit/provider-freesound': minor -'@refkit/provider-gutendex': minor -'@refkit/provider-internet-archive': minor -'@refkit/provider-jamendo': minor -'@refkit/provider-met': minor -'@refkit/provider-nailbook': minor -'@refkit/provider-openverse': minor -'@refkit/provider-pexels': minor -'@refkit/provider-pixabay': minor -'@refkit/provider-poetrydb': minor -'@refkit/provider-polyhaven': minor -'@refkit/provider-rijksmuseum': minor -'@refkit/provider-smithsonian': minor -'@refkit/provider-unsplash': minor -'@refkit/provider-wikimedia-commons': minor ---- - -References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. - -**Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. - -### What changed and why - -#### `Reference` is a union keyed by `modality` - -Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). - -| `modality` | Its own fields | -|---|---| -| `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | -| `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | -| `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | -| `text` | `text: TextMeta` | - -- **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). -- **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. -- **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. -- **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. -- **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. - -#### Audio and video metadata - -There are two new types. Units: - -- **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. -- **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). - -The bundled sources fill them from the responses they already fetch. No source makes an extra request. - -| Source | Fields | -|---|---| -| `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | -| `freesound` | `audio.durationSec` | -| `jamendo` | `audio.durationSec` | -| `pexels-video` | `video.durationSec` | -| `pixabay-video` | `video.durationSec` | - -- **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. -- **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. - -#### One word for the catalog a search queries: "source" - -A *provider* is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one *source*. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. - -Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". - -#### Fixes - -**openverse-audio** - -- **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. -- **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: - - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; - - MIDI files are `audio/midi`; - - WebM files are `audio/webm`. - - All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. -- **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. -- **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. - -**pixabay-video** - -- **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. -- **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. -- **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. - -**Flickr** - -- It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. - -**Internet Archive** - -- It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. -- A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. -- When the query wants neither modality, it returns no results without making a request. - -**Poly Haven** - -- `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. - -**Core** - -- **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. -- **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. -- **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. -- **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". - -#### `@refkit/mcp` - -- **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: - - `durationSec` is the audio or video length in seconds; - - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). - - So agents can filter by length or resolution. The output schema describes all three. -- **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". - - `sources: []` now gets that hint too. - - So does a miss thrown by a client built with another copy of `@refkit/core`. - -### Breaking changes and how to migrate - -#### Renames - -| Where | Old | New | -|---|---|---| -| search meta | `meta.providers` | `meta.sources` | -| search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | -| search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | -| search meta | `meta.providerOptions` | `meta.sourceOptions` | -| skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | -| search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | -| search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | -| reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | -| pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | -| messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | -| MCP input | `providerOptions` | `sourceOptions` | -| MCP output | `provider` on each reference | `source` | - -These names stay unchanged: - -- adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; -- `SearchInput.sources` and the MCP `sources` parameter; -- **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. - -#### `Reference` is a type alias of a union - -- **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: - - ```ts - type DistributiveOmit = T extends unknown ? Omit : never - ``` -- **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. -- **Declaration merging.** You can no longer augment `Reference` by declaration merging. -- **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. - -#### Custom providers - -- **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. -- **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. -- **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. - -#### `SourceSelectionError` and `isSourceSelectionError` - -Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: - -- `code` is `'no-matching-source'` or `'no-source-for-query'`: - - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); - - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). -- `requested` is the `sources` as passed. -- `unknown` lists the ids that match no configured source. -- `modalities` is the requested modalities. -- `kind` is set when the search narrowed by `controls.media.kind`. - -Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: - -- it is true for this copy's class; -- it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. - -`SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. - -#### `runProviderSearch` rejects modality leaks - -- The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. -- A direct call with `modalities: []` therefore rejects every live item. -- Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. - -#### Other breaks - -- **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. -- **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. -- **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. -- **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. -- **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. -- **MCP agent ref.** Each reference's `provider` is now `source`. -- **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index dda4d04..4047a5f 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,195 @@ # @refkit/core +## 0.10.0 + +### Minor Changes + +- 0c3c346: `search_references` results now carry what an agent needs to judge a match and handle the file: the source's `description` (capped at 280 characters), its first 8 `tags`, the `author`, the 0..1 cross-source `relevance`, and `rights.rehostPolicy`. The output schema and the tool description explain each `rehostPolicy` value, so agents know whether they may download, cache or only hotlink a result's media. The per-source `sourceScore` stays out because it is not comparable across sources. Core exports the policy values as `REHOST_POLICIES`, which its own schema now uses as well. +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + ## 0.9.0 ### Minor Changes diff --git a/packages/core/package.json b/packages/core/package.json index 8f838ef..9deba5d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/core", - "version": "0.9.0", + "version": "0.10.0", "description": "refkit core — neutral reference-retrieval brain: Reference/RightsRecord contract, license normalization, strict-deny use-gate, RRF merge/dedup, ReferenceProvider interfaces. Zero-network, zero-provider, only zod.", "type": "module", "license": "Apache-2.0", diff --git a/packages/mcp/CHANGELOG.md b/packages/mcp/CHANGELOG.md index c7da643..b89143b 100644 --- a/packages/mcp/CHANGELOG.md +++ b/packages/mcp/CHANGELOG.md @@ -1,5 +1,211 @@ # @refkit/mcp +## 0.9.0 + +### Minor Changes + +- 0c3c346: `search_references` results now carry what an agent needs to judge a match and handle the file: the source's `description` (capped at 280 characters), its first 8 `tags`, the `author`, the 0..1 cross-source `relevance`, and `rights.rehostPolicy`. The output schema and the tool description explain each `rehostPolicy` value, so agents know whether they may download, cache or only hotlink a result's media. The per-source `sourceScore` stays out because it is not comparable across sources. Core exports the policy values as `REHOST_POLICIES`, which its own schema now uses as well. +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + - @refkit/provider-artic@0.5.0 + - @refkit/provider-gutendex@0.5.0 + - @refkit/provider-internet-archive@0.6.0 + - @refkit/provider-met@0.5.0 + - @refkit/provider-nailbook@0.3.0 + - @refkit/provider-openverse@0.6.0 + - @refkit/provider-poetrydb@0.5.0 + - @refkit/provider-polyhaven@0.5.0 + - @refkit/provider-rijksmuseum@0.5.0 + - @refkit/provider-wikimedia-commons@0.6.0 + ## 0.8.2 ### Patch Changes diff --git a/packages/mcp/package.json b/packages/mcp/package.json index b51163a..2de2fe2 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/mcp", - "version": "0.8.2", + "version": "0.9.0", "description": "MCP server for refkit — exposes license-normalized reference search as an agent tool.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-artic/CHANGELOG.md b/packages/provider-artic/CHANGELOG.md index 1a1b36b..49a51f0 100644 --- a/packages/provider-artic/CHANGELOG.md +++ b/packages/provider-artic/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-artic +## 0.5.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.4.1 ### Patch Changes diff --git a/packages/provider-artic/package.json b/packages/provider-artic/package.json index 0fd3c51..093fd42 100644 --- a/packages/provider-artic/package.json +++ b/packages/provider-artic/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-artic", - "version": "0.4.1", + "version": "0.5.0", "description": "Art Institute of Chicago provider satellite for refkit.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-brave/CHANGELOG.md b/packages/provider-brave/CHANGELOG.md index b84240e..e597d67 100644 --- a/packages/provider-brave/CHANGELOG.md +++ b/packages/provider-brave/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-brave +## 0.5.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.4.0 ### Minor Changes diff --git a/packages/provider-brave/package.json b/packages/provider-brave/package.json index 708fd7f..4169d51 100644 --- a/packages/provider-brave/package.json +++ b/packages/provider-brave/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-brave", - "version": "0.4.0", + "version": "0.5.0", "description": "Brave Search web-discovery provider satellite for refkit — open-web image breadth, license:unknown (use-gated to needs-review).", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-europeana/CHANGELOG.md b/packages/provider-europeana/CHANGELOG.md index a54dd40..67737ab 100644 --- a/packages/provider-europeana/CHANGELOG.md +++ b/packages/provider-europeana/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-europeana +## 0.6.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.5.0 ### Minor Changes diff --git a/packages/provider-europeana/package.json b/packages/provider-europeana/package.json index 90df616..3ddf912 100644 --- a/packages/provider-europeana/package.json +++ b/packages/provider-europeana/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-europeana", - "version": "0.5.0", + "version": "0.6.0", "description": "Europeana provider satellite for refkit.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-flickr/CHANGELOG.md b/packages/provider-flickr/CHANGELOG.md index 8c09916..fa2ffe1 100644 --- a/packages/provider-flickr/CHANGELOG.md +++ b/packages/provider-flickr/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-flickr +## 0.6.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.5.0 ### Minor Changes diff --git a/packages/provider-flickr/package.json b/packages/provider-flickr/package.json index 6871f61..5a2e6ad 100644 --- a/packages/provider-flickr/package.json +++ b/packages/provider-flickr/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-flickr", - "version": "0.5.0", + "version": "0.6.0", "description": "Flickr provider satellite for refkit.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-freesound/CHANGELOG.md b/packages/provider-freesound/CHANGELOG.md index 4a83e6b..6d13201 100644 --- a/packages/provider-freesound/CHANGELOG.md +++ b/packages/provider-freesound/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-freesound +## 0.6.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.5.0 ### Minor Changes diff --git a/packages/provider-freesound/package.json b/packages/provider-freesound/package.json index c2bb3b8..df7ee13 100644 --- a/packages/provider-freesound/package.json +++ b/packages/provider-freesound/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-freesound", - "version": "0.5.0", + "version": "0.6.0", "description": "Freesound provider satellite for refkit.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-gutendex/CHANGELOG.md b/packages/provider-gutendex/CHANGELOG.md index 616d02c..732d648 100644 --- a/packages/provider-gutendex/CHANGELOG.md +++ b/packages/provider-gutendex/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-gutendex +## 0.5.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.4.0 ### Minor Changes diff --git a/packages/provider-gutendex/package.json b/packages/provider-gutendex/package.json index 07470ee..d8c4366 100644 --- a/packages/provider-gutendex/package.json +++ b/packages/provider-gutendex/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-gutendex", - "version": "0.4.0", + "version": "0.5.0", "description": "Gutendex (Project Gutenberg) provider satellite for refkit — public-domain book references, per-item copyright→license.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-internet-archive/CHANGELOG.md b/packages/provider-internet-archive/CHANGELOG.md index 164a39a..c27f8d5 100644 --- a/packages/provider-internet-archive/CHANGELOG.md +++ b/packages/provider-internet-archive/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-internet-archive +## 0.6.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.5.0 ### Minor Changes diff --git a/packages/provider-internet-archive/package.json b/packages/provider-internet-archive/package.json index 3e02fed..a1a8a23 100644 --- a/packages/provider-internet-archive/package.json +++ b/packages/provider-internet-archive/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-internet-archive", - "version": "0.5.0", + "version": "0.6.0", "description": "Internet Archive provider satellite for refkit.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-jamendo/CHANGELOG.md b/packages/provider-jamendo/CHANGELOG.md index 8d86152..0a8e37c 100644 --- a/packages/provider-jamendo/CHANGELOG.md +++ b/packages/provider-jamendo/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-jamendo +## 0.6.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.5.0 ### Minor Changes diff --git a/packages/provider-jamendo/package.json b/packages/provider-jamendo/package.json index b90f20e..2a4201d 100644 --- a/packages/provider-jamendo/package.json +++ b/packages/provider-jamendo/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-jamendo", - "version": "0.5.0", + "version": "0.6.0", "description": "Jamendo provider satellite for refkit.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-met/CHANGELOG.md b/packages/provider-met/CHANGELOG.md index 5bf3a1b..6181ae3 100644 --- a/packages/provider-met/CHANGELOG.md +++ b/packages/provider-met/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-met +## 0.5.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.4.1 ### Patch Changes diff --git a/packages/provider-met/package.json b/packages/provider-met/package.json index 5316b6f..23c31b4 100644 --- a/packages/provider-met/package.json +++ b/packages/provider-met/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-met", - "version": "0.4.1", + "version": "0.5.0", "description": "The Metropolitan Museum of Art provider satellite for refkit.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-nailbook/CHANGELOG.md b/packages/provider-nailbook/CHANGELOG.md index ed6dbcb..317e941 100644 --- a/packages/provider-nailbook/CHANGELOG.md +++ b/packages/provider-nailbook/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-nailbook +## 0.3.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.2.0 ### Minor Changes diff --git a/packages/provider-nailbook/package.json b/packages/provider-nailbook/package.json index 87ec204..b56f6a6 100644 --- a/packages/provider-nailbook/package.json +++ b/packages/provider-nailbook/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-nailbook", - "version": "0.2.0", + "version": "0.3.0", "description": "Nailbook (nailbook.jp) nail-design provider satellite for refkit — keyless Japanese nail-art image references.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-openverse/CHANGELOG.md b/packages/provider-openverse/CHANGELOG.md index 2b7c276..0939eb0 100644 --- a/packages/provider-openverse/CHANGELOG.md +++ b/packages/provider-openverse/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-openverse +## 0.6.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.5.1 ### Patch Changes diff --git a/packages/provider-openverse/package.json b/packages/provider-openverse/package.json index 9cb2d02..98e2c8f 100644 --- a/packages/provider-openverse/package.json +++ b/packages/provider-openverse/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-openverse", - "version": "0.5.1", + "version": "0.6.0", "description": "Openverse provider satellite for refkit — CC/PD licensed image search, license-normalized.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-pexels/CHANGELOG.md b/packages/provider-pexels/CHANGELOG.md index 5eeb916..0cdc16a 100644 --- a/packages/provider-pexels/CHANGELOG.md +++ b/packages/provider-pexels/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-pexels +## 0.5.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.4.0 ### Minor Changes diff --git a/packages/provider-pexels/package.json b/packages/provider-pexels/package.json index 857c661..759263b 100644 --- a/packages/provider-pexels/package.json +++ b/packages/provider-pexels/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-pexels", - "version": "0.4.0", + "version": "0.5.0", "description": "Pexels provider satellite for refkit — image search, hotlink-required.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-pixabay/CHANGELOG.md b/packages/provider-pixabay/CHANGELOG.md index aca99d2..ac6f658 100644 --- a/packages/provider-pixabay/CHANGELOG.md +++ b/packages/provider-pixabay/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-pixabay +## 0.5.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.4.0 ### Minor Changes diff --git a/packages/provider-pixabay/package.json b/packages/provider-pixabay/package.json index 75fd33e..08b6d90 100644 --- a/packages/provider-pixabay/package.json +++ b/packages/provider-pixabay/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-pixabay", - "version": "0.4.0", + "version": "0.5.0", "description": "Pixabay provider satellite for refkit — image search, cache-allowed.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-poetrydb/CHANGELOG.md b/packages/provider-poetrydb/CHANGELOG.md index e3d7348..4b916d0 100644 --- a/packages/provider-poetrydb/CHANGELOG.md +++ b/packages/provider-poetrydb/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-poetrydb +## 0.5.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.4.0 ### Minor Changes diff --git a/packages/provider-poetrydb/package.json b/packages/provider-poetrydb/package.json index f3ce58e..c644600 100644 --- a/packages/provider-poetrydb/package.json +++ b/packages/provider-poetrydb/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-poetrydb", - "version": "0.4.0", + "version": "0.5.0", "description": "PoetryDB provider satellite for refkit — classic public-domain poems.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-polyhaven/CHANGELOG.md b/packages/provider-polyhaven/CHANGELOG.md index 5dcbdb8..84af7f1 100644 --- a/packages/provider-polyhaven/CHANGELOG.md +++ b/packages/provider-polyhaven/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-polyhaven +## 0.5.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.4.0 ### Minor Changes diff --git a/packages/provider-polyhaven/package.json b/packages/provider-polyhaven/package.json index 9039a87..206f456 100644 --- a/packages/provider-polyhaven/package.json +++ b/packages/provider-polyhaven/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-polyhaven", - "version": "0.4.0", + "version": "0.5.0", "description": "Poly Haven provider satellite for refkit.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-rijksmuseum/CHANGELOG.md b/packages/provider-rijksmuseum/CHANGELOG.md index 4bb6d8a..6c02152 100644 --- a/packages/provider-rijksmuseum/CHANGELOG.md +++ b/packages/provider-rijksmuseum/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-rijksmuseum +## 0.5.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.4.0 ### Minor Changes diff --git a/packages/provider-rijksmuseum/package.json b/packages/provider-rijksmuseum/package.json index b574000..bae4ce1 100644 --- a/packages/provider-rijksmuseum/package.json +++ b/packages/provider-rijksmuseum/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-rijksmuseum", - "version": "0.4.0", + "version": "0.5.0", "description": "Rijksmuseum provider satellite for refkit.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-smithsonian/CHANGELOG.md b/packages/provider-smithsonian/CHANGELOG.md index 5885174..27e8ded 100644 --- a/packages/provider-smithsonian/CHANGELOG.md +++ b/packages/provider-smithsonian/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-smithsonian +## 0.5.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.4.0 ### Minor Changes diff --git a/packages/provider-smithsonian/package.json b/packages/provider-smithsonian/package.json index daae1c2..fb25e1f 100644 --- a/packages/provider-smithsonian/package.json +++ b/packages/provider-smithsonian/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-smithsonian", - "version": "0.4.0", + "version": "0.5.0", "description": "Smithsonian Open Access provider satellite for refkit.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-testkit/CHANGELOG.md b/packages/provider-testkit/CHANGELOG.md index 1c68dc2..525a6ff 100644 --- a/packages/provider-testkit/CHANGELOG.md +++ b/packages/provider-testkit/CHANGELOG.md @@ -1,5 +1,13 @@ # @refkit/provider-testkit +## 0.1.2 + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.1.1 ### Patch Changes diff --git a/packages/provider-testkit/package.json b/packages/provider-testkit/package.json index 3635aa6..f9cd4f9 100644 --- a/packages/provider-testkit/package.json +++ b/packages/provider-testkit/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-testkit", - "version": "0.1.1", + "version": "0.1.2", "private": true, "description": "Conformance test helpers for refkit provider satellites (internal, not published).", "type": "module", diff --git a/packages/provider-unsplash/CHANGELOG.md b/packages/provider-unsplash/CHANGELOG.md index 891b432..ab17d1b 100644 --- a/packages/provider-unsplash/CHANGELOG.md +++ b/packages/provider-unsplash/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-unsplash +## 0.5.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.4.0 ### Minor Changes diff --git a/packages/provider-unsplash/package.json b/packages/provider-unsplash/package.json index 963f59e..73fc8f2 100644 --- a/packages/provider-unsplash/package.json +++ b/packages/provider-unsplash/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-unsplash", - "version": "0.4.0", + "version": "0.5.0", "description": "Unsplash provider satellite for refkit.", "type": "module", "license": "Apache-2.0", diff --git a/packages/provider-wikimedia-commons/CHANGELOG.md b/packages/provider-wikimedia-commons/CHANGELOG.md index bc79bd8..2722911 100644 --- a/packages/provider-wikimedia-commons/CHANGELOG.md +++ b/packages/provider-wikimedia-commons/CHANGELOG.md @@ -1,5 +1,200 @@ # @refkit/provider-wikimedia-commons +## 0.6.0 + +### Minor Changes + +- 6f05cfa: References now have a shape per modality, every public surface calls the catalog a search queries a "source", and audio and video results carry their duration. This release renames and retypes public API; the migration notes below list every break. + + **Upgrade core, MCP and every provider together.** Each `@refkit/provider-*` package pins the exact `@refkit/core` version it was built with, so a host that upgrades core alone ends up with two copies of core. + + ### What changed and why + + #### `Reference` is a union keyed by `modality` + + Before, every result had one flat shape: any modality could carry `visual`, `text` or `preview`, and nothing said which fields belonged together. `EmittedReference` and `Reference` are now unions of four variants. Each variant is exported (`EmittedImageReference`, `EmittedVideoReference`, `EmittedAudioReference`, `EmittedTextReference`, and `ImageReference`, `VideoReference`, `AudioReference`, `TextReference`). + + | `modality` | Its own fields | + | ---------- | --------------------------------------------------------------- | + | `image` | `preview` (`mediaType` `image/*`), `visual`, `perceptualHash` | + | `video` | `preview` (`mediaType` `video/*`), `visual`, `video: VideoMeta` | + | `audio` | `preview` (`mediaType` `audio/*`), `audio: AudioMeta` | + | `text` | `text: TextMeta` | + + - **Common fields.** Every variant keeps `kind`, `title`, `description`, `tags`, `rights`, `thumbnail`, `sourceScore` and `raw`. An emitted item also has `sourceUrl` and an optional `canonicalUrl`; a complete reference has the fields core stamps (`id`, `source`, `canonicalUrl`, `verifiedAt`, `relevance`). + - **`thumbnail`** is always an image a UI can show: the picture itself, a video poster, audio cover art or a book cover. + - **`visual`** means the size of the original image or video. A source that only knows a rendition's size leaves `visual` unset and puts that size on `preview.width` / `preview.height`. + - **Types.** Each variant declares the other variants' fields as `?: never`. A provider that writes a field of another modality, such as `visual` on an audio item, gets a compile error. Code that reads the union still compiles without narrowing: `r.visual` is `VisualMeta | undefined` on any reference. + - **Runtime.** `emittedReferenceSchema` and `referenceSchema` are discriminated unions on `modality`. An item whose `preview.mediaType` does not start with its variant's prefix (`image/`, `video/`, `audio/`) is rejected. A field that belongs to another modality is stripped. + + #### Audio and video metadata + + There are two new types. Units: + + - **`VideoMeta`**: `durationSec` (seconds, as a float) and `fps`. + - **`AudioMeta`**: `durationSec` (seconds, as a float), `bitrateKbps` (kilobits per second, bits/s ÷ 1000), `sampleRateHz` (hertz), `channels`, and `format` (a short name such as `mp3`). + + The bundled sources fill them from the responses they already fetch. No source makes an extra request. + + | Source | Fields | + | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `openverse-audio` | `audio.durationSec` (Openverse's milliseconds ÷ 1000), `audio.bitrateKbps` (bits/s ÷ 1000; Jamendo-sourced items report none), `audio.sampleRateHz` (often missing), `audio.format` | + | `freesound` | `audio.durationSec` | + | `jamendo` | `audio.durationSec` | + | `pexels-video` | `video.durationSec` | + | `pixabay-video` | `video.durationSec` | + + - **Unset values.** A missing, non-finite, zero or negative duration, bitrate or sample rate leaves the field unset. Openverse reports a duration of 0 for MIDI files it never measured, and zero is never a real measurement for these fields. + - **Not filled yet.** No bundled source fills `fps` or `channels`. Internet Archive leaves durations unset. + + #### One word for the catalog a search queries: "source" + + A _provider_ is adapter code, such as a `ReferenceProvider` from a `@refkit/provider-*` package. Each provider instance serves one _source_. That source's id (`provider.id`, e.g. `openverse-audio`) is what `sources`, `sourceOptions`, `meta.sources` and each result's `source.id` speak. + + Before, the same id had several names: callers passed `sources`, but results reported `meta.providers[].providerId`, options went under `providerOptions`, errors went to `onProviderError`, and each reference named it `source.providerId`. The rename table is under "Breaking changes". + + #### Fixes + + **openverse-audio** + + - **Thumbnail.** `thumbnail` is the item's album art, and is unset when there is none. It used to point at Openverse's waveform endpoint, which returns JSON peak data, not an image. + - **Media types.** `preview.mediaType` and `audio.format` now come from one table of known filetypes: + + - Wikimedia's `ogx` files (Ogg streams) are `audio/ogg` with `format: 'ogg'`; + - MIDI files are `audio/midi`; + - WebM files are `audio/webm`. + + All of these used to be labelled `audio/mpeg`. Jamendo's `mp31` and `mp32` read as `mp3`. + + - **Unknown filetypes.** An item whose filetype is outside the table has no `preview` and no `format`, instead of a guessed `audio/mpeg`. No such item was seen in live results. + - **Kind.** `kind` comes from Openverse's `category`: `music` becomes `music`, and `sound_effect` becomes `sound-effect`. Other categories leave it unset. In live results only Jamendo-sourced items carried a mapped category (`music`). Freesound-sourced items carry no category, so they have no `kind`. + + **pixabay-video** + + - **Kind.** `kind` comes from Pixabay's `type`: `film` or `animation`. This source and openverse-audio declared these kinds but never emitted them. + - **No `visual`.** It no longer sets `visual`, because it only knows a rendition's size. That size stays on `preview`. + - **Renditions.** When choosing the preview, it skips a rendition whose URL is empty or whose size is 0. Pixabay returns an unavailable `large` rendition that way. + + **Flickr** + + - It no longer sets `visual` from its ≤1024px rendition. The rendition's size stays on `preview`. + + **Internet Archive** + + - It asks only for the mediatypes the query wants: `movies` for video, `texts` for text. + - A text-only search used to get films: in a live check for "jazz", all 20 results on page one were films. + - When the query wants neither modality, it returns no results without making a request. + + **Poly Haven** + + - `polyhaven({ assetType: 'hdris' })` has its own source id, `polyhaven-hdri`, so textures (`polyhaven`) and HDRIs can be configured side by side. + + **Core** + + - **Modality leaks.** An item whose `modality` is outside what its source was asked for (the requested modalities that source declares) is rejected like a malformed item. It is dropped, counted in `meta.sources[].rejected`, and passed to `onSourceError`. + - **Duplicate ids.** `createRefkit` throws when two providers share a source id. Before, this passed silently: the two shared one `meta.sources` entry and one cache key, so one could be answered from the other's cache. + - **Unknown `sourceOptions` ids.** An id in `sourceOptions` that matches no configured source now adds `unknown sourceOptions id(s) ignored: …` to `meta.warnings`, as an unknown id in `sources` already did. + - **Typed selection error.** When a search has no source to fan out to, core throws a `SourceSelectionError`. A host can now branch on its fields instead of matching the message. See "Breaking changes". + + #### `@refkit/mcp` + + - **New fields.** Each `search_references` result gains optional `durationSec`, `width` and `height`: + + - `durationSec` is the audio or video length in seconds; + - `width` and `height` are in pixels: the original's size from `visual`, else the preview rendition's size (same aspect ratio). + + So agents can filter by length or resolution. The output schema describes all three. + + - **Selection misses.** The "unknown sources" tool error is recognised with `isSourceSelectionError` and `code === 'no-matching-source'`, not by reading the message. It still ends with "Enabled source ids: …". + - `sources: []` now gets that hint too. + - So does a miss thrown by a client built with another copy of `@refkit/core`. + + ### Breaking changes and how to migrate + + #### Renames + + | Where | Old | New | + | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | + | search meta | `meta.providers` | `meta.sources` | + | search meta | `ProviderSearchStatus` (`providerId`), `providerSearchStatusSchema` | `SourceStatus` (`sourceId`), `sourceStatusSchema` | + | search meta | `meta.controls.appliedByProvider` / `ignoredByProvider` | `appliedBySource` / `ignoredBySource` | + | search meta | `meta.providerOptions` | `meta.sourceOptions` | + | skip reasons | `PROVIDER_SKIP_REASONS`, `ProviderSkipReason` | `SOURCE_SKIP_REASONS`, `SourceSkipReason` (values unchanged) | + | search input | `providerOptions`, `ProviderOptionsById` | `sourceOptions`, `SourceOptionsById` | + | search input | `onProviderError`, `ProviderError { providerId }` | `onSourceError`, `SourceError { sourceId }` | + | reference | `source: { providerId, sourceUrl }` | `source: { id, url }` | + | pipeline (exported) | `PassDeps.onProviderError`, `PassDeps.providerOptions`, `PassOutcome.statusByProvider`, `normalizeQuery`'s `providerOptions` input | `onSourceError`, `sourceOptions`, `statusBySource`, `sourceOptions` | + | messages | `no configured provider matches source id(s) …`, `no registered provider supports modalities …`, `all providers failed`, `n provider(s) failed` | `no configured source matches id(s) …`, `no configured source supports modalities …`, `all sources failed`, `n source(s) failed` | + | MCP input | `providerOptions` | `sourceOptions` | + | MCP output | `provider` on each reference | `source` | + + These names stay unchanged: + + - adapter-side names: `RefkitOptions.providers`, `ReferenceProvider`, `defineProvider`, `ProviderContext`, `ProviderOptions`, `selectProviders`, `runProviderSearch`, `providerCacheKey`; + - `SearchInput.sources` and the MCP `sources` parameter; + - **`NormalizedQuery.providerOptions`**: provider code keeps reading its own options from `q.providerOptions`. + + #### `Reference` is a type alias of a union + + - **`Omit`.** `Omit` loses the link between `modality` and its fields. Use a distributive omit: + + ```ts + type DistributiveOmit = T extends unknown + ? Omit + : never; + ``` + + - **`extends`.** An interface can no longer `extend` `Reference` or `EmittedReference` (TS2312). Use a type alias with an intersection, or extend one variant. + - **Declaration merging.** You can no longer augment `Reference` by declaration merging. + - **Test fixtures.** A builder that spreads `Partial` or `Partial` over an image default no longer compiles. Type its parameter as `Partial` / `Partial`. + + #### Custom providers + + - **Wrong-modality fields.** Writing a field of another modality is a compile error. At runtime the field is stripped. + - **Preview media type.** A `preview.mediaType` without the variant's prefix (`image/`, `video/`, `audio/`) rejects the item. + - **Undeclared modalities.** An item whose `modality` was not asked of the source is rejected. That covers a modality the source does not declare, and one the search did not request. + + #### `SourceSelectionError` and `isSourceSelectionError` + + Core still throws when a search has no source to fan out to, but the error is now typed and its messages have changed. Stop matching the message and branch on the fields: + + - `code` is `'no-matching-source'` or `'no-source-for-query'`: + - `'no-matching-source'`: `sources` was given and none of it can serve the search (an unknown id, a known id lacking the modality or the kind, or `sources: []`); + - `'no-source-for-query'`: no `sources` was given, and no configured source supports the modalities (and kind). + - `requested` is the `sources` as passed. + - `unknown` lists the ids that match no configured source. + - `modalities` is the requested modalities. + - `kind` is set when the search narrowed by `controls.media.kind`. + + Recognise the error with `isSourceSelectionError(e)`, not `instanceof`: + + - it is true for this copy's class; + - it is also true for an error thrown by another copy of `@refkit/core` that has `name: 'SourceSelectionError'`, a known `code`, and `unknown` and `modalities` arrays. + + `SOURCE_SELECTION_ERROR_CODES` and the `SourceSelectionErrorCode` type are exported. + + #### `runProviderSearch` rejects modality leaks + + - The exported `runProviderSearch` now rejects every live item whose `modality` is not in the `modalities` of the query it is given. + - A direct call with `modalities: []` therefore rejects every live item. + - Through `createRefkit` this cannot happen: a source is only searched when it declares one of the requested modalities, so its query's `modalities` is never empty. + + #### Other breaks + + - **One-time cache miss.** The cache key prefix moved from `refkit:v3` to `refkit:v4`. Every entry cached by the previous version is missed once after the upgrade, instead of being parsed in the old shape. + - **Duplicate source ids.** `createRefkit` now throws on duplicate source ids. Configure each source once. + - **Poly Haven HDRI ids.** For HDRI users, `polyhaven({ assetType: 'hdris' })` changes its id from `polyhaven` to `polyhaven-hdri`. Reference ids, cache keys, and the `sources` and `sourceOptions` keys for HDRIs change with it. Replace `'polyhaven'` with `'polyhaven-hdri'` wherever you meant the HDRI source. + - **`visual` on Flickr and pixabay-video.** These results no longer carry `visual`. For their size or aspect ratio, fall back to `preview.width` / `preview.height`, as `@refkit/mcp` does. The reranker's resolution term now treats these results as unknown (neutral) instead of scoring the rendition's size, so they can rank slightly higher in a mixed batch. + - **MCP `providerOptions`.** The input is now `sourceOptions`. The MCP SDK silently strips unknown input keys, so a call that still sends `providerOptions` loses its options without an error. + - **MCP agent ref.** Each reference's `provider` is now `source`. + - **MCP errors with `sources` set.** Errors other than the source miss that are raised while `sources` is set (for example an invalid cursor) are now rethrown by the tool handler. Before, the handler returned its own `isError` result and appended a misleading "Enabled source ids" hint. The MCP SDK still reports a thrown error to the agent as an `isError` result with the error's message, now without the hint. + +### Patch Changes + +- Updated dependencies [0c3c346] +- Updated dependencies [6f05cfa] + - @refkit/core@0.10.0 + ## 0.5.0 ### Minor Changes diff --git a/packages/provider-wikimedia-commons/package.json b/packages/provider-wikimedia-commons/package.json index 0369e33..ec5f56f 100644 --- a/packages/provider-wikimedia-commons/package.json +++ b/packages/provider-wikimedia-commons/package.json @@ -1,6 +1,6 @@ { "name": "@refkit/provider-wikimedia-commons", - "version": "0.5.0", + "version": "0.6.0", "description": "Wikimedia Commons provider satellite for refkit.", "type": "module", "license": "Apache-2.0", diff --git a/plugins/refkit/.claude-plugin/plugin.json b/plugins/refkit/.claude-plugin/plugin.json index 816d1da..7804867 100644 --- a/plugins/refkit/.claude-plugin/plugin.json +++ b/plugins/refkit/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "refkit", "displayName": "refkit", - "version": "0.8.2", + "version": "0.9.0", "description": "License-aware reference search for creative work: find images, art, textures, audio and text across open sources, with a use verdict and attribution line per result.", "author": { "name": "refkitjs", diff --git a/plugins/refkit/.codex-plugin/plugin.json b/plugins/refkit/.codex-plugin/plugin.json index 7df5a69..b302f54 100644 --- a/plugins/refkit/.codex-plugin/plugin.json +++ b/plugins/refkit/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "refkit", - "version": "0.8.2", + "version": "0.9.0", "description": "License-aware reference search for creative work: find images, art, textures, audio and text across open sources, with a use verdict and attribution line per result.", "author": { "name": "refkitjs", diff --git a/plugins/refkit/.mcp.json b/plugins/refkit/.mcp.json index d902fc3..928f80d 100644 --- a/plugins/refkit/.mcp.json +++ b/plugins/refkit/.mcp.json @@ -4,7 +4,7 @@ "command": "npx", "args": [ "-y", - "@refkit/mcp@0.8.2" + "@refkit/mcp@0.9.0" ] } }