@combycode/llm-sdk 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/dist/catalog/catalog.d.ts +15 -3
- package/dist/helpers/select-model.d.ts +35 -0
- package/dist/index.browser.js +229 -67
- package/dist/index.d.ts +2 -2
- package/dist/index.js +229 -67
- package/dist/llm/client.d.ts +3 -0
- package/dist/llm/types/provider.d.ts +5 -0
- package/dist/wire/interpreter.d.ts +40 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,62 @@ All notable changes to `@combycode/llm-sdk` are documented here. The format foll
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/) and the project adheres to
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [3.1.0] — 2026-08-25
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **A provider can declare a hosted tool it will not run beside certain content** (`toolConstraints`
|
|
14
|
+
in a wire spec, plus a `hasPartType` condition). Measured 2026-08-25: Google answers 400 `The mime
|
|
15
|
+
type: video/mp4 is not supported for code execution` when `code_interpreter` accompanies a PDF or a
|
|
16
|
+
video. Images are fine, `web_search` is fine, and OpenAI accepts every combination — so it is one
|
|
17
|
+
provider's rule, and it lives in that provider's spec as data rather than as an `if` in the builder.
|
|
18
|
+
|
|
19
|
+
A matched constraint drops the tool — `hasTool` reports it absent, so the spec's existing guard
|
|
20
|
+
omits it with no further edit — and records why on the built request. `LLMClient` emits each as
|
|
21
|
+
`onWarning` with code `request_adjusted`, on both `complete()` and `stream()`. Dropping it quietly
|
|
22
|
+
would trade a confusing error for the silent loss of a capability the caller asked for.
|
|
23
|
+
|
|
24
|
+
- **Any spelling of a model id finds the model, and every helper resolves it the same way.**
|
|
25
|
+
Providers spell one version several ways and users copy whichever they saw: `gpt-4.1` / `gpt-4-1`,
|
|
26
|
+
`gemini-2.5-flash` / `gemini-2-5-flash`, `claude-haiku-4.5` / `claude-haiku-4-5`. Only some are
|
|
27
|
+
callable, and the rest missed the catalog outright — no price, no capabilities — and were then
|
|
28
|
+
forwarded to the provider verbatim, turning a spelling difference into a 404.
|
|
29
|
+
|
|
30
|
+
Catalog lookups are now insensitive to the separator between two digits and to case, so ~556
|
|
31
|
+
previously-unresolvable spellings reach their entry. The rule is deliberately narrow — only a
|
|
32
|
+
separator between two DIGITS moves — and is verified across the shipped catalogs to merge nothing:
|
|
33
|
+
1016 normalized keys, zero collisions.
|
|
34
|
+
|
|
35
|
+
`resolveModelId()` now always returns something callable. A spelling the provider itself accepts is
|
|
36
|
+
sent unchanged; one it would reject is corrected to the canonical id instead of being forwarded;
|
|
37
|
+
an unknown id still passes through verbatim, so fine-tunes and same-day releases keep working.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **`createRealtime()` never translated the model id.** It parsed the provider but skipped the
|
|
42
|
+
catalog step every other helper performs, so a realtime session was the one path that sent our
|
|
43
|
+
slug instead of the provider's id. All helpers now share the single resolution step, and a test
|
|
44
|
+
calls each one and reads the wire so a helper that forgets fails CI rather than a user's request.
|
|
45
|
+
|
|
46
|
+
- **An Anthropic model is reachable by the name Anthropic documents.** `claude-haiku-4-5` — the
|
|
47
|
+
spelling in Anthropic's own docs, and one the API accepts — matched no catalog key and no alias,
|
|
48
|
+
because `/v1/models` lists only the dated snapshot (`claude-haiku-4-5-20251001`) while our slug
|
|
49
|
+
dots the version (`claude-haiku-4.5`). `get()` and `getPricing()` missed in silence, and an
|
|
50
|
+
unpriced model is indistinguishable from a free one: reported from production as 72k tokens
|
|
51
|
+
billed at $0.00.
|
|
52
|
+
|
|
53
|
+
The undated form is now carried as an alias for the four affected entries (`claude-haiku-4-5`,
|
|
54
|
+
`claude-sonnet-4-5`, `claude-opus-4-5`, `claude-opus-4-1`), derived in the catalog pipeline so a
|
|
55
|
+
regeneration keeps it. Anthropic-only, and deliberately so — the same date-stripping applied to
|
|
56
|
+
other providers invents ids that do not exist (`imagen-4.0-generate`, `command-r7b-12`), since
|
|
57
|
+
only there is the undated form a truncation rather than a real alias. Each was probed live.
|
|
58
|
+
|
|
59
|
+
`providerModelName` is untouched, so **what goes on the wire is unchanged**: a slug still
|
|
60
|
+
translates to its pinned snapshot, and an alias is still sent verbatim as the floating id the
|
|
61
|
+
caller chose. This widens what the catalog recognises, never what it calls.
|
|
62
|
+
|
|
7
63
|
## [3.0.0] — 2026-08-24
|
|
8
64
|
|
|
9
65
|
### Added
|
|
@@ -141,14 +141,26 @@ export declare class ModelCatalog {
|
|
|
141
141
|
/** `provider/alias` → `provider/canonical-slug`. Lets get()/resolveModelId
|
|
142
142
|
* accept any callable id (providerModelName, dated snapshot) AND the slug. */
|
|
143
143
|
private aliasIndex;
|
|
144
|
+
/** `provider/normalized-id` → `provider/canonical-slug`. The last resort, so a
|
|
145
|
+
* user's spelling of a version never decides whether the model is found. */
|
|
146
|
+
private normIndex;
|
|
144
147
|
private key;
|
|
148
|
+
private normKey;
|
|
145
149
|
set(provider: string, model: string, info: Partial<Omit<ModelInfo, 'provider' | 'model'>> & {
|
|
146
150
|
pricing: ModelPricing;
|
|
147
151
|
}): void;
|
|
148
152
|
get(provider: string, model: string): ModelInfo | null;
|
|
149
|
-
/** The exact id to SEND to the provider for a given model string.
|
|
150
|
-
*
|
|
151
|
-
*
|
|
153
|
+
/** The exact id to SEND to the provider for a given model string.
|
|
154
|
+
*
|
|
155
|
+
* Three cases, and the difference between them is what the caller ASKED for:
|
|
156
|
+
* - our slug → translate to providerModelName (the pinned snapshot)
|
|
157
|
+
* - an id the provider itself accepts (a listed alias, e.g. a dated snapshot
|
|
158
|
+
* or anthropic's undated name) → verbatim, because it is a deliberate choice
|
|
159
|
+
* and rewriting it would pin a caller who asked to float
|
|
160
|
+
* - a spelling variant that is NOT callable (`gemini-2-5-flash`) → the
|
|
161
|
+
* canonical entry's providerModelName, since forwarding it verbatim only
|
|
162
|
+
* produces a 404 with the user's typo in it
|
|
163
|
+
* - unknown → verbatim, so a model we have never heard of still works */
|
|
152
164
|
resolveModelId(provider: string, model: string): string;
|
|
153
165
|
getPricing(provider: string, model: string): ModelPricing | null;
|
|
154
166
|
getPreferredApi(provider: string, model: string): ApiType | null;
|
|
@@ -34,6 +34,41 @@ declare const DEFAULT_THRESHOLDS: {
|
|
|
34
34
|
'context.small': number;
|
|
35
35
|
'context.large': number;
|
|
36
36
|
};
|
|
37
|
+
/** One filter a UI can offer, and what it accepts. */
|
|
38
|
+
export interface FilterFacet {
|
|
39
|
+
/** The DSL key, e.g. `price`. Write it as `key:value`. */
|
|
40
|
+
key: string;
|
|
41
|
+
/** Group heading for a picker. */
|
|
42
|
+
category: 'what it is' | 'cost' | 'thinking' | 'inputs' | 'hosted tools' | 'availability';
|
|
43
|
+
/** Short human label. */
|
|
44
|
+
label: string;
|
|
45
|
+
/** The values this key accepts. Empty when the key is a bare flag (`vision`),
|
|
46
|
+
* which the parser reads as `key:yes`. */
|
|
47
|
+
values: string[];
|
|
48
|
+
/** True when the key also accepts `> N` / `< N`, so a picker can offer a number. */
|
|
49
|
+
numeric: boolean;
|
|
50
|
+
/** True when a bare `key` (no value) is meaningful — the parser expands it to
|
|
51
|
+
* `key:yes`. */
|
|
52
|
+
bare: boolean;
|
|
53
|
+
}
|
|
54
|
+
/** Every clause the query parser understands, as data.
|
|
55
|
+
*
|
|
56
|
+
* Exported because the alternative is a UI hand-listing the same tags: a second
|
|
57
|
+
* copy of this vocabulary, drifting from the parser the first time either moves,
|
|
58
|
+
* and drifting invisibly because a wrong tag reads as "no models matched" rather
|
|
59
|
+
* than as an error. Here the picker and the parser cannot disagree — both come
|
|
60
|
+
* from `KNOWN_KEYS`, `CAP_KEYS` and `BUILTIN_TOOL_KEYS` above.
|
|
61
|
+
*
|
|
62
|
+
* `type`, `provider`, `status` and `tier` take their values from the CATALOG
|
|
63
|
+
* when one is passed, because those are open sets: a provider ships a new model
|
|
64
|
+
* type and a hard-coded list is wrong that day. Without a catalog they come back
|
|
65
|
+
* empty rather than guessed — an empty list is honest, a stale list is not. */
|
|
66
|
+
export declare function filterFacets(catalog?: {
|
|
67
|
+
list(): ModelInfo[];
|
|
68
|
+
}): FilterFacet[];
|
|
69
|
+
/** The shorthand tags the parser expands before matching (`cheap` → `price:low`).
|
|
70
|
+
* A picker can show these as one-click presets. */
|
|
71
|
+
export declare function filterAliases(): Record<string, string>;
|
|
37
72
|
/** All matching models, ranked cheapest-first (tiebreak: newest version). */
|
|
38
73
|
export declare function selectModels(query: string | string[], opts?: SelectOptions): ModelInfo[];
|
|
39
74
|
/** The single best match as a `provider/slug` string (feedable to complete), or null. */
|