@agent-native/core 0.119.4 → 0.119.5

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.
Files changed (71) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +8 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/engine/index.ts +2 -0
  5. package/corpus/core/src/agent/engine/registry.ts +95 -2
  6. package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +8 -0
  7. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +5 -1
  8. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +9 -1
  9. package/corpus/templates/slides/actions/patch-deck.ts +2 -1
  10. package/corpus/templates/slides/changelog/2026-07-23-opening-or-refining-a-deck-no-longer-crashes-with-a-blank-er.md +6 -0
  11. package/corpus/templates/slides/shared/aspect-ratios.ts +4 -1
  12. package/corpus/templates/tasks/.oxfmtrc.json +8 -0
  13. package/corpus/templates/tasks/AGENTS.md +34 -41
  14. package/corpus/templates/tasks/README.md +13 -6
  15. package/corpus/templates/tasks/actions/bulk-delete-inbox-items.ts +10 -8
  16. package/corpus/templates/tasks/actions/bulk-delete-tasks.ts +10 -8
  17. package/corpus/templates/tasks/actions/bulk-mark-inbox-items-ready.ts +1 -1
  18. package/corpus/templates/tasks/actions/bulk-update-tasks.ts +12 -10
  19. package/corpus/templates/tasks/actions/create-inbox-item.ts +7 -4
  20. package/corpus/templates/tasks/actions/create-task.ts +2 -1
  21. package/corpus/templates/tasks/actions/delete-inbox-item.ts +6 -4
  22. package/corpus/templates/tasks/actions/delete-task.ts +1 -1
  23. package/corpus/templates/tasks/actions/list-inbox-items.ts +4 -2
  24. package/corpus/templates/tasks/actions/list-visible-task-fields.ts +3 -1
  25. package/corpus/templates/tasks/actions/mark-inbox-item-ready.ts +6 -4
  26. package/corpus/templates/tasks/actions/navigate.ts +16 -14
  27. package/corpus/templates/tasks/actions/reorder-inbox-items.ts +9 -7
  28. package/corpus/templates/tasks/actions/update-inbox-item.ts +6 -4
  29. package/corpus/templates/tasks/actions/update-task.ts +12 -9
  30. package/corpus/templates/tasks/actions/update-visible-task-fields.ts +10 -8
  31. package/corpus/templates/tasks/actions/view-screen.ts +1 -1
  32. package/corpus/templates/tasks/app/components/tasks/fields/TaskFieldsSidebar.tsx +15 -0
  33. package/corpus/templates/tasks/changelog/2026-07-23-task-detail-extensions.md +6 -0
  34. package/corpus/templates/tasks/drizzle.config.ts +3 -0
  35. package/corpus/templates/tasks/package.json +4 -1
  36. package/corpus/templates/tasks/server/db/schema.ts +0 -10
  37. package/corpus/templates/tasks/server/db/test-tasks-table.ts +0 -5
  38. package/corpus/templates/tasks/server/plugins/agent-chat.ts +1 -1
  39. package/corpus/templates/tasks/server/plugins/db.ts +43 -10
  40. package/corpus/templates/tasks/server/user-config/store.ts +42 -81
  41. package/corpus/templates/tasks/tsconfig.json +1 -2
  42. package/corpus/templates/tasks/vitest.config.ts +10 -1
  43. package/dist/agent/engine/index.d.ts +1 -1
  44. package/dist/agent/engine/index.d.ts.map +1 -1
  45. package/dist/agent/engine/index.js +1 -1
  46. package/dist/agent/engine/index.js.map +1 -1
  47. package/dist/agent/engine/registry.d.ts +32 -1
  48. package/dist/agent/engine/registry.d.ts.map +1 -1
  49. package/dist/agent/engine/registry.js +75 -3
  50. package/dist/agent/engine/registry.js.map +1 -1
  51. package/dist/collab/routes.d.ts +1 -1
  52. package/dist/collab/struct-routes.d.ts +1 -1
  53. package/dist/notifications/routes.d.ts +1 -1
  54. package/dist/observability/routes.d.ts +1 -1
  55. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  56. package/dist/scripts/agent-engines/list-agent-engines.js +8 -2
  57. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  58. package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
  59. package/dist/scripts/agent-engines/manage-agent-engine.js +5 -2
  60. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  61. package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
  62. package/dist/scripts/agent-engines/set-agent-engine.js +9 -2
  63. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  64. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  65. package/package.json +1 -1
  66. package/src/agent/engine/index.ts +2 -0
  67. package/src/agent/engine/registry.ts +95 -2
  68. package/src/scripts/agent-engines/list-agent-engines.ts +8 -0
  69. package/src/scripts/agent-engines/manage-agent-engine.ts +5 -1
  70. package/src/scripts/agent-engines/set-agent-engine.ts +9 -1
  71. package/corpus/templates/tasks/.prettierrc +0 -5
@@ -1 +1 @@
1
- {"version":3,"file":"set-agent-engine.js","sourceRoot":"","sources":["../../../src/scripts/agent-engines/set-agent-engine.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,6BAA6B,EAC7B,8BAA8B,EAC9B,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,WAAW,EACT,oKAAoK;IACtK,UAAU,EAAE;QACV,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,kIAAkI;aACrI;YACD,KAAK,EAAE;gBACL,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,8HAA8H;aACjI;SACF;QACD,QAAQ,EAAE,CAAC,QAAQ,CAAC;KACrB;CACF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAA4B;IACpD,sBAAsB,EAAE,CAAC;IAEzB,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IAE3C,IAAI,CAAC,UAAU;QAAE,OAAO,6BAA6B,CAAC;IAEtD,MAAM,KAAK,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAC9C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,SAAS,GAAG,gBAAgB,EAAE;aACjC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAClB,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,OAAO,kBAAkB,UAAU,mCAAmC,SAAS,EAAE,CAAC;IACpF,CAAC;IAED,IAAI,CAAC,6BAA6B,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1C,OAAO,kBAAkB,UAAU,kFAAkF,KAAK,CAAC,cAAc,EAAE,CAAC;IAC9I,CAAC;IAED,MAAM,cAAc,GAAG,KAAK,IAAI,KAAK,CAAC,YAAY,CAAC;IACnD,MAAM,aAAa,GAAG,uBAAuB,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;IAErE,MAAM,MAAM,GAAG,MAAM,8BAA8B,CACjD,EAAE,MAAM,EAAE,UAAU,EAAE,EACtB,KAAK,CACN,CAAC;IACF,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,cAAc,GAAG,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,OAAO,oBAAoB,UAAU,mFAAmF,cAAc,iDAAiD,CAAC;IAC1L,CAAC;IAED,MAAM,UAAU,CAAC,cAAc,EAAE;QAC/B,MAAM,EAAE,UAAU;QAClB,KAAK,EAAE,aAAa;KACrB,CAAC,CAAC;IAEH,MAAM,cAAc,GAClB,aAAa,KAAK,cAAc;QAC9B,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,qBAAqB,cAAc,iCAAiC,aAAa,sBAAsB,CAAC;IAE9G,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,EAAE,EAAE,IAAI;QACR,MAAM,EAAE,UAAU;QAClB,KAAK,EAAE,aAAa;QACpB,OAAO,EAAE,uBAAuB,KAAK,CAAC,KAAK,eAAe,aAAa,2CAA2C,cAAc,EAAE;KACnI,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * set-agent-engine — validates and writes agent engine selection to settings.\n */\n\nimport {\n listAgentEngines,\n getAgentEngineEntry,\n isAgentEnginePackageInstalled,\n isStoredEngineUsableForRequest,\n normalizeModelForEngine,\n registerBuiltinEngines,\n} from \"../../agent/engine/index.js\";\nimport type { ActionTool } from \"../../agent/types.js\";\nimport { putSetting } from \"../../settings/index.js\";\n\nexport const tool: ActionTool = {\n description:\n 'Set the active AI agent engine and model. Changes take effect on the next conversation. Use manage-agent-engine with action=\"list\" first to see available options.',\n parameters: {\n type: \"object\",\n properties: {\n engine: {\n type: \"string\",\n description:\n 'Engine name (e.g. \"anthropic\", \"ai-sdk:openai\", \"ai-sdk:google\"). Use manage-agent-engine with action=\"list\" to see all options.',\n },\n model: {\n type: \"string\",\n description:\n \"Model ID to use with this engine (e.g. 'gpt-5.6-sol', 'claude-sonnet-5'). Defaults to the engine's default model if omitted.\",\n },\n },\n required: [\"engine\"],\n },\n};\n\nexport async function run(args: Record<string, string>): Promise<string> {\n registerBuiltinEngines();\n\n const { engine: engineName, model } = args;\n\n if (!engineName) return \"Error: --engine is required\";\n\n const entry = getAgentEngineEntry(engineName);\n if (!entry) {\n const available = listAgentEngines()\n .map((e) => e.name)\n .join(\", \");\n return `Error: Engine \"${engineName}\" not found. Available engines: ${available}`;\n }\n\n if (!isAgentEnginePackageInstalled(entry)) {\n return `Error: Engine \"${engineName}\" requires optional packages that are not installed in this app. Run: pnpm add ${entry.installPackage}`;\n }\n\n const requestedModel = model ?? entry.defaultModel;\n const resolvedModel = normalizeModelForEngine(entry, requestedModel);\n\n const usable = await isStoredEngineUsableForRequest(\n { engine: engineName },\n entry,\n );\n if (!usable) {\n const missingEnvVars = entry.requiredEnvVars.join(\", \");\n return `Warning: Engine \"${engineName}\" requires the following credentials which are not configured for this request: ${missingEnvVars}. The engine will fail at runtime without them.`;\n }\n\n await putSetting(\"agent-engine\", {\n engine: engineName,\n model: resolvedModel,\n });\n\n const normalizedNote =\n resolvedModel === requestedModel\n ? \"\"\n : ` Requested model \"${requestedModel}\" is no longer supported, so \"${resolvedModel}\" was saved instead.`;\n\n return JSON.stringify({\n ok: true,\n engine: engineName,\n model: resolvedModel,\n message: `Agent engine set to ${entry.label} with model ${resolvedModel}. Takes effect on the next conversation.${normalizedNote}`,\n });\n}\n"]}
1
+ {"version":3,"file":"set-agent-engine.js","sourceRoot":"","sources":["../../../src/scripts/agent-engines/set-agent-engine.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,6BAA6B,EAC7B,8BAA8B,EAC9B,uBAAuB,EACvB,kCAAkC,EAClC,sBAAsB,GACvB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,WAAW,EACT,oKAAoK;IACtK,UAAU,EAAE;QACV,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,kIAAkI;aACrI;YACD,KAAK,EAAE;gBACL,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,8HAA8H;aACjI;SACF;QACD,QAAQ,EAAE,CAAC,QAAQ,CAAC;KACrB;CACF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAA4B;IACpD,sBAAsB,EAAE,CAAC;IAEzB,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IAE3C,IAAI,CAAC,UAAU;QAAE,OAAO,6BAA6B,CAAC;IAEtD,MAAM,KAAK,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAC9C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,SAAS,GAAG,gBAAgB,EAAE;aACjC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAClB,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,OAAO,kBAAkB,UAAU,mCAAmC,SAAS,EAAE,CAAC;IACpF,CAAC;IAED,IAAI,CAAC,6BAA6B,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1C,OAAO,kBAAkB,UAAU,kFAAkF,KAAK,CAAC,cAAc,EAAE,CAAC;IAC9I,CAAC;IAED,MAAM,cAAc,GAAG,KAAK,IAAI,KAAK,CAAC,YAAY,CAAC;IACnD,yEAAyE;IACzE,8EAA8E;IAC9E,8EAA8E;IAC9E,0BAA0B;IAC1B,MAAM,oBAAoB,GAAG,MAAM,kCAAkC,CAAC,KAAK,CAAC,CAAC;IAC7E,MAAM,aAAa,GAAG,uBAAuB,CAAC,KAAK,EAAE,cAAc,EAAE;QACnE,oBAAoB;KACrB,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,8BAA8B,CACjD,EAAE,MAAM,EAAE,UAAU,EAAE,EACtB,KAAK,CACN,CAAC;IACF,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,cAAc,GAAG,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,OAAO,oBAAoB,UAAU,mFAAmF,cAAc,iDAAiD,CAAC;IAC1L,CAAC;IAED,MAAM,UAAU,CAAC,cAAc,EAAE;QAC/B,MAAM,EAAE,UAAU;QAClB,KAAK,EAAE,aAAa;KACrB,CAAC,CAAC;IAEH,MAAM,cAAc,GAClB,aAAa,KAAK,cAAc;QAC9B,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,qBAAqB,cAAc,iCAAiC,aAAa,sBAAsB,CAAC;IAE9G,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,EAAE,EAAE,IAAI;QACR,MAAM,EAAE,UAAU;QAClB,KAAK,EAAE,aAAa;QACpB,OAAO,EAAE,uBAAuB,KAAK,CAAC,KAAK,eAAe,aAAa,2CAA2C,cAAc,EAAE;KACnI,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * set-agent-engine — validates and writes agent engine selection to settings.\n */\n\nimport {\n listAgentEngines,\n getAgentEngineEntry,\n isAgentEnginePackageInstalled,\n isStoredEngineUsableForRequest,\n normalizeModelForEngine,\n resolveEnginePreservesCustomModels,\n registerBuiltinEngines,\n} from \"../../agent/engine/index.js\";\nimport type { ActionTool } from \"../../agent/types.js\";\nimport { putSetting } from \"../../settings/index.js\";\n\nexport const tool: ActionTool = {\n description:\n 'Set the active AI agent engine and model. Changes take effect on the next conversation. Use manage-agent-engine with action=\"list\" first to see available options.',\n parameters: {\n type: \"object\",\n properties: {\n engine: {\n type: \"string\",\n description:\n 'Engine name (e.g. \"anthropic\", \"ai-sdk:openai\", \"ai-sdk:google\"). Use manage-agent-engine with action=\"list\" to see all options.',\n },\n model: {\n type: \"string\",\n description:\n \"Model ID to use with this engine (e.g. 'gpt-5.6-sol', 'claude-sonnet-5'). Defaults to the engine's default model if omitted.\",\n },\n },\n required: [\"engine\"],\n },\n};\n\nexport async function run(args: Record<string, string>): Promise<string> {\n registerBuiltinEngines();\n\n const { engine: engineName, model } = args;\n\n if (!engineName) return \"Error: --engine is required\";\n\n const entry = getAgentEngineEntry(engineName);\n if (!entry) {\n const available = listAgentEngines()\n .map((e) => e.name)\n .join(\", \");\n return `Error: Engine \"${engineName}\" not found. Available engines: ${available}`;\n }\n\n if (!isAgentEnginePackageInstalled(entry)) {\n return `Error: Engine \"${engineName}\" requires optional packages that are not installed in this app. Run: pnpm add ${entry.installPackage}`;\n }\n\n const requestedModel = model ?? entry.defaultModel;\n // A static registry entry can't carry the runtime `preserveCustomModels`\n // flag, so resolve the OpenAI-compatible-endpoint capability here and pass it\n // through — otherwise a gateway model (e.g. an Ollama id) is rewritten to the\n // engine default on save.\n const preserveCustomModels = await resolveEnginePreservesCustomModels(entry);\n const resolvedModel = normalizeModelForEngine(entry, requestedModel, {\n preserveCustomModels,\n });\n\n const usable = await isStoredEngineUsableForRequest(\n { engine: engineName },\n entry,\n );\n if (!usable) {\n const missingEnvVars = entry.requiredEnvVars.join(\", \");\n return `Warning: Engine \"${engineName}\" requires the following credentials which are not configured for this request: ${missingEnvVars}. The engine will fail at runtime without them.`;\n }\n\n await putSetting(\"agent-engine\", {\n engine: engineName,\n model: resolvedModel,\n });\n\n const normalizedNote =\n resolvedModel === requestedModel\n ? \"\"\n : ` Requested model \"${requestedModel}\" is no longer supported, so \"${resolvedModel}\" was saved instead.`;\n\n return JSON.stringify({\n ok: true,\n engine: engineName,\n model: resolvedModel,\n message: `Agent engine set to ${entry.label} with model ${resolvedModel}. Takes effect on the next conversation.${normalizedNote}`,\n });\n}\n"]}
@@ -27,11 +27,11 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
27
27
  export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
28
28
  error: any;
29
29
  } | {
30
+ error?: undefined;
30
31
  ok: boolean;
31
32
  key: string;
32
33
  baseUrlKey?: string;
33
34
  scope: AgentEngineApiKeyScope;
34
- error?: undefined;
35
35
  }>>;
36
36
  export {};
37
37
  //# sourceMappingURL=agent-engine-api-key-route.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.119.4",
3
+ "version": "0.119.5",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -25,6 +25,8 @@ export {
25
25
  getConfiguredEngineNameForRequest,
26
26
  getStoredModelForEngine,
27
27
  normalizeModelForEngine,
28
+ resolveEnginePreservesCustomModels,
29
+ type NormalizeModelOptions,
28
30
  detectEngineFromEnv,
29
31
  detectEngineFromEnvForRequest,
30
32
  detectEngineFromUserSecrets,
@@ -96,6 +96,42 @@ function packageNameFromInstallSpecifier(specifier: string): string | null {
96
96
  return versionIndex === -1 ? trimmed : trimmed.slice(0, versionIndex);
97
97
  }
98
98
 
99
+ /**
100
+ * True only when there is positive evidence this module is executing from a
101
+ * bundled serverless function where optional dependencies were inlined into the
102
+ * bundle and are therefore NOT resolvable via `require.resolve` — even though
103
+ * the dynamic `import()` the engine uses to load them still works.
104
+ *
105
+ * Deliberately narrow. The Nitro Vercel/Netlify presets (which agent-native's
106
+ * own `deploy` command emits) inline optional peers and always set these env
107
+ * markers, so they are a reliable signal. Other serverless runtimes — a
108
+ * container on Cloud Run / Google Cloud Functions (`K_SERVICE` /
109
+ * `FUNCTION_TARGET`), or a plain AWS Lambda — commonly ship a real
110
+ * `node_modules` where `require.resolve` is authoritative; there a resolve miss
111
+ * means the package is genuinely absent and must NOT be masked. Those runtimes
112
+ * are still covered *when the code is actually bundled*, via the module-path
113
+ * check below, which stays false for a normal `node_modules` layout.
114
+ */
115
+ function isBundledServerlessRuntime(): boolean {
116
+ const env = process.env;
117
+ // Nitro's Vercel/Netlify presets inline optional peers into the function
118
+ // bundle; these platforms always set these markers.
119
+ if (env.VERCEL || env.NETLIFY) return true;
120
+ // Otherwise require direct evidence that this module is running from inside a
121
+ // bundle output directory (Vercel's `/var/task`, Nitro's `.output/server`,
122
+ // inlined `_libs`). This is the real signal that `require.resolve` cannot be
123
+ // trusted; it stays false for normal `node_modules` layouts (dev, tests, and
124
+ // container/Lambda/Cloud Run deploys that ship their dependencies), so a
125
+ // genuine "package not installed" miss still surfaces there.
126
+ try {
127
+ return /[\\/](?:_libs|\.vercel|\.netlify|\.output)[\\/]|\/var\/task\//.test(
128
+ import.meta.url ?? "",
129
+ );
130
+ } catch {
131
+ return false;
132
+ }
133
+ }
134
+
99
135
  function canResolvePackage(packageName: string): boolean {
100
136
  const cached = _packageAvailabilityCache.get(packageName);
101
137
  if (cached !== undefined) return cached;
@@ -104,7 +140,15 @@ function canResolvePackage(packageName: string): boolean {
104
140
  require.resolve(packageName);
105
141
  available = true;
106
142
  } catch {
107
- available = false;
143
+ // Bundled serverless runtimes (e.g. Nitro on Vercel/Netlify) inline optional
144
+ // provider packages into the function bundle, so require.resolve cannot find
145
+ // them even though the dynamic `import()` the engine actually uses to load
146
+ // them works. Treat them as available there and let the engine's own import
147
+ // be the real gate — it already fails with a clear "pnpm add …" message when
148
+ // the package is genuinely missing. Without this, every engine-usability
149
+ // gate rejects the AI-SDK engines at runtime and the agent silently falls
150
+ // back to the native Anthropic engine.
151
+ available = isBundledServerlessRuntime();
108
152
  }
109
153
  _packageAvailabilityCache.set(packageName, available);
110
154
  return available;
@@ -179,17 +223,41 @@ function findLatestSupportedVersionMatch(
179
223
  return best?.model;
180
224
  }
181
225
 
226
+ export interface NormalizeModelOptions {
227
+ /**
228
+ * Force unrecognized (custom) model IDs to be kept verbatim, as if
229
+ * `engine.preserveCustomModels` were set on a live engine instance.
230
+ *
231
+ * The settings actions call `normalizeModelForEngine` with a static registry
232
+ * ENTRY, which never carries the runtime `preserveCustomModels` flag — that
233
+ * is only set on the engine INSTANCE created with an OpenAI-compatible
234
+ * `baseUrl`. They resolve the capability with
235
+ * {@link resolveEnginePreservesCustomModels} and pass it here so a gateway
236
+ * model (e.g. an Ollama `gemma4`) is not rewritten to the OpenAI default on
237
+ * save/read. First-party OpenAI (no gateway) leaves this unset, so an unknown
238
+ * or invalid model still normalizes to a supported one.
239
+ */
240
+ preserveCustomModels?: boolean;
241
+ }
242
+
182
243
  export function normalizeModelForEngine(
183
244
  engine: Pick<
184
245
  AgentEngine,
185
246
  "name" | "defaultModel" | "supportedModels" | "preserveCustomModels"
186
247
  >,
187
248
  model: string | null | undefined,
249
+ options: NormalizeModelOptions = {},
188
250
  ): string {
189
251
  const candidate = typeof model === "string" ? model.trim() : "";
190
252
  if (!candidate) return engine.defaultModel;
191
253
 
192
- if (engine.preserveCustomModels) return candidate;
254
+ // Preserve custom IDs verbatim BEFORE any catalog/version matching, so a
255
+ // version-shaped gateway model that happens to share a family with a
256
+ // built-in model (e.g. `gpt-5.4` on an OpenAI-compatible endpoint) is not
257
+ // rewritten to a catalog entry.
258
+ if (engine.preserveCustomModels || options.preserveCustomModels) {
259
+ return candidate;
260
+ }
193
261
 
194
262
  if (engine.supportedModels.length === 0) return candidate;
195
263
 
@@ -203,6 +271,31 @@ export function normalizeModelForEngine(
203
271
  );
204
272
  }
205
273
 
274
+ /**
275
+ * Whether models saved or read for this engine ENTRY should be preserved
276
+ * verbatim instead of normalized against the built-in catalog.
277
+ *
278
+ * `normalizeModelForEngine` honors a live engine's `preserveCustomModels`, but
279
+ * that flag is only set on an AI SDK engine INSTANCE when the OpenAI provider
280
+ * is pointed at an OpenAI-compatible gateway (a custom base URL — e.g. Ollama
281
+ * Cloud or LiteLLM), whose model IDs are not in the built-in OpenAI catalog.
282
+ * The static registry entry the settings actions pass to
283
+ * `normalizeModelForEngine` cannot carry that runtime flag, so this async
284
+ * helper reproduces the same decision — `ai-sdk:openai` AND a resolved base URL
285
+ * — from the request's stored/deploy config. First-party OpenAI (no gateway)
286
+ * returns false so an unknown/invalid model still normalizes to a supported one.
287
+ */
288
+ export async function resolveEnginePreservesCustomModels(
289
+ entry: Pick<AgentEngineEntry, "name">,
290
+ ): Promise<boolean> {
291
+ if (entry.name !== "ai-sdk:openai") return false;
292
+ try {
293
+ return Boolean(await resolveOpenAiBaseUrl());
294
+ } catch {
295
+ return false;
296
+ }
297
+ }
298
+
206
299
  function assertAgentEnginePackageInstalled(entry: AgentEngineEntry): void {
207
300
  if (isAgentEnginePackageInstalled(entry)) return;
208
301
  const installHint = entry.installPackage
@@ -13,6 +13,7 @@ import {
13
13
  isAgentEnginePackageInstalled,
14
14
  isStoredEngineUsableForRequest,
15
15
  normalizeModelForEngine,
16
+ resolveEnginePreservesCustomModels,
16
17
  } from "../../agent/engine/index.js";
17
18
  import type { ActionTool } from "../../agent/types.js";
18
19
  import { getSetting } from "../../settings/index.js";
@@ -82,11 +83,18 @@ export async function run(args: Record<string, string> = {}): Promise<string> {
82
83
  ? current?.model
83
84
  : undefined;
84
85
  const currentEngineName = currentEntry?.name ?? "anthropic";
86
+ // Resolve the OpenAI-compatible-endpoint capability so a custom gateway model
87
+ // is reported as-is instead of being normalized to the engine default — the
88
+ // read-side counterpart of the same fix in set-/manage-agent-engine.
89
+ const preserveCustomModels = currentEntry
90
+ ? await resolveEnginePreservesCustomModels(currentEntry)
91
+ : false;
85
92
  const currentModel =
86
93
  currentEntry && !envUnavailable
87
94
  ? normalizeModelForEngine(
88
95
  currentEntry,
89
96
  currentModelCandidate ?? currentEntry.defaultModel,
97
+ { preserveCustomModels },
90
98
  )
91
99
  : (currentModelCandidate ?? DEFAULT_MODEL);
92
100
  const result = {
@@ -16,6 +16,7 @@ import {
16
16
  getAgentEngineEntry,
17
17
  isAgentEnginePackageInstalled,
18
18
  normalizeModelForEngine,
19
+ resolveEnginePreservesCustomModels,
19
20
  registerBuiltinEngines,
20
21
  } from "../../agent/engine/index.js";
21
22
  import type { ActionTool } from "../../agent/types.js";
@@ -113,7 +114,10 @@ async function runSetAppDefault(args: Record<string, string>): Promise<string> {
113
114
  if (!isAgentEnginePackageInstalled(entry)) {
114
115
  return `Error: Engine "${engine}" requires optional packages that are not installed in this app. Run: pnpm add ${entry.installPackage}`;
115
116
  }
116
- const normalizedModel = normalizeModelForEngine(entry, model);
117
+ const preserveCustomModels = await resolveEnginePreservesCustomModels(entry);
118
+ const normalizedModel = normalizeModelForEngine(entry, model, {
119
+ preserveCustomModels,
120
+ });
117
121
 
118
122
  const ctx = currentContext();
119
123
  const canUpdate = await canUpdateAgentAppModelDefaultSettings(
@@ -8,6 +8,7 @@ import {
8
8
  isAgentEnginePackageInstalled,
9
9
  isStoredEngineUsableForRequest,
10
10
  normalizeModelForEngine,
11
+ resolveEnginePreservesCustomModels,
11
12
  registerBuiltinEngines,
12
13
  } from "../../agent/engine/index.js";
13
14
  import type { ActionTool } from "../../agent/types.js";
@@ -54,7 +55,14 @@ export async function run(args: Record<string, string>): Promise<string> {
54
55
  }
55
56
 
56
57
  const requestedModel = model ?? entry.defaultModel;
57
- const resolvedModel = normalizeModelForEngine(entry, requestedModel);
58
+ // A static registry entry can't carry the runtime `preserveCustomModels`
59
+ // flag, so resolve the OpenAI-compatible-endpoint capability here and pass it
60
+ // through — otherwise a gateway model (e.g. an Ollama id) is rewritten to the
61
+ // engine default on save.
62
+ const preserveCustomModels = await resolveEnginePreservesCustomModels(entry);
63
+ const resolvedModel = normalizeModelForEngine(entry, requestedModel, {
64
+ preserveCustomModels,
65
+ });
58
66
 
59
67
  const usable = await isStoredEngineUsableForRequest(
60
68
  { engine: engineName },
@@ -1,5 +0,0 @@
1
- {
2
- "tabWidth": 2,
3
- "useTabs": false,
4
- "trailingComma": "all"
5
- }