@compilr-dev/sdk 0.34.0 → 0.36.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))
package/dist/index.d.ts CHANGED
@@ -64,7 +64,7 @@ export { detectProject, suggestProjectType, detectCommon } from './detection/ind
64
64
  export type { DetectProjectOptions, DetectionResult, ContentSummary } from './detection/index.js';
65
65
  export { createGuideTool, SHARED_GUIDE_ENTRIES, searchGuideEntries, topicToGuideEntry, } from './guide/index.js';
66
66
  export type { GuideEntry, ContentTopic, ContentSection, GuideToolConfig } from './guide/index.js';
67
- export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createSceneTools, SCENE_TOOL_NAMES, createSceneWriter, SCENE_JOURNAL_SIZE, createImageTools, ProjectAnchorStore, FileArtifactService, } from './platform/index.js';
67
+ export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createSceneTools, SCENE_TOOL_NAMES, resolveSelfAlias, createSceneWriter, SCENE_JOURNAL_SIZE, createImageTools, ProjectAnchorStore, FileArtifactService, } from './platform/index.js';
68
68
  export type { ProjectAnchorStoreConfig, FileArtifactServiceConfig, ImageToolsConfig, ImageResizer, } from './platform/index.js';
69
69
  export { STEP_ORDER, GUIDED_STEP_CRITERIA, getNextStep, isValidTransition, getStepCriteria, formatStepDisplay, getStepNumber, } from './platform/index.js';
70
70
  export type { CustomSkill, CompilrSkillExtension, ForkedFromMarker, InstalledFromMarker, SkillEligibilityContext, SkillCollision, SkillDiffLine, SkillValidationIssue, ScopeConfig, SkillResolution, } from './skills/index.js';
package/dist/index.js CHANGED
@@ -147,7 +147,7 @@ export { createGuideTool, SHARED_GUIDE_ENTRIES, searchGuideEntries, topicToGuide
147
147
  // =============================================================================
148
148
  // Platform Tools (runtime — createPlatformTools factory + individual factories)
149
149
  // =============================================================================
150
- export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createSceneTools, SCENE_TOOL_NAMES, createSceneWriter, SCENE_JOURNAL_SIZE, createImageTools, ProjectAnchorStore, FileArtifactService, } from './platform/index.js';
150
+ export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createSceneTools, SCENE_TOOL_NAMES, resolveSelfAlias, createSceneWriter, SCENE_JOURNAL_SIZE, createImageTools, ProjectAnchorStore, FileArtifactService, } from './platform/index.js';
151
151
  // =============================================================================
152
152
  // Platform Workflow (pure step-criteria logic)
153
153
  // =============================================================================
@@ -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
  })();
@@ -47,9 +47,29 @@ export interface PlatformHooks {
47
47
  id: number;
48
48
  name: string;
49
49
  }) => void;
50
- /** Resolve owner aliases (e.g., 'self' → active agent ID) */
50
+ /**
51
+ * Resolve owner aliases (e.g., 'self' → active agent ID).
52
+ *
53
+ * ⚠️ MUST read the agent executing RIGHT NOW, not the one that was active when the tools were
54
+ * built — platform tools are built once and memoised by every host, so a captured value freezes
55
+ * for the session. Same trap as `cwd`, below. Implement it with {@link resolveSelfAlias}.
56
+ */
51
57
  resolveOwner?: (alias: 'self') => string | undefined;
52
58
  }
59
+ /**
60
+ * Turn a host's notion of "the agent running now" into an owner alias.
61
+ *
62
+ * `'default'` is the coordinator slot every single-agent session uses, not an identity, so it
63
+ * resolves to `undefined` — which callers then render as their own fallback (`'agent'` for a work
64
+ * item's author) rather than writing the literal string `default` into stored data.
65
+ *
66
+ * ⚠️ This lives here because BOTH hosts need the same answer and only one of them had it. The CLI
67
+ * implemented the rule inline; Desktop passed no `resolveOwner` at all, so every scene edit
68
+ * recorded no agent and `consumeUserEdits` keyed every agent on the empty string — one shared
69
+ * "already told" mark, so only the FIRST agent to touch a canvas was ever told about the user's
70
+ * edits to it. The rule being in one host and not the other is a bug in both.
71
+ */
72
+ export declare function resolveSelfAlias(agentId: string | null | undefined): string | undefined;
53
73
  export interface PlatformToolsConfig {
54
74
  /** Data access layer */
55
75
  context: PlatformContext;
@@ -4,6 +4,24 @@
4
4
  * Provides a single entry point for data access, abstracting over the
5
5
  * underlying storage backend (SQLite in CLI, PostgreSQL in web/API).
6
6
  */
7
+ /**
8
+ * Turn a host's notion of "the agent running now" into an owner alias.
9
+ *
10
+ * `'default'` is the coordinator slot every single-agent session uses, not an identity, so it
11
+ * resolves to `undefined` — which callers then render as their own fallback (`'agent'` for a work
12
+ * item's author) rather than writing the literal string `default` into stored data.
13
+ *
14
+ * ⚠️ This lives here because BOTH hosts need the same answer and only one of them had it. The CLI
15
+ * implemented the rule inline; Desktop passed no `resolveOwner` at all, so every scene edit
16
+ * recorded no agent and `consumeUserEdits` keyed every agent on the empty string — one shared
17
+ * "already told" mark, so only the FIRST agent to touch a canvas was ever told about the user's
18
+ * edits to it. The rule being in one host and not the other is a bug in both.
19
+ */
20
+ export function resolveSelfAlias(agentId) {
21
+ if (!agentId || agentId === 'default')
22
+ return undefined;
23
+ return agentId;
24
+ }
7
25
  /**
8
26
  * Resolve a `cwd` that may be a value or a getter.
9
27
  *
@@ -5,6 +5,8 @@ export type { ProjectType, ProjectStatus, RepoPattern, WorkflowMode, LifecycleSt
5
5
  export type { IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository, ICanvasRepository, } from './repositories.js';
6
6
  export type { IAnchorService, IArtifactService, IEpisodeService, ICanvasRenderer, RenderedCanvasImage, AnchorData, ArtifactType, ArtifactData, ArtifactSummaryData, WorkEpisode, ProjectWorkSummary, } from './services.js';
7
7
  export type { PlatformContext, PlatformToolsConfig, PlatformHooks } from './context.js';
8
+ /** Owner-alias rule shared by every host — see its doc comment for why it lives here. */
9
+ export { resolveSelfAlias } from './context.js';
8
10
  export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createSceneTools, SCENE_TOOL_NAMES, sceneOutline, createImageTools, } from './tools/index.js';
9
11
  export { createSceneWriter, SCENE_JOURNAL_SIZE } from './scene-writer.js';
10
12
  export type { ISceneWriter, SceneReadResult, SceneApplyResult, SceneCreateResult, SceneLastEdit, } from './scene-writer.js';
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Platform — Repository interfaces, data models, tools, and workflow.
3
3
  */
4
+ /** Owner-alias rule shared by every host — see its doc comment for why it lives here. */
5
+ export { resolveSelfAlias } from './context.js';
4
6
  // Platform tools (runtime)
5
7
  export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createSceneTools, SCENE_TOOL_NAMES, sceneOutline, createImageTools, } from './tools/index.js';
6
8
  // The scene writer — one per host process, shared by the scene tools and the host's own
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/sdk",
3
- "version": "0.34.0",
3
+ "version": "0.36.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"