@sayknow-cli/ai 0.5.24 → 0.5.26

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 CHANGED
@@ -2,7 +2,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- ## [0.5.24] - 2026-09-21
5
+ ## [0.5.26] - 2026-09-23
6
+ ### Fixed
7
+ - API-key `/login` now upserts a second key instead of replacing the whole provider pool. Same-key re-login still reuses the existing row.
8
+ - Bundle `xai/grok-4.7` and use it as the xAI default without changing explicitly saved model choices. Preserve its `low`–`xhigh` reasoning range during catalog generation. Cost metadata records the standard (<200k prompt tokens) tariff; the existing cost engine does not represent long-context price tiers.
9
+ - Bundle `anthropic/claude-opus-5-5` (1M context, 128K output, vision, adaptive thinking). The provider default stays `claude-sonnet-4-6`: promoting a Sonnet default to an Opus would raise per-token cost for every user, so selecting Opus 5.5 stays explicit. Cost metadata records the standard tariff ($4/$20, cache read $0.20 at the Opus-5.5-specific 0.05x multiplier, 5-minute cache write $5); the cost engine represents neither the fast-mode tier ($8/$40) nor long-context tiers. Anthropic's server-side default effort (`medium`) is deliberately not encoded as `thinking.defaultLevel`, because `inferDefaultEffort` has no Anthropic rule and catalog regeneration would strip it — every sibling Opus entry omits it for the same reason.
6
10
 
7
11
  ## [0.5.12] - 2026-09-15
8
12
  ### Added
@@ -226,7 +226,7 @@ export interface AuthCredentialStore {
226
226
  }): Promise<void>;
227
227
  /**
228
228
  * Optional async write hook for upserting a single credential. When present,
229
- * `AuthStorage.#upsertOAuthCredential` routes through this instead of the
229
+ * `AuthStorage.#upsertStoredCredential` routes through this instead of the
230
230
  * sync `upsertAuthCredentialForProvider`. `RemoteAuthCredentialStore` uses
231
231
  * it to send the upsert to the broker via `POST /v1/credential`.
232
232
  *
@@ -236,10 +236,11 @@ export interface AuthCredentialStore {
236
236
  upsertAuthCredentialRemote?(provider: string, credential: AuthCredential): Promise<StoredAuthCredential[]>;
237
237
  upsertAuthCredentialRemoteIfAbsent?(provider: string, credential: AuthCredential): Promise<AuthCredentialIfAbsentResult>;
238
238
  /**
239
- * Optional async write hook for replace-all semantics (e.g. API-key login
240
- * overwriting any previous keys for the same provider). When present,
239
+ * Optional async write hook for replace-all semantics (logout-style
240
+ * overwrite of every stored credential for a provider). When present,
241
241
  * `AuthStorage.set` routes through this instead of the sync
242
- * `replaceAuthCredentialsForProvider`.
242
+ * `replaceAuthCredentialsForProvider`. API-key login no longer uses this
243
+ * path: it upserts so a second account is added instead of wiping the pool.
243
244
  */
244
245
  replaceAuthCredentialsRemote?(provider: string, credentials: AuthCredential[]): Promise<StoredAuthCredential[]>;
245
246
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@sayknow-cli/ai",
4
- "version": "0.5.24",
4
+ "version": "0.5.26",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://sayknow-cli.com",
7
7
  "author": "jaybeyond",
@@ -43,7 +43,7 @@
43
43
  "dependencies": {
44
44
  "@anthropic-ai/sdk": "^0.94.0",
45
45
  "@bufbuild/protobuf": "^2.12.0",
46
- "@sayknow-cli/utils": "0.5.24",
46
+ "@sayknow-cli/utils": "0.5.26",
47
47
  "openai": "^6.36.0",
48
48
  "partial-json": "^0.1.7",
49
49
  "zod": "4.4.3"
@@ -387,7 +387,7 @@ export interface AuthCredentialStore {
387
387
  markCredentialSuspect?(credentialId: number, opts?: { signal?: AbortSignal }): Promise<void>;
388
388
  /**
389
389
  * Optional async write hook for upserting a single credential. When present,
390
- * `AuthStorage.#upsertOAuthCredential` routes through this instead of the
390
+ * `AuthStorage.#upsertStoredCredential` routes through this instead of the
391
391
  * sync `upsertAuthCredentialForProvider`. `RemoteAuthCredentialStore` uses
392
392
  * it to send the upsert to the broker via `POST /v1/credential`.
393
393
  *
@@ -400,10 +400,11 @@ export interface AuthCredentialStore {
400
400
  credential: AuthCredential,
401
401
  ): Promise<AuthCredentialIfAbsentResult>;
402
402
  /**
403
- * Optional async write hook for replace-all semantics (e.g. API-key login
404
- * overwriting any previous keys for the same provider). When present,
403
+ * Optional async write hook for replace-all semantics (logout-style
404
+ * overwrite of every stored credential for a provider). When present,
405
405
  * `AuthStorage.set` routes through this instead of the sync
406
- * `replaceAuthCredentialsForProvider`.
406
+ * `replaceAuthCredentialsForProvider`. API-key login no longer uses this
407
+ * path: it upserts so a second account is added instead of wiping the pool.
407
408
  */
408
409
  replaceAuthCredentialsRemote?(provider: string, credentials: AuthCredential[]): Promise<StoredAuthCredential[]>;
409
410
  /**
@@ -1550,7 +1551,7 @@ export class AuthStorage {
1550
1551
  };
1551
1552
  }
1552
1553
 
1553
- async #upsertOAuthCredential(provider: string, credential: OAuthCredential): Promise<void> {
1554
+ async #upsertStoredCredential(provider: string, credential: AuthCredential): Promise<void> {
1554
1555
  const stored = this.#store.upsertAuthCredentialRemote
1555
1556
  ? await this.#store.upsertAuthCredentialRemote(provider, credential)
1556
1557
  : this.#store.upsertAuthCredentialForProvider(provider, credential);
@@ -1562,6 +1563,17 @@ export class AuthStorage {
1562
1563
  this.#invalidateUsageCacheForProvider(provider);
1563
1564
  }
1564
1565
 
1566
+ async #dropStoredApiKeysForProvider(provider: string): Promise<void> {
1567
+ const remaining = this.#getCredentialsForProvider(provider).filter(credential => credential.type !== "api_key");
1568
+ if (remaining.length === this.#getCredentialsForProvider(provider).length) return;
1569
+ await this.set(provider, remaining);
1570
+ }
1571
+
1572
+ async #upsertOAuthCredential(provider: string, credential: OAuthCredential): Promise<void> {
1573
+ await this.#dropStoredApiKeysForProvider(provider);
1574
+ await this.#upsertStoredCredential(provider, credential);
1575
+ }
1576
+
1565
1577
  #invalidateUsageCacheForProvider(provider: string): void {
1566
1578
  this.#usageRequestInFlight.clear();
1567
1579
  this.#usageReportsInFlight.clear();
@@ -1696,7 +1708,7 @@ export class AuthStorage {
1696
1708
  let credentials: OAuthCredentials;
1697
1709
  const saveApiKeyCredential = async (apiKey: string): Promise<void> => {
1698
1710
  const newCredential: ApiKeyCredential = { type: "api_key", key: apiKey };
1699
- await this.set(provider, newCredential);
1711
+ await this.#upsertStoredCredential(provider, newCredential);
1700
1712
  };
1701
1713
  const manualCodeInput = () => ctrl.onPrompt({ message: "Paste the authorization code (or full redirect URL):" });
1702
1714
  switch (provider) {
@@ -2048,13 +2060,6 @@ export class AuthStorage {
2048
2060
  }
2049
2061
  }
2050
2062
  const newCredential: OAuthCredential = { type: "oauth", ...credentials };
2051
- if (provider === "xai") {
2052
- const existingOAuthCredentials = this.#getCredentialsForProvider(provider).filter(
2053
- (credential): credential is OAuthCredential => credential.type === "oauth",
2054
- );
2055
- await this.set(provider, [...existingOAuthCredentials, newCredential]);
2056
- return;
2057
- }
2058
2063
  await this.#upsertOAuthCredential(provider, newCredential);
2059
2064
  }
2060
2065
 
@@ -475,12 +475,12 @@ function applyGeneratedModelPolicy(model: ApiModel<Api>): void {
475
475
  if (model.provider !== "opencode-go" && model.id === "minimax-m3") {
476
476
  model.contextWindow = 512_000;
477
477
  }
478
- // xAI Grok 4.5/4.6: official reasoning docs (docs.x.ai) expose
479
- // low/medium/high, with xhigh only on grok-4.6+. Catalog snapshots and
478
+ // xAI Grok 4.5/4.6/4.7: official reasoning docs (docs.x.ai) expose
479
+ // low/medium/high, with xhigh on the reviewed 4.6 and 4.7 models. Catalog snapshots and
480
480
  // openai-compat historically clamped everything to high because
481
481
  // supportsReasoningEffort was false for Grok; pin the documented ranges so
482
482
  // profile suffixes and requireSupportedEffort stay honest across regen.
483
- if (model.provider === "xai" && /^grok-4\.6(?:$|[-.])/.test(model.id)) {
483
+ if (model.provider === "xai" && /^grok-4\.(?:6|7)(?:$|[-.])/.test(model.id)) {
484
484
  if (model.thinking) {
485
485
  model.thinking = {
486
486
  ...model.thinking,
package/src/models.json CHANGED
@@ -5005,6 +5005,31 @@
5005
5005
  "maxLevel": "max"
5006
5006
  }
5007
5007
  },
5008
+ "claude-opus-5-5": {
5009
+ "id": "claude-opus-5-5",
5010
+ "name": "Anthropic Opus 5.5",
5011
+ "api": "anthropic-messages",
5012
+ "provider": "anthropic",
5013
+ "baseUrl": "https://api.anthropic.com",
5014
+ "reasoning": true,
5015
+ "input": [
5016
+ "text",
5017
+ "image"
5018
+ ],
5019
+ "cost": {
5020
+ "input": 4,
5021
+ "output": 20,
5022
+ "cacheRead": 0.2,
5023
+ "cacheWrite": 5
5024
+ },
5025
+ "contextWindow": 1000000,
5026
+ "maxTokens": 128000,
5027
+ "thinking": {
5028
+ "mode": "anthropic-adaptive",
5029
+ "minLevel": "minimal",
5030
+ "maxLevel": "max"
5031
+ }
5032
+ },
5008
5033
  "claude-sonnet-4-0": {
5009
5034
  "id": "claude-sonnet-4-0",
5010
5035
  "name": "Anthropic Sonnet 4 (latest)",
@@ -90861,6 +90886,32 @@
90861
90886
  "defaultLevel": "high"
90862
90887
  }
90863
90888
  },
90889
+ "grok-4.7": {
90890
+ "id": "grok-4.7",
90891
+ "name": "Grok 4.7",
90892
+ "api": "openai-completions",
90893
+ "provider": "xai",
90894
+ "baseUrl": "https://api.x.ai/v1",
90895
+ "reasoning": true,
90896
+ "input": [
90897
+ "text",
90898
+ "image"
90899
+ ],
90900
+ "cost": {
90901
+ "input": 2,
90902
+ "output": 6,
90903
+ "cacheRead": 0.5,
90904
+ "cacheWrite": 0
90905
+ },
90906
+ "contextWindow": 500000,
90907
+ "maxTokens": 500000,
90908
+ "thinking": {
90909
+ "mode": "effort",
90910
+ "minLevel": "low",
90911
+ "maxLevel": "xhigh",
90912
+ "defaultLevel": "high"
90913
+ }
90914
+ },
90864
90915
  "grok-beta": {
90865
90916
  "id": "grok-beta",
90866
90917
  "name": "Grok Beta",
@@ -166,7 +166,7 @@ export const PROVIDER_DESCRIPTORS: readonly ProviderDescriptor[] = [
166
166
  config => fuguModelManagerOptions(config),
167
167
  catalog("Sakana Fugu", ["FUGU_API_KEY"]),
168
168
  ),
169
- descriptor("xai", "grok-4.6", config => xaiModelManagerOptions(config)),
169
+ descriptor("xai", "grok-4.7", config => xaiModelManagerOptions(config)),
170
170
  catalogDescriptor(
171
171
  "deepseek",
172
172
  "deepseek-v4-pro",