@compilr-dev/sdk 0.34.0 → 0.35.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.
@@ -33,13 +33,50 @@ function statusFromText(text) {
33
33
  const m = text.match(/\b(400|401|403|404|408|409|413|422|429|500|502|503|504|529)\b/);
34
34
  return m ? Number(m[1]) : undefined;
35
35
  }
36
+ /**
37
+ * Textual markers for "the key is wrong", measured against the real wire text on 2026-10-03.
38
+ *
39
+ * ⚠️ EVERY MARKER BUT ONE USED TO BE AN ANTHROPIC STRING, so a bad key classified as `auth` for
40
+ * exactly one of three first-party providers. What the others actually send:
41
+ *
42
+ * Anthropic 401 {"error":{"type":"authentication_error","message":"API key is invalid."}}
43
+ * OpenAI 401 {"error":{"message":"Incorrect API key provided: sk-…","code":"invalid_api_key"}}
44
+ * Gemini 400 {"error":{"message":"API key not valid. Please pass a valid API key.",
45
+ * "details":[{"reason":"API_KEY_INVALID"}]}}
46
+ *
47
+ * Two independent reasons the old set missed them:
48
+ *
49
+ * 1. **Gemini answers 400, not 401**, so no status test can catch it — the text is the only
50
+ * signal, and "API key not valid" is not the substring "invalid api key".
51
+ * 2. **Our own providers rewrite the message and interpolate their name into the middle of the
52
+ * phrase**: `gemini-native` produces "Invalid Gemini API key. …" and `openai.ts` produces
53
+ * "Invalid OpenAI API key. …". Neither contains "invalid api key". In-process the thrown
54
+ * `ProviderError.statusCode` saved OpenAI; across an IPC boundary — Desktop, the path this
55
+ * module exists for — only the string crosses, and both classified as `unknown`.
56
+ *
57
+ * Hence `invalid(?: \w+)? api key`: it matches the bare phrase and the branded one, so a
58
+ * provider adding its name no longer silently breaks the classification.
59
+ */
60
+ const AUTH_MARKERS = [
61
+ /authentication_error/,
62
+ /permission_error/,
63
+ /unauthorized/,
64
+ /x-api-key/,
65
+ // Gemini: the message, and the machine-readable reason in `details`.
66
+ /api key not valid/,
67
+ /api[_ -]?key[_ -]?invalid/,
68
+ // OpenAI: the message, and its `error.code`.
69
+ /incorrect api key/,
70
+ /invalid_api_key/,
71
+ // Ours and anyone else's, with or without a provider name in the middle.
72
+ /invalid(?: \w+)? api key/,
73
+ /\b(?:no|missing) api key/,
74
+ ];
36
75
  function categorize(statusCode, text) {
37
76
  const lower = text.toLowerCase();
38
- // Auth: key issues. Match on status OR the provider's textual markers, since a
39
- // stringified error may not preserve the numeric code.
40
- if (statusCode === 401 ||
41
- statusCode === 403 ||
42
- /authentication_error|invalid x-api-key|invalid api key|no api key|x-api-key|permission_error|unauthorized/.test(lower)) {
77
+ // Auth: key issues. Match on status OR a textual marker, since a stringified error may not
78
+ // preserve the numeric code — and Gemini's bad-key status (400) is not an auth code at all.
79
+ if (statusCode === 401 || statusCode === 403 || AUTH_MARKERS.some((re) => re.test(lower))) {
43
80
  return 'auth';
44
81
  }
45
82
  if (statusCode === 429 || /rate.?limit|too many requests/.test(lower))
@@ -739,8 +739,24 @@ export function shouldClearHistoryOnModelChange(_fromModelId, _toModelId) {
739
739
  // Context Window Resolution
740
740
  // =============================================================================
741
741
  /**
742
- * Provider fallback context limits.
743
- * Used when a model is not in the registry or has no contextWindow set.
742
+ * What to assume when the endpoint is unknown.
743
+ *
744
+ * ⚠️ UNDER-REPORTING IS THE SAFE DIRECTION. Too small wastes context and compacts early; too
745
+ * large means compaction fires after the real limit, so the request 400s and reads as a provider
746
+ * fault. `custom` points at whatever the user is running — LM Studio, vLLM, a proxy — so it gets
747
+ * the same local-model assumption as Ollama rather than a number borrowed from a frontier model.
748
+ */
749
+ const UNKNOWN_ENDPOINT_CONTEXT = 32000;
750
+ /**
751
+ * Provider fallback context limits, used when a model is not in the registry or declares no
752
+ * `contextWindow` (every Groq / Together / Fireworks / Perplexity / OpenRouter / Ollama entry —
753
+ * those hosts define the window by deployment, so the registry does not guess per model).
754
+ *
755
+ * ⚠️ EXHAUSTIVE ON PURPOSE. This was a `default: return 200000`, which handed out ANTHROPIC's
756
+ * window to any provider not listed — so `getModelContextWindow('some-local-model', 'custom')`
757
+ * answered 200000 and a local 8k model would compact long after it had already overflowed. The
758
+ * `never` assignment below makes a new `ProviderType` a compile error here instead of a silent
759
+ * inheritance of someone else's limit.
744
760
  */
745
761
  function getProviderContextLimitFallback(provider) {
746
762
  switch (provider) {
@@ -763,8 +779,16 @@ function getProviderContextLimitFallback(provider) {
763
779
  return 131072;
764
780
  case 'openrouter':
765
781
  return 128000;
766
- default:
767
- return 200000;
782
+ case 'custom':
783
+ case undefined:
784
+ return UNKNOWN_ENDPOINT_CONTEXT;
785
+ default: {
786
+ // Unreachable while every ProviderType is handled above — and a COMPILE ERROR the moment
787
+ // one is added without a limit, which is the whole point of writing it this way.
788
+ const unhandled = provider;
789
+ void unhandled;
790
+ return UNKNOWN_ENDPOINT_CONTEXT;
791
+ }
768
792
  }
769
793
  }
770
794
  /**
package/dist/models.d.ts CHANGED
@@ -9,6 +9,20 @@ import type { ProviderType } from './config.js';
9
9
  * Default model for each provider.
10
10
  * Derived from MODEL_REGISTRY — uses the 'balanced' tier default for each provider.
11
11
  */
12
+ /**
13
+ * Default model for each provider — the 'balanced' tier entry from MODEL_REGISTRY.
14
+ *
15
+ * ⚠️ THE KEYS COME FROM `PROVIDER_METADATA`, NOT A HAND-WRITTEN LIST. The list used to be typed
16
+ * out here and was missing `ollama-anthropic`, so `DEFAULT_MODELS['ollama-anthropic']` was
17
+ * `undefined` behind a `Record<ProviderType, string>` — the `{} as Record<…>` cast is what let
18
+ * that compile. `PROVIDER_METADATA` is a true complete Record, so adding a provider to the union
19
+ * now forces an entry here too.
20
+ *
21
+ * ⚠️ `custom` IS DELIBERATELY EMPTY. It used to fall through to `'gpt-6-sol'`, so pointing
22
+ * `custom` at LM Studio or vLLM without naming a model requested one that endpoint has never
23
+ * heard of. A custom endpoint's model is the user's to name; there is no default to invent, and
24
+ * an empty string fails immediately and obviously instead of looking like an intent.
25
+ */
12
26
  export declare const DEFAULT_MODELS: Record<ProviderType, string>;
13
27
  /**
14
28
  * Default context window when model is unknown
package/dist/models.js CHANGED
@@ -4,36 +4,39 @@
4
4
  * This file maintains backward compatibility with the original SDK API.
5
5
  * Internally, it delegates to the full model registry in ./models/.
6
6
  */
7
- import { getDefaultModelForTier, getModelContextWindow, MODEL_REGISTRY } from './models/index.js';
7
+ import { getDefaultModelForTier, getModelContextWindow, MODEL_REGISTRY, PROVIDER_METADATA, } from './models/index.js';
8
8
  /**
9
9
  * Default model for each provider.
10
10
  * Derived from MODEL_REGISTRY — uses the 'balanced' tier default for each provider.
11
11
  */
12
+ /**
13
+ * Default model for each provider — the 'balanced' tier entry from MODEL_REGISTRY.
14
+ *
15
+ * ⚠️ THE KEYS COME FROM `PROVIDER_METADATA`, NOT A HAND-WRITTEN LIST. The list used to be typed
16
+ * out here and was missing `ollama-anthropic`, so `DEFAULT_MODELS['ollama-anthropic']` was
17
+ * `undefined` behind a `Record<ProviderType, string>` — the `{} as Record<…>` cast is what let
18
+ * that compile. `PROVIDER_METADATA` is a true complete Record, so adding a provider to the union
19
+ * now forces an entry here too.
20
+ *
21
+ * ⚠️ `custom` IS DELIBERATELY EMPTY. It used to fall through to `'gpt-6-sol'`, so pointing
22
+ * `custom` at LM Studio or vLLM without naming a model requested one that endpoint has never
23
+ * heard of. A custom endpoint's model is the user's to name; there is no default to invent, and
24
+ * an empty string fails immediately and obviously instead of looking like an intent.
25
+ */
12
26
  export const DEFAULT_MODELS = (() => {
13
- const providers = [
14
- 'claude',
15
- 'openai',
16
- 'gemini',
17
- 'ollama',
18
- 'together',
19
- 'groq',
20
- 'fireworks',
21
- 'perplexity',
22
- 'openrouter',
23
- 'custom',
24
- ];
25
27
  const result = {};
26
- for (const provider of providers) {
27
- const balanced = getDefaultModelForTier(provider, 'balanced');
28
- if (balanced) {
29
- result[provider] = balanced.id;
30
- }
31
- else {
32
- // Fallback: first supported model for this provider, or a sensible default
33
- const firstModel = MODEL_REGISTRY.find((m) => m.provider === provider && m.status === 'supported');
34
- // Fallback only when the registry has no model at all for the provider; gpt-4o is gone.
35
- result[provider] = firstModel?.id ?? 'gpt-6-sol';
28
+ for (const provider of Object.keys(PROVIDER_METADATA)) {
29
+ if (provider === 'custom') {
30
+ result[provider] = '';
31
+ continue;
36
32
  }
33
+ // `ollama-anthropic` is Ollama behind an Anthropic-shaped shim: same models.
34
+ const lookup = provider === 'ollama-anthropic' ? 'ollama' : provider;
35
+ const balanced = getDefaultModelForTier(lookup, 'balanced');
36
+ result[provider] =
37
+ balanced?.id ??
38
+ MODEL_REGISTRY.find((m) => m.provider === lookup && m.status === 'supported')?.id ??
39
+ '';
37
40
  }
38
41
  return result;
39
42
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/sdk",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "Universal agent runtime for building AI-powered applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -81,7 +81,7 @@
81
81
  "node": ">=20.0.0"
82
82
  },
83
83
  "dependencies": {
84
- "@compilr-dev/agents": "^0.7.1",
84
+ "@compilr-dev/agents": "^0.8.0",
85
85
  "@compilr-dev/logger": "^0.1.0",
86
86
  "ajv": "^6.14.0",
87
87
  "yaml": "^2.8.4"