@databricks/appkit 0.66.1 → 0.68.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.
Files changed (85) hide show
  1. package/CLAUDE.md +2 -2
  2. package/dist/appkit/package.js +1 -1
  3. package/dist/core/agent/frontmatter.js +23 -0
  4. package/dist/core/agent/frontmatter.js.map +1 -0
  5. package/dist/core/agent/load-agents.d.ts.map +1 -1
  6. package/dist/core/agent/load-agents.js +36 -6
  7. package/dist/core/agent/load-agents.js.map +1 -1
  8. package/dist/core/agent/skills/index.js +7 -0
  9. package/dist/core/agent/skills/load-skills.js +78 -0
  10. package/dist/core/agent/skills/load-skills.js.map +1 -0
  11. package/dist/core/agent/skills/parse-skill.js +69 -0
  12. package/dist/core/agent/skills/parse-skill.js.map +1 -0
  13. package/dist/core/agent/skills/read-resource.js +31 -0
  14. package/dist/core/agent/skills/read-resource.js.map +1 -0
  15. package/dist/core/agent/skills/render.js +33 -0
  16. package/dist/core/agent/skills/render.js.map +1 -0
  17. package/dist/core/agent/skills/resolve-catalog.js +78 -0
  18. package/dist/core/agent/skills/resolve-catalog.js.map +1 -0
  19. package/dist/core/agent/skills/types.d.ts +50 -0
  20. package/dist/core/agent/skills/types.d.ts.map +1 -0
  21. package/dist/core/agent/types.d.ts +47 -0
  22. package/dist/core/agent/types.d.ts.map +1 -1
  23. package/dist/core/agent/types.js.map +1 -1
  24. package/dist/database/contract/index.js +1 -1
  25. package/dist/database/contract/wire.d.ts.map +1 -1
  26. package/dist/database/contract/wire.js +5 -1
  27. package/dist/database/contract/wire.js.map +1 -1
  28. package/dist/database/errors.js +39 -3
  29. package/dist/database/errors.js.map +1 -1
  30. package/dist/database/runtime/data-path.js.map +1 -1
  31. package/dist/database/runtime/engine/drizzle-data-path.js +1 -1
  32. package/dist/database/runtime/engine/drizzle-data-path.js.map +1 -1
  33. package/dist/database/runtime/engine/translate.js +9 -2
  34. package/dist/database/runtime/engine/translate.js.map +1 -1
  35. package/dist/database/schema-builder/define-schema.d.ts +5 -1
  36. package/dist/database/schema-builder/define-schema.d.ts.map +1 -1
  37. package/dist/database/schema-builder/define-schema.js +4 -0
  38. package/dist/database/schema-builder/define-schema.js.map +1 -1
  39. package/dist/database/schema-builder/types.d.ts +7 -2
  40. package/dist/database/schema-builder/types.d.ts.map +1 -1
  41. package/dist/database/schema-builder/types.js.map +1 -1
  42. package/dist/plugins/agents/agents.d.ts +50 -0
  43. package/dist/plugins/agents/agents.d.ts.map +1 -1
  44. package/dist/plugins/agents/agents.js +225 -13
  45. package/dist/plugins/agents/agents.js.map +1 -1
  46. package/dist/plugins/agents/manifest.js +40 -21
  47. package/dist/plugins/agents/schemas.js +2 -1
  48. package/dist/plugins/agents/schemas.js.map +1 -1
  49. package/dist/plugins/database/crud/codecs.js +78 -0
  50. package/dist/plugins/database/crud/codecs.js.map +1 -0
  51. package/dist/plugins/database/crud/contract.js +151 -0
  52. package/dist/plugins/database/crud/contract.js.map +1 -0
  53. package/dist/plugins/database/crud/exposure.js +40 -0
  54. package/dist/plugins/database/crud/exposure.js.map +1 -0
  55. package/dist/plugins/database/crud/query.js +244 -0
  56. package/dist/plugins/database/crud/query.js.map +1 -0
  57. package/dist/plugins/database/crud/routes.js +124 -0
  58. package/dist/plugins/database/crud/routes.js.map +1 -0
  59. package/dist/plugins/database/database.d.ts +6 -0
  60. package/dist/plugins/database/database.d.ts.map +1 -1
  61. package/dist/plugins/database/database.js +58 -4
  62. package/dist/plugins/database/database.js.map +1 -1
  63. package/dist/plugins/database/defaults.js +28 -1
  64. package/dist/plugins/database/defaults.js.map +1 -1
  65. package/dist/plugins/database/entity-client.js +1 -1
  66. package/dist/plugins/database/entity-types.d.ts +13 -4
  67. package/dist/plugins/database/entity-types.d.ts.map +1 -1
  68. package/dist/plugins/database/lifecycle.js +2 -1
  69. package/dist/plugins/database/lifecycle.js.map +1 -1
  70. package/dist/plugins/database/types.d.ts +32 -0
  71. package/dist/plugins/database/types.d.ts.map +1 -1
  72. package/dist/shared/src/schemas/manifest.d.ts +35 -35
  73. package/docs/api/appkit/Function.database.md +3 -3
  74. package/docs/api/appkit/Function.defineSchema.md +14 -6
  75. package/docs/api/appkit/Interface.AgentDefinition.md +11 -0
  76. package/docs/api/appkit/Interface.AgentsPluginConfig.md +35 -0
  77. package/docs/api/appkit/Interface.RegisteredAgent.md +11 -0
  78. package/docs/api/appkit/Interface.Schema.md +10 -2
  79. package/docs/api/appkit/TypeAlias.IDatabaseConfig.md +20 -0
  80. package/docs/api/appkit/TypeAlias.ResolvedToolEntry.md +46 -0
  81. package/docs/api/appkit.md +2 -2
  82. package/docs/plugins/agents.md +68 -1
  83. package/llms.txt +2 -2
  84. package/package.json +1 -1
  85. package/sbom.cdx.json +1 -1
@@ -0,0 +1,78 @@
1
+ import { createLogger } from "../../../logging/logger.js";
2
+
3
+ //#region src/core/agent/skills/resolve-catalog.ts
4
+ const logger = createLogger("agents:skills");
5
+ /** Qualified-name scope prefix per source, used only on cross-source collision. */
6
+ const SCOPE_BY_SOURCE = {
7
+ "bundle-agent": "agent",
8
+ "bundle-global": "bundle",
9
+ volume: "volume"
10
+ };
11
+ /**
12
+ * Applies visibility (per-agent auto; global opt-in or auto-inherit) then
13
+ * collision handling: a unique name is addressable bare; a name provided by
14
+ * multiple sources becomes `<scope>:name` per source and the bare name is
15
+ * marked ambiguous (addressing it errors with the alternatives). Two skills
16
+ * with the same name from the *same* source is a fatal config error.
17
+ */
18
+ function resolveSkillCatalog(input) {
19
+ const { agentName, agentSkillNames, perAgentSkills, globalSkills, autoInherit } = input;
20
+ const visible = [...perAgentSkills];
21
+ if (autoInherit) visible.push(...globalSkills);
22
+ else if (agentSkillNames && agentSkillNames.length > 0) {
23
+ const wanted = new Set(agentSkillNames);
24
+ for (const skill of globalSkills) if (wanted.has(skill.name)) visible.push(skill);
25
+ const localNames = new Set(perAgentSkills.map((s) => s.name));
26
+ const globalNames = new Set(globalSkills.map((s) => s.name));
27
+ for (const want of agentSkillNames) if (!globalNames.has(want) && !localNames.has(want)) logger.warn("Agent '%s' lists skill '%s' in 'skills:', but no global or per-agent skill with that name exists.", agentName, want);
28
+ }
29
+ const byName = /* @__PURE__ */ new Map();
30
+ for (const skill of visible) {
31
+ const group = byName.get(skill.name) ?? [];
32
+ group.push(skill);
33
+ byName.set(skill.name, group);
34
+ }
35
+ const byAddress = /* @__PURE__ */ new Map();
36
+ const ambiguous = /* @__PURE__ */ new Map();
37
+ for (const [name, group] of byName) {
38
+ if (group.length === 1) {
39
+ byAddress.set(name, group[0]);
40
+ continue;
41
+ }
42
+ const alternatives = [];
43
+ for (const skill of group) {
44
+ const qualified = `${SCOPE_BY_SOURCE[skill.source]}:${name}`;
45
+ const existing = byAddress.get(qualified);
46
+ if (existing) throw new Error(`Agent '${agentName}': two '${skill.source}' skills are both named '${name}' (${existing.dir} and ${skill.dir}). Skill names must be unique within a source.`);
47
+ byAddress.set(qualified, skill);
48
+ alternatives.push(qualified);
49
+ }
50
+ alternatives.sort();
51
+ ambiguous.set(name, alternatives);
52
+ logger.warn("Agent '%s': skill name '%s' is provided by multiple sources; address it as %s.", agentName, name, alternatives.join(" or "));
53
+ }
54
+ return {
55
+ byAddress,
56
+ ambiguous,
57
+ catalog: [...byAddress.entries()].map(([address, skill]) => ({
58
+ name: address,
59
+ description: skill.description
60
+ })).sort((a, b) => a.name.localeCompare(b.name))
61
+ };
62
+ }
63
+ /**
64
+ * Resolves a requested skill name (bare or qualified) against a catalog.
65
+ * Throws a helpful error on ambiguous or unknown names.
66
+ */
67
+ function resolveSkill(catalog, requested) {
68
+ const direct = catalog.byAddress.get(requested);
69
+ if (direct) return direct;
70
+ const alternatives = catalog.ambiguous.get(requested);
71
+ if (alternatives) throw new Error(`Skill '${requested}' is ambiguous; specify one of: ${alternatives.join(", ")}.`);
72
+ const available = [...catalog.byAddress.keys()].sort().join(", ") || "<none>";
73
+ throw new Error(`Unknown skill '${requested}'. Available: ${available}.`);
74
+ }
75
+
76
+ //#endregion
77
+ export { resolveSkill, resolveSkillCatalog };
78
+ //# sourceMappingURL=resolve-catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-catalog.js","names":[],"sources":["../../../../src/core/agent/skills/resolve-catalog.ts"],"sourcesContent":["import { createLogger } from \"../../../logging/logger\";\nimport type {\n ResolvedSkillCatalog,\n SkillCatalogEntry,\n SkillDefinition,\n SkillSource,\n} from \"./types\";\n\nconst logger = createLogger(\"agents:skills\");\n\n/** Qualified-name scope prefix per source, used only on cross-source collision. */\nconst SCOPE_BY_SOURCE: Record<SkillSource, string> = {\n \"bundle-agent\": \"agent\",\n \"bundle-global\": \"bundle\",\n volume: \"volume\",\n};\n\ninterface ResolveCatalogInput {\n agentName: string;\n /** The agent's `skills:` frontmatter — opt-in selection from the global pool. */\n agentSkillNames?: string[];\n /** Skills private to this agent (`<id>/skills/`), always visible. */\n perAgentSkills: SkillDefinition[];\n /** Shared pool (bundle-global + volume), visible only when opted in or inherited. */\n globalSkills: SkillDefinition[];\n /** When true, every global skill is visible without an explicit `skills:` list. */\n autoInherit: boolean;\n}\n\n/**\n * Applies visibility (per-agent auto; global opt-in or auto-inherit) then\n * collision handling: a unique name is addressable bare; a name provided by\n * multiple sources becomes `<scope>:name` per source and the bare name is\n * marked ambiguous (addressing it errors with the alternatives). Two skills\n * with the same name from the *same* source is a fatal config error.\n */\nexport function resolveSkillCatalog(\n input: ResolveCatalogInput,\n): ResolvedSkillCatalog {\n const {\n agentName,\n agentSkillNames,\n perAgentSkills,\n globalSkills,\n autoInherit,\n } = input;\n\n const visible: SkillDefinition[] = [...perAgentSkills];\n if (autoInherit) {\n visible.push(...globalSkills);\n } else if (agentSkillNames && agentSkillNames.length > 0) {\n const wanted = new Set(agentSkillNames);\n for (const skill of globalSkills) {\n if (wanted.has(skill.name)) visible.push(skill);\n }\n const localNames = new Set(perAgentSkills.map((s) => s.name));\n const globalNames = new Set(globalSkills.map((s) => s.name));\n for (const want of agentSkillNames) {\n if (!globalNames.has(want) && !localNames.has(want)) {\n logger.warn(\n \"Agent '%s' lists skill '%s' in 'skills:', but no global or per-agent skill with that name exists.\",\n agentName,\n want,\n );\n }\n }\n }\n\n const byName = new Map<string, SkillDefinition[]>();\n for (const skill of visible) {\n const group = byName.get(skill.name) ?? [];\n group.push(skill);\n byName.set(skill.name, group);\n }\n\n const byAddress = new Map<string, SkillDefinition>();\n const ambiguous = new Map<string, string[]>();\n\n for (const [name, group] of byName) {\n if (group.length === 1) {\n byAddress.set(name, group[0]);\n continue;\n }\n\n const alternatives: string[] = [];\n for (const skill of group) {\n const qualified = `${SCOPE_BY_SOURCE[skill.source]}:${name}`;\n const existing = byAddress.get(qualified);\n if (existing) {\n throw new Error(\n `Agent '${agentName}': two '${skill.source}' skills are both named '${name}' ` +\n `(${existing.dir} and ${skill.dir}). Skill names must be unique within a source.`,\n );\n }\n byAddress.set(qualified, skill);\n alternatives.push(qualified);\n }\n alternatives.sort();\n ambiguous.set(name, alternatives);\n logger.warn(\n \"Agent '%s': skill name '%s' is provided by multiple sources; address it as %s.\",\n agentName,\n name,\n alternatives.join(\" or \"),\n );\n }\n\n const catalog: SkillCatalogEntry[] = [...byAddress.entries()]\n .map(([address, skill]) => ({\n name: address,\n description: skill.description,\n }))\n .sort((a, b) => a.name.localeCompare(b.name));\n\n return { byAddress, ambiguous, catalog };\n}\n\n/**\n * Resolves a requested skill name (bare or qualified) against a catalog.\n * Throws a helpful error on ambiguous or unknown names.\n */\nexport function resolveSkill(\n catalog: ResolvedSkillCatalog,\n requested: string,\n): SkillDefinition {\n const direct = catalog.byAddress.get(requested);\n if (direct) return direct;\n\n const alternatives = catalog.ambiguous.get(requested);\n if (alternatives) {\n throw new Error(\n `Skill '${requested}' is ambiguous; specify one of: ${alternatives.join(\", \")}.`,\n );\n }\n\n const available = [...catalog.byAddress.keys()].sort().join(\", \") || \"<none>\";\n throw new Error(`Unknown skill '${requested}'. Available: ${available}.`);\n}\n"],"mappings":";;;AAQA,MAAM,SAAS,aAAa,gBAAgB;;AAG5C,MAAM,kBAA+C;CACnD,gBAAgB;CAChB,iBAAiB;CACjB,QAAQ;CACT;;;;;;;;AAqBD,SAAgB,oBACd,OACsB;CACtB,MAAM,EACJ,WACA,iBACA,gBACA,cACA,gBACE;CAEJ,MAAM,UAA6B,CAAC,GAAG,eAAe;AACtD,KAAI,YACF,SAAQ,KAAK,GAAG,aAAa;UACpB,mBAAmB,gBAAgB,SAAS,GAAG;EACxD,MAAM,SAAS,IAAI,IAAI,gBAAgB;AACvC,OAAK,MAAM,SAAS,aAClB,KAAI,OAAO,IAAI,MAAM,KAAK,CAAE,SAAQ,KAAK,MAAM;EAEjD,MAAM,aAAa,IAAI,IAAI,eAAe,KAAK,MAAM,EAAE,KAAK,CAAC;EAC7D,MAAM,cAAc,IAAI,IAAI,aAAa,KAAK,MAAM,EAAE,KAAK,CAAC;AAC5D,OAAK,MAAM,QAAQ,gBACjB,KAAI,CAAC,YAAY,IAAI,KAAK,IAAI,CAAC,WAAW,IAAI,KAAK,CACjD,QAAO,KACL,qGACA,WACA,KACD;;CAKP,MAAM,yBAAS,IAAI,KAAgC;AACnD,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,QAAQ,OAAO,IAAI,MAAM,KAAK,IAAI,EAAE;AAC1C,QAAM,KAAK,MAAM;AACjB,SAAO,IAAI,MAAM,MAAM,MAAM;;CAG/B,MAAM,4BAAY,IAAI,KAA8B;CACpD,MAAM,4BAAY,IAAI,KAAuB;AAE7C,MAAK,MAAM,CAAC,MAAM,UAAU,QAAQ;AAClC,MAAI,MAAM,WAAW,GAAG;AACtB,aAAU,IAAI,MAAM,MAAM,GAAG;AAC7B;;EAGF,MAAM,eAAyB,EAAE;AACjC,OAAK,MAAM,SAAS,OAAO;GACzB,MAAM,YAAY,GAAG,gBAAgB,MAAM,QAAQ,GAAG;GACtD,MAAM,WAAW,UAAU,IAAI,UAAU;AACzC,OAAI,SACF,OAAM,IAAI,MACR,UAAU,UAAU,UAAU,MAAM,OAAO,2BAA2B,KAAK,KACrE,SAAS,IAAI,OAAO,MAAM,IAAI,gDACrC;AAEH,aAAU,IAAI,WAAW,MAAM;AAC/B,gBAAa,KAAK,UAAU;;AAE9B,eAAa,MAAM;AACnB,YAAU,IAAI,MAAM,aAAa;AACjC,SAAO,KACL,kFACA,WACA,MACA,aAAa,KAAK,OAAO,CAC1B;;AAUH,QAAO;EAAE;EAAW;EAAW,SAPM,CAAC,GAAG,UAAU,SAAS,CAAC,CAC1D,KAAK,CAAC,SAAS,YAAY;GAC1B,MAAM;GACN,aAAa,MAAM;GACpB,EAAE,CACF,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAAC;EAEP;;;;;;AAO1C,SAAgB,aACd,SACA,WACiB;CACjB,MAAM,SAAS,QAAQ,UAAU,IAAI,UAAU;AAC/C,KAAI,OAAQ,QAAO;CAEnB,MAAM,eAAe,QAAQ,UAAU,IAAI,UAAU;AACrD,KAAI,aACF,OAAM,IAAI,MACR,UAAU,UAAU,kCAAkC,aAAa,KAAK,KAAK,CAAC,GAC/E;CAGH,MAAM,YAAY,CAAC,GAAG,QAAQ,UAAU,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI;AACrE,OAAM,IAAI,MAAM,kBAAkB,UAAU,gBAAgB,UAAU,GAAG"}
@@ -0,0 +1,50 @@
1
+ //#region src/core/agent/skills/types.d.ts
2
+ /** Where a skill was discovered. Drives the qualified name used on collision. */
3
+ type SkillSource = "bundle-agent" | "bundle-global" | "volume";
4
+ /**
5
+ * A single skill: a `SKILL.md` (frontmatter `name`+`description` + Markdown
6
+ * body) plus any bundled resource files in the same directory. The body is
7
+ * loaded into model context on demand (via the `load_skill` tool or a forced
8
+ * `/skill-name` invocation); only `name`+`description` are always-on in the
9
+ * prompt catalog.
10
+ */
11
+ interface SkillDefinition {
12
+ /** Frontmatter `name`. The addressable skill id. */
13
+ name: string;
14
+ /** Frontmatter `description`. Injected into the always-on prompt catalog. */
15
+ description: string;
16
+ /** Markdown body — the instructions loaded on demand. */
17
+ body: string;
18
+ /** Where the skill came from. */
19
+ source: SkillSource;
20
+ /** Absolute directory containing `SKILL.md` and any bundled resources. */
21
+ dir: string;
22
+ /** Relative posix paths of bundled resource files (excludes `SKILL.md`). */
23
+ files: string[];
24
+ /**
25
+ * Optional advisory tool allowlist from frontmatter `allowed-tools`. Surfaced
26
+ * as a hint in v1 — NOT enforced (loading a skill does not restrict the
27
+ * agent's callable tools).
28
+ */
29
+ allowedTools?: string[];
30
+ }
31
+ /** The always-on prompt entry for a skill (what the model sees before loading). */
32
+ interface SkillCatalogEntry {
33
+ /** Addressable name — bare when unique, `<scope>:name` when collided. */
34
+ name: string;
35
+ description: string;
36
+ }
37
+ /**
38
+ * Per-agent resolved skill catalog: visibility + collision rules applied.
39
+ * `byAddress` maps every addressable name (bare or qualified) to its skill;
40
+ * `ambiguous` maps a bare name shadowed by multiple sources to the qualified
41
+ * alternatives; `catalog` is the always-on prompt list (one entry per address).
42
+ */
43
+ interface ResolvedSkillCatalog {
44
+ byAddress: Map<string, SkillDefinition>;
45
+ ambiguous: Map<string, string[]>;
46
+ catalog: SkillCatalogEntry[];
47
+ }
48
+ //#endregion
49
+ export { ResolvedSkillCatalog };
50
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../../src/core/agent/skills/types.ts"],"mappings":";;KACY,WAAA;;;;;AASZ;;;UAAiB,eAAA;EAEf;EAAA,IAAA;EAIA;EAFA,WAAA;EAIQ;EAFR,IAAA;EAMA;EAJA,MAAA,EAAQ,WAAA;EAUI;EARZ,GAAA;EAYe;EAVf,KAAA;;;;AAsBF;;EAhBE,YAAA;AAAA;;UAIe,iBAAA;EAeN;EAbT,IAAA;EACA,WAAA;AAAA;;;;;;;UASe,oBAAA;EACf,SAAA,EAAW,GAAA,SAAY,eAAA;EACvB,SAAA,EAAW,GAAA;EACX,OAAA,EAAS,iBAAA;AAAA"}
@@ -5,6 +5,7 @@ import { HostedSupervisorTool, SupervisorTool } from "../../agents/supervisor-ap
5
5
  import { GenerationParams } from "../../agents/databricks.js";
6
6
  import { McpHostPolicyConfig } from "../../connectors/mcp/host-policy.js";
7
7
  import "../../connectors/mcp/index.js";
8
+ import { ResolvedSkillCatalog } from "./skills/types.js";
8
9
  import { FunctionTool } from "./tools/function-tool.js";
9
10
  import { HostedTool } from "./tools/hosted-tools.js";
10
11
 
@@ -155,6 +156,13 @@ interface AgentDefinition {
155
156
  tools?: AgentTools | AgentToolsFn;
156
157
  /** Sub-agents, exposed as `agent-<key>` tools on this agent. */
157
158
  agents?: Record<string, AgentDefinition>;
159
+ /**
160
+ * Names of global skills (shared `skills/` pool or catalog volume) to make
161
+ * visible to this agent. Per-agent skills under `<id>/skills/` are always
162
+ * visible and need not be listed. Ignored when the plugin's
163
+ * `autoInheritSkills` makes every global skill visible.
164
+ */
165
+ skills?: string[];
158
166
  /** Override the plugin's baseSystemPrompt for this agent only. */
159
167
  baseSystemPrompt?: BaseSystemPromptOption;
160
168
  maxSteps?: number;
@@ -213,6 +221,28 @@ interface AgentsPluginConfig extends BasePluginConfig {
213
221
  tools?: Record<string, AgentTool>;
214
222
  /** Whether to auto-inherit every ToolProvider plugin's toolkit. Accepts a boolean shorthand. */
215
223
  autoInheritTools?: boolean | AutoInheritToolsConfig;
224
+ /**
225
+ * Whether every global skill (shared `skills/` pool or catalog volume) is
226
+ * visible to an agent without listing it in `skills:` frontmatter. Off by
227
+ * default so each agent's always-on skill catalog stays lean; accepts a
228
+ * boolean shorthand or a per-origin `{ file, code }` config, mirroring
229
+ * {@link autoInheritTools}.
230
+ */
231
+ autoInheritSkills?: boolean | AutoInheritToolsConfig;
232
+ /**
233
+ * Unity Catalog Volume path for catalog-sourced skills (e.g.
234
+ * `/Volumes/<catalog>/<schema>/<volume>`). Falls back to the
235
+ * `DATABRICKS_VOLUME_AGENT_SKILLS` env var. Skills at `<volume>/<name>/SKILL.md`
236
+ * are discovered at boot and on `reload()` and read as the service principal.
237
+ */
238
+ skillsVolume?: string;
239
+ /**
240
+ * Identity used to read catalog (volume) skills. v1 supports `"sp"` (default —
241
+ * a shared, service-principal-readable curated pool). `"obo"` is the reserved
242
+ * switch point for per-user skill volumes and is not wired yet (falls back to
243
+ * `"sp"` with a warning).
244
+ */
245
+ skillCredentialMode?: "sp" | "obo";
216
246
  /** Persistent thread store. Default: in-memory. */
217
247
  threadStore?: ThreadStore;
218
248
  /** Customize or disable the AppKit base system prompt. */
@@ -316,6 +346,17 @@ type ResolvedToolEntry = {
316
346
  source: "hosted-supervisor";
317
347
  spec: SupervisorTool;
318
348
  def: AgentToolDefinition;
349
+ } | {
350
+ /**
351
+ * Built-in skill tools (`load_skill`, `read_skill_file`) injected into
352
+ * any agent that has a visible skill catalog. Executed in-process by the
353
+ * agents plugin against the agent's resolved catalog; read-only, so they
354
+ * bypass the approval gate.
355
+ */
356
+ source: "skill";
357
+ builtin: "load_skill" | "read_skill_file";
358
+ catalog: ResolvedSkillCatalog;
359
+ def: AgentToolDefinition;
319
360
  };
320
361
  interface RegisteredAgent {
321
362
  name: string;
@@ -329,6 +370,12 @@ interface RegisteredAgent {
329
370
  generationParams?: GenerationParams;
330
371
  /** Mirrors `AgentDefinition.ephemeral` — skip thread persistence. */
331
372
  ephemeral?: boolean;
373
+ /**
374
+ * Resolved per-agent skill catalog (visibility + collision rules applied).
375
+ * Present when any skill is visible to this agent; drives the always-on
376
+ * prompt catalog and `load_skill` dispatch.
377
+ */
378
+ skills?: ResolvedSkillCatalog;
332
379
  }
333
380
  /**
334
381
  * Type guard for `ToolkitEntry` — used by the agents plugin to differentiate
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/core/agent/types.ts"],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/core/agent/types.ts"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../src/core/agent/types.ts"],"sourcesContent":["import type {\n AgentAdapter,\n AgentToolDefinition,\n BasePluginConfig,\n ThreadStore,\n ToolAnnotations,\n} from \"shared\";\n\nimport type { GenerationParams } from \"../../agents/databricks\";\nimport type { McpHostPolicyConfig } from \"../../connectors/mcp\";\nimport type { FunctionTool } from \"./tools/function-tool\";\nimport type { HostedTool } from \"./tools/hosted-tools\";\n\n/**\n * A tool reference produced by a plugin's `.toolkit()` call. The agents plugin\n * recognizes the `__toolkitRef` brand and dispatches tool invocations through\n * `PluginContext.executeTool(req, pluginName, localName, ...)`, preserving\n * OBO (asUser) and telemetry spans.\n */\nexport interface ToolkitEntry {\n readonly __toolkitRef: true;\n pluginName: string;\n localName: string;\n def: AgentToolDefinition;\n annotations?: ToolAnnotations;\n /**\n * Whether this tool is eligible for `autoInheritTools` spreading. Mirrors\n * {@link ToolEntry.autoInheritable} from the source registry so the agents\n * plugin can filter auto-inherited tools without re-walking the provider's\n * internal registry.\n */\n autoInheritable?: boolean;\n}\n\n/**\n * Any tool an agent can invoke: inline function tools (`tool()`), hosted MCP\n * tools (`mcpServer()` / raw hosted), toolkit references from plugins\n * (`analytics().toolkit()`), or adapter-hosted Supervisor-API tools\n * (`supervisorTools.*`).\n */\nexport type AgentTool =\n | FunctionTool\n | HostedTool\n | ToolkitEntry\n | import(\"../../agents/supervisor-api\").HostedSupervisorTool;\n\nexport interface ToolkitOptions {\n /** Key prefix to prepend to each tool's local name. Defaults to `${pluginName}.`. */\n prefix?: string;\n /** Only include tools whose local name matches one of these. */\n only?: string[];\n /** Exclude tools whose local name matches one of these. */\n except?: string[];\n /** Remap specific local names to different keys (applied after prefix). */\n rename?: Record<string, string>;\n}\n\n/**\n * Minimum shape every entry in the {@link Plugins} map must expose. Core\n * plugins (analytics, files, genie, lakebase) implement this directly via\n * their `.toolkit()` method. The agents plugin and standalone `runAgent`\n * synthesize this shape for any registered plugin that doesn't implement\n * `.toolkit()` directly (falling back to `getAgentTools()` walking).\n */\nexport interface PluginToolkitProvider {\n toolkit(opts?: ToolkitOptions): Record<string, ToolkitEntry>;\n}\n\n/**\n * Plugin map passed to the function form of {@link AgentDefinition.tools}.\n * Each entry exposes a `.toolkit(opts?)` method that returns a record of\n * {@link ToolkitEntry} markers ready to be spread into a tool record.\n *\n * AppKit does not statically know which plugins the surrounding\n * `createApp` will register, so this is a plain string-keyed record.\n * Refer to plugins by the name used in `createApp({ plugins: [...] })`;\n * unknown names resolve to `undefined` at runtime.\n *\n * @example\n * ```ts\n * const support = createAgent({\n * instructions: \"...\",\n * tools(plugins) {\n * return {\n * get_weather: tool({ ... }),\n * ...plugins.analytics.toolkit(),\n * ...plugins.files.toolkit({ only: [\"uploads.read\"] }),\n * };\n * },\n * });\n * ```\n */\nexport type Plugins = Record<string, PluginToolkitProvider>;\n\n/**\n * Context passed to `baseSystemPrompt` callbacks.\n */\nexport interface PromptContext {\n agentName: string;\n pluginNames: string[];\n toolNames: string[];\n}\n\nexport type BaseSystemPromptOption =\n | false\n | string\n | ((ctx: PromptContext) => string);\n\n/**\n * Per-agent tool record. String keys map to inline tools, toolkit entries,\n * hosted tools, etc.\n */\nexport type AgentTools = Record<string, AgentTool>;\n\n/**\n * Function form of `AgentDefinition.tools`. Receives the typed\n * {@link Plugins} map and returns a tool record. Invoked exactly once at\n * setup (or once per `runAgent` call in standalone mode); the result is\n * cached as the agent's resolved tool record.\n *\n * Use the function form when an agent needs tools from registered plugins.\n * The bare object form is fine when an agent only uses inline tools.\n */\nexport type AgentToolsFn = (plugins: Plugins) => AgentTools;\n\nexport interface AgentDefinition {\n /**\n * Stable identifier for the agent. **Optional and informational** —\n * when the definition is registered via `agents: { foo: def }` (code) or\n * lives at `server/agents/<id>/agent.md` (markdown), the **registry key\n * always wins** and `name` is ignored. The agent will be reachable as\n * `foo` (or `<id>`) regardless of what this field contains.\n *\n * Set `name` when:\n * - Running standalone via `runAgent({ agent: def })`, where there is\n * no enclosing key. The runtime uses it for the agent's slot in\n * error messages and OTel spans.\n * - Building a definition that may be passed to either form and you\n * want a consistent fallback label.\n *\n * Setting `name` to a value that differs from the registry key is\n * harmless but confusing — prefer keeping them aligned or omitting `name`\n * entirely.\n */\n name?: string;\n /**\n * Marks this agent as the default one chosen when a client doesn't name an\n * agent. Mirrors markdown frontmatter `default: true`. When several agents\n * set it, a code (discovered) agent wins over a markdown one, then the\n * lowest id; an explicit `agents({ defaultAgent })` always overrides it.\n * Defaults to `false`.\n */\n default?: boolean;\n /** System prompt body. For markdown-loaded agents this is the file body. */\n instructions: string;\n /**\n * Model adapter (or endpoint-name string sugar for\n * `DatabricksAdapter.fromServingEndpoint({ endpointName })`). Optional —\n * falls back to the plugin's `defaultModel`.\n */\n model?: AgentAdapter | Promise<AgentAdapter> | string;\n /**\n * Per-agent tool record. Key is the LLM-visible tool-call name.\n *\n * Accepts either a plain record (for agents that only use inline tools)\n * or a function `(plugins) => Record<string, AgentTool>` that receives\n * the typed {@link Plugins} map and returns a tool record (for agents\n * that pull tools from registered plugins).\n *\n * The function is invoked once at agent setup; the result is cached.\n * Don't put per-request logic in there.\n */\n tools?: AgentTools | AgentToolsFn;\n /** Sub-agents, exposed as `agent-<key>` tools on this agent. */\n agents?: Record<string, AgentDefinition>;\n /** Override the plugin's baseSystemPrompt for this agent only. */\n baseSystemPrompt?: BaseSystemPromptOption;\n maxSteps?: number;\n maxTokens?: number;\n /**\n * Optional generation parameters (`temperature`, `top_p`, `stop`,\n * `frequency_penalty`, `presence_penalty`) forwarded to the OpenAI-compatible\n * serving request body. Only set keys are sent. Applied only when AppKit\n * builds the adapter itself (string or omitted `model`); when you pass a\n * pre-built `AgentAdapter`, configure generation params on it directly.\n */\n generationParams?: GenerationParams;\n /**\n * When true, the thread used for a chat request against this agent is\n * deleted from `ThreadStore` after the stream completes (success or\n * failure). Use for stateless one-shot agents — e.g. autocomplete, where\n * each request is independent and retaining history would both poison\n * future calls and accumulate unbounded state in the default\n * `InMemoryThreadStore`. Defaults to `false`.\n */\n ephemeral?: boolean;\n}\n\n/**\n * Auto-inherit configuration. When enabled for a given agent origin, agents\n * with no explicit `tools:` declaration receive every registered ToolProvider\n * plugin tool whose author marked `autoInheritable: true`. Tools without that\n * flag — destructive, state-mutating, or privilege-sensitive — never spread\n * automatically and must be wired via `tools:` (object or function form in\n * code, `plugin:NAME` entries in markdown frontmatter).\n *\n * Defaults are `false` for both origins (safe-by-default): developers must\n * consciously opt an origin in to any auto-inherit behaviour.\n */\nexport interface AutoInheritToolsConfig {\n /** Default for agents loaded from markdown files. Default: `false`. */\n file?: boolean;\n /** Default for code-defined agents (via `agents: { foo: createAgent(...) }`). Default: `false`. */\n code?: boolean;\n}\n\nexport interface AgentsPluginConfig extends BasePluginConfig {\n /**\n * @deprecated Put each code agent in its own folder under\n * `server/agents/<id>/agent.ts` (`export default createAgent({ ... })`); it is\n * discovered automatically at startup and the call collapses to\n * `agents({ ... })` with no map. Still honored for backward compatibility\n * (emits a one-time deprecation warning) but will be removed in a future\n * minor. If both discovery and this map define the same id, discovery wins\n * and the map entry is ignored.\n */\n agents?: Record<string, AgentDefinition>;\n /** Agent used when clients don't specify one. Precedence: this value, else a code agent with `default: true`, else a markdown agent with `default: true`, else the first-registered agent. */\n defaultAgent?: string;\n /** Default model for agents that don't specify their own (in code or frontmatter). */\n defaultModel?: AgentAdapter | Promise<AgentAdapter> | string;\n /** Ambient tool library. Keys may be referenced by markdown frontmatter via `tools: [key1, key2]`. */\n tools?: Record<string, AgentTool>;\n /** Whether to auto-inherit every ToolProvider plugin's toolkit. Accepts a boolean shorthand. */\n autoInheritTools?: boolean | AutoInheritToolsConfig;\n /** Persistent thread store. Default: in-memory. */\n threadStore?: ThreadStore;\n /** Customize or disable the AppKit base system prompt. */\n baseSystemPrompt?: BaseSystemPromptOption;\n /**\n * MCP server host policy. By default only same-origin Databricks workspace\n * URLs may be used as MCP endpoints; custom hosts must be explicitly\n * allowlisted here. Workspace credentials (SP / OBO) are never forwarded\n * to non-workspace hosts.\n */\n mcp?: McpHostPolicyConfig;\n /**\n * Human-in-the-loop approval gate for mutating tool calls. When enabled\n * (the default), the agents plugin emits an `appkit.approval_pending` SSE\n * event before executing any tool whose annotation flags it as mutating —\n * `effect: \"write\" | \"update\" | \"destructive\"` (preferred) or the legacy\n * `destructive: true` boolean — and waits for a `POST /api/agents/approve`\n * decision from the same user who initiated the stream. A missing decision\n * after `timeoutMs` auto-denies the call.\n */\n approval?: {\n /**\n * Require human approval for tools that mutate state. Triggered by\n * `effect: \"write\" | \"update\" | \"destructive\"` (preferred) or the legacy\n * `destructive: true` boolean. Default: `true`.\n */\n requireForDestructive?: boolean;\n /** Milliseconds to wait before auto-denying. Default: 60_000. */\n timeoutMs?: number;\n };\n /**\n * Runtime resource limits applied during agent execution. Defaults are\n * tuned to protect a single-instance deployment from a misbehaving user or\n * a runaway prompt injection; tighten or relax as appropriate for the\n * deployment's scale and trust model. Request-body caps (chat message\n * size, invocations input size / length) are enforced statically by the\n * Zod schemas and are not configurable here.\n */\n limits?: {\n /**\n * Max concurrent chat streams a single user may have open. Subsequent\n * `POST /chat` requests from that user while at-limit are rejected with\n * HTTP 429. Default: `5`.\n */\n maxConcurrentStreamsPerUser?: number;\n /**\n * Max tool invocations per agent run (across the full tool-call graph,\n * including sub-agent invocations). A run that exceeds the budget is\n * aborted with a terminal error event. Default: `50`.\n */\n maxToolCalls?: number;\n /**\n * Max sub-agent recursion depth. Protects against a prompt-injected\n * agent that delegates to a sub-agent which in turn delegates back to\n * itself (directly or transitively). Default: `3`.\n */\n maxSubAgentDepth?: number;\n /**\n * Per-call timeout for tools dispatched through `PluginContext`\n * (toolkit-routed tools — analytics SQL warehouse queries, Genie\n * messages, Lakebase queries). Independent of `maxToolCalls`: the\n * budget caps how many tools fire per run, this caps how long any\n * single tool call may run. The signal handed to plugin tool\n * implementations combines this timeout with the parent stream's\n * abort signal via `AbortSignal.any`. Function and MCP tools have\n * their own timeouts in their respective adapters and ignore this\n * setting. Default: `300_000` (5 minutes) — generous enough for cold\n * SQL Warehouse round-trips and long Genie conversations.\n */\n toolCallTimeoutMs?: number;\n };\n}\n\n/** Internal tool-index entry after a tool record has been resolved to a dispatchable form. */\nexport type ResolvedToolEntry =\n | {\n source: \"toolkit\";\n pluginName: string;\n localName: string;\n def: AgentToolDefinition;\n }\n | {\n source: \"function\";\n functionTool: FunctionTool;\n def: AgentToolDefinition;\n }\n | {\n source: \"mcp\";\n mcpToolName: string;\n def: AgentToolDefinition;\n }\n | {\n source: \"subagent\";\n agentName: string;\n def: AgentToolDefinition;\n }\n | {\n /**\n * Adapter-side hosted tool (executed by the model-host, not by the\n * Node process). Today: Supervisor API hosted tools (Genie spaces,\n * UC functions, etc.). The `spec` is opaque to the agents plugin —\n * it routes the entry into `AgentInput.extensions` for the adapter\n * that declared the matching `acceptsExtensions` key. `def` is a\n * synthetic placeholder kept so the index has a uniform shape; it\n * is intentionally NOT included in the `tools` array passed to\n * `adapter.run()` (those entries are not callable functions).\n */\n source: \"hosted-supervisor\";\n spec: import(\"../../agents/supervisor-api\").SupervisorTool;\n def: AgentToolDefinition;\n };\n\nexport interface RegisteredAgent {\n name: string;\n instructions: string;\n adapter: AgentAdapter;\n toolIndex: Map<string, ResolvedToolEntry>;\n baseSystemPrompt?: BaseSystemPromptOption;\n maxSteps?: number;\n maxTokens?: number;\n /** Mirrors `AgentDefinition.generationParams`. */\n generationParams?: GenerationParams;\n /** Mirrors `AgentDefinition.ephemeral` — skip thread persistence. */\n ephemeral?: boolean;\n}\n\n/**\n * Type guard for `ToolkitEntry` — used by the agents plugin to differentiate\n * toolkit references from inline tools in a mixed `tools` record.\n */\nexport function isToolkitEntry(value: unknown): value is ToolkitEntry {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as { __toolkitRef?: unknown }).__toolkitRef === true\n );\n}\n"],"mappings":";;;;;AA6WA,SAAgB,eAAe,OAAuC;AACpE,QACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../src/core/agent/types.ts"],"sourcesContent":["import type {\n AgentAdapter,\n AgentToolDefinition,\n BasePluginConfig,\n ThreadStore,\n ToolAnnotations,\n} from \"shared\";\n\nimport type { GenerationParams } from \"../../agents/databricks\";\nimport type { McpHostPolicyConfig } from \"../../connectors/mcp\";\nimport type { ResolvedSkillCatalog } from \"./skills/types\";\nimport type { FunctionTool } from \"./tools/function-tool\";\nimport type { HostedTool } from \"./tools/hosted-tools\";\n\n/**\n * A tool reference produced by a plugin's `.toolkit()` call. The agents plugin\n * recognizes the `__toolkitRef` brand and dispatches tool invocations through\n * `PluginContext.executeTool(req, pluginName, localName, ...)`, preserving\n * OBO (asUser) and telemetry spans.\n */\nexport interface ToolkitEntry {\n readonly __toolkitRef: true;\n pluginName: string;\n localName: string;\n def: AgentToolDefinition;\n annotations?: ToolAnnotations;\n /**\n * Whether this tool is eligible for `autoInheritTools` spreading. Mirrors\n * {@link ToolEntry.autoInheritable} from the source registry so the agents\n * plugin can filter auto-inherited tools without re-walking the provider's\n * internal registry.\n */\n autoInheritable?: boolean;\n}\n\n/**\n * Any tool an agent can invoke: inline function tools (`tool()`), hosted MCP\n * tools (`mcpServer()` / raw hosted), toolkit references from plugins\n * (`analytics().toolkit()`), or adapter-hosted Supervisor-API tools\n * (`supervisorTools.*`).\n */\nexport type AgentTool =\n | FunctionTool\n | HostedTool\n | ToolkitEntry\n | import(\"../../agents/supervisor-api\").HostedSupervisorTool;\n\nexport interface ToolkitOptions {\n /** Key prefix to prepend to each tool's local name. Defaults to `${pluginName}.`. */\n prefix?: string;\n /** Only include tools whose local name matches one of these. */\n only?: string[];\n /** Exclude tools whose local name matches one of these. */\n except?: string[];\n /** Remap specific local names to different keys (applied after prefix). */\n rename?: Record<string, string>;\n}\n\n/**\n * Minimum shape every entry in the {@link Plugins} map must expose. Core\n * plugins (analytics, files, genie, lakebase) implement this directly via\n * their `.toolkit()` method. The agents plugin and standalone `runAgent`\n * synthesize this shape for any registered plugin that doesn't implement\n * `.toolkit()` directly (falling back to `getAgentTools()` walking).\n */\nexport interface PluginToolkitProvider {\n toolkit(opts?: ToolkitOptions): Record<string, ToolkitEntry>;\n}\n\n/**\n * Plugin map passed to the function form of {@link AgentDefinition.tools}.\n * Each entry exposes a `.toolkit(opts?)` method that returns a record of\n * {@link ToolkitEntry} markers ready to be spread into a tool record.\n *\n * AppKit does not statically know which plugins the surrounding\n * `createApp` will register, so this is a plain string-keyed record.\n * Refer to plugins by the name used in `createApp({ plugins: [...] })`;\n * unknown names resolve to `undefined` at runtime.\n *\n * @example\n * ```ts\n * const support = createAgent({\n * instructions: \"...\",\n * tools(plugins) {\n * return {\n * get_weather: tool({ ... }),\n * ...plugins.analytics.toolkit(),\n * ...plugins.files.toolkit({ only: [\"uploads.read\"] }),\n * };\n * },\n * });\n * ```\n */\nexport type Plugins = Record<string, PluginToolkitProvider>;\n\n/**\n * Context passed to `baseSystemPrompt` callbacks.\n */\nexport interface PromptContext {\n agentName: string;\n pluginNames: string[];\n toolNames: string[];\n}\n\nexport type BaseSystemPromptOption =\n | false\n | string\n | ((ctx: PromptContext) => string);\n\n/**\n * Per-agent tool record. String keys map to inline tools, toolkit entries,\n * hosted tools, etc.\n */\nexport type AgentTools = Record<string, AgentTool>;\n\n/**\n * Function form of `AgentDefinition.tools`. Receives the typed\n * {@link Plugins} map and returns a tool record. Invoked exactly once at\n * setup (or once per `runAgent` call in standalone mode); the result is\n * cached as the agent's resolved tool record.\n *\n * Use the function form when an agent needs tools from registered plugins.\n * The bare object form is fine when an agent only uses inline tools.\n */\nexport type AgentToolsFn = (plugins: Plugins) => AgentTools;\n\nexport interface AgentDefinition {\n /**\n * Stable identifier for the agent. **Optional and informational** —\n * when the definition is registered via `agents: { foo: def }` (code) or\n * lives at `server/agents/<id>/agent.md` (markdown), the **registry key\n * always wins** and `name` is ignored. The agent will be reachable as\n * `foo` (or `<id>`) regardless of what this field contains.\n *\n * Set `name` when:\n * - Running standalone via `runAgent({ agent: def })`, where there is\n * no enclosing key. The runtime uses it for the agent's slot in\n * error messages and OTel spans.\n * - Building a definition that may be passed to either form and you\n * want a consistent fallback label.\n *\n * Setting `name` to a value that differs from the registry key is\n * harmless but confusing — prefer keeping them aligned or omitting `name`\n * entirely.\n */\n name?: string;\n /**\n * Marks this agent as the default one chosen when a client doesn't name an\n * agent. Mirrors markdown frontmatter `default: true`. When several agents\n * set it, a code (discovered) agent wins over a markdown one, then the\n * lowest id; an explicit `agents({ defaultAgent })` always overrides it.\n * Defaults to `false`.\n */\n default?: boolean;\n /** System prompt body. For markdown-loaded agents this is the file body. */\n instructions: string;\n /**\n * Model adapter (or endpoint-name string sugar for\n * `DatabricksAdapter.fromServingEndpoint({ endpointName })`). Optional —\n * falls back to the plugin's `defaultModel`.\n */\n model?: AgentAdapter | Promise<AgentAdapter> | string;\n /**\n * Per-agent tool record. Key is the LLM-visible tool-call name.\n *\n * Accepts either a plain record (for agents that only use inline tools)\n * or a function `(plugins) => Record<string, AgentTool>` that receives\n * the typed {@link Plugins} map and returns a tool record (for agents\n * that pull tools from registered plugins).\n *\n * The function is invoked once at agent setup; the result is cached.\n * Don't put per-request logic in there.\n */\n tools?: AgentTools | AgentToolsFn;\n /** Sub-agents, exposed as `agent-<key>` tools on this agent. */\n agents?: Record<string, AgentDefinition>;\n /**\n * Names of global skills (shared `skills/` pool or catalog volume) to make\n * visible to this agent. Per-agent skills under `<id>/skills/` are always\n * visible and need not be listed. Ignored when the plugin's\n * `autoInheritSkills` makes every global skill visible.\n */\n skills?: string[];\n /** Override the plugin's baseSystemPrompt for this agent only. */\n baseSystemPrompt?: BaseSystemPromptOption;\n maxSteps?: number;\n maxTokens?: number;\n /**\n * Optional generation parameters (`temperature`, `top_p`, `stop`,\n * `frequency_penalty`, `presence_penalty`) forwarded to the OpenAI-compatible\n * serving request body. Only set keys are sent. Applied only when AppKit\n * builds the adapter itself (string or omitted `model`); when you pass a\n * pre-built `AgentAdapter`, configure generation params on it directly.\n */\n generationParams?: GenerationParams;\n /**\n * When true, the thread used for a chat request against this agent is\n * deleted from `ThreadStore` after the stream completes (success or\n * failure). Use for stateless one-shot agents — e.g. autocomplete, where\n * each request is independent and retaining history would both poison\n * future calls and accumulate unbounded state in the default\n * `InMemoryThreadStore`. Defaults to `false`.\n */\n ephemeral?: boolean;\n}\n\n/**\n * Auto-inherit configuration. When enabled for a given agent origin, agents\n * with no explicit `tools:` declaration receive every registered ToolProvider\n * plugin tool whose author marked `autoInheritable: true`. Tools without that\n * flag — destructive, state-mutating, or privilege-sensitive — never spread\n * automatically and must be wired via `tools:` (object or function form in\n * code, `plugin:NAME` entries in markdown frontmatter).\n *\n * Defaults are `false` for both origins (safe-by-default): developers must\n * consciously opt an origin in to any auto-inherit behaviour.\n */\nexport interface AutoInheritToolsConfig {\n /** Default for agents loaded from markdown files. Default: `false`. */\n file?: boolean;\n /** Default for code-defined agents (via `agents: { foo: createAgent(...) }`). Default: `false`. */\n code?: boolean;\n}\n\nexport interface AgentsPluginConfig extends BasePluginConfig {\n /**\n * @deprecated Put each code agent in its own folder under\n * `server/agents/<id>/agent.ts` (`export default createAgent({ ... })`); it is\n * discovered automatically at startup and the call collapses to\n * `agents({ ... })` with no map. Still honored for backward compatibility\n * (emits a one-time deprecation warning) but will be removed in a future\n * minor. If both discovery and this map define the same id, discovery wins\n * and the map entry is ignored.\n */\n agents?: Record<string, AgentDefinition>;\n /** Agent used when clients don't specify one. Precedence: this value, else a code agent with `default: true`, else a markdown agent with `default: true`, else the first-registered agent. */\n defaultAgent?: string;\n /** Default model for agents that don't specify their own (in code or frontmatter). */\n defaultModel?: AgentAdapter | Promise<AgentAdapter> | string;\n /** Ambient tool library. Keys may be referenced by markdown frontmatter via `tools: [key1, key2]`. */\n tools?: Record<string, AgentTool>;\n /** Whether to auto-inherit every ToolProvider plugin's toolkit. Accepts a boolean shorthand. */\n autoInheritTools?: boolean | AutoInheritToolsConfig;\n /**\n * Whether every global skill (shared `skills/` pool or catalog volume) is\n * visible to an agent without listing it in `skills:` frontmatter. Off by\n * default so each agent's always-on skill catalog stays lean; accepts a\n * boolean shorthand or a per-origin `{ file, code }` config, mirroring\n * {@link autoInheritTools}.\n */\n autoInheritSkills?: boolean | AutoInheritToolsConfig;\n /**\n * Unity Catalog Volume path for catalog-sourced skills (e.g.\n * `/Volumes/<catalog>/<schema>/<volume>`). Falls back to the\n * `DATABRICKS_VOLUME_AGENT_SKILLS` env var. Skills at `<volume>/<name>/SKILL.md`\n * are discovered at boot and on `reload()` and read as the service principal.\n */\n skillsVolume?: string;\n /**\n * Identity used to read catalog (volume) skills. v1 supports `\"sp\"` (default —\n * a shared, service-principal-readable curated pool). `\"obo\"` is the reserved\n * switch point for per-user skill volumes and is not wired yet (falls back to\n * `\"sp\"` with a warning).\n */\n skillCredentialMode?: \"sp\" | \"obo\";\n /** Persistent thread store. Default: in-memory. */\n threadStore?: ThreadStore;\n /** Customize or disable the AppKit base system prompt. */\n baseSystemPrompt?: BaseSystemPromptOption;\n /**\n * MCP server host policy. By default only same-origin Databricks workspace\n * URLs may be used as MCP endpoints; custom hosts must be explicitly\n * allowlisted here. Workspace credentials (SP / OBO) are never forwarded\n * to non-workspace hosts.\n */\n mcp?: McpHostPolicyConfig;\n /**\n * Human-in-the-loop approval gate for mutating tool calls. When enabled\n * (the default), the agents plugin emits an `appkit.approval_pending` SSE\n * event before executing any tool whose annotation flags it as mutating —\n * `effect: \"write\" | \"update\" | \"destructive\"` (preferred) or the legacy\n * `destructive: true` boolean — and waits for a `POST /api/agents/approve`\n * decision from the same user who initiated the stream. A missing decision\n * after `timeoutMs` auto-denies the call.\n */\n approval?: {\n /**\n * Require human approval for tools that mutate state. Triggered by\n * `effect: \"write\" | \"update\" | \"destructive\"` (preferred) or the legacy\n * `destructive: true` boolean. Default: `true`.\n */\n requireForDestructive?: boolean;\n /** Milliseconds to wait before auto-denying. Default: 60_000. */\n timeoutMs?: number;\n };\n /**\n * Runtime resource limits applied during agent execution. Defaults are\n * tuned to protect a single-instance deployment from a misbehaving user or\n * a runaway prompt injection; tighten or relax as appropriate for the\n * deployment's scale and trust model. Request-body caps (chat message\n * size, invocations input size / length) are enforced statically by the\n * Zod schemas and are not configurable here.\n */\n limits?: {\n /**\n * Max concurrent chat streams a single user may have open. Subsequent\n * `POST /chat` requests from that user while at-limit are rejected with\n * HTTP 429. Default: `5`.\n */\n maxConcurrentStreamsPerUser?: number;\n /**\n * Max tool invocations per agent run (across the full tool-call graph,\n * including sub-agent invocations). A run that exceeds the budget is\n * aborted with a terminal error event. Default: `50`.\n */\n maxToolCalls?: number;\n /**\n * Max sub-agent recursion depth. Protects against a prompt-injected\n * agent that delegates to a sub-agent which in turn delegates back to\n * itself (directly or transitively). Default: `3`.\n */\n maxSubAgentDepth?: number;\n /**\n * Per-call timeout for tools dispatched through `PluginContext`\n * (toolkit-routed tools — analytics SQL warehouse queries, Genie\n * messages, Lakebase queries). Independent of `maxToolCalls`: the\n * budget caps how many tools fire per run, this caps how long any\n * single tool call may run. The signal handed to plugin tool\n * implementations combines this timeout with the parent stream's\n * abort signal via `AbortSignal.any`. Function and MCP tools have\n * their own timeouts in their respective adapters and ignore this\n * setting. Default: `300_000` (5 minutes) — generous enough for cold\n * SQL Warehouse round-trips and long Genie conversations.\n */\n toolCallTimeoutMs?: number;\n };\n}\n\n/** Internal tool-index entry after a tool record has been resolved to a dispatchable form. */\nexport type ResolvedToolEntry =\n | {\n source: \"toolkit\";\n pluginName: string;\n localName: string;\n def: AgentToolDefinition;\n }\n | {\n source: \"function\";\n functionTool: FunctionTool;\n def: AgentToolDefinition;\n }\n | {\n source: \"mcp\";\n mcpToolName: string;\n def: AgentToolDefinition;\n }\n | {\n source: \"subagent\";\n agentName: string;\n def: AgentToolDefinition;\n }\n | {\n /**\n * Adapter-side hosted tool (executed by the model-host, not by the\n * Node process). Today: Supervisor API hosted tools (Genie spaces,\n * UC functions, etc.). The `spec` is opaque to the agents plugin —\n * it routes the entry into `AgentInput.extensions` for the adapter\n * that declared the matching `acceptsExtensions` key. `def` is a\n * synthetic placeholder kept so the index has a uniform shape; it\n * is intentionally NOT included in the `tools` array passed to\n * `adapter.run()` (those entries are not callable functions).\n */\n source: \"hosted-supervisor\";\n spec: import(\"../../agents/supervisor-api\").SupervisorTool;\n def: AgentToolDefinition;\n }\n | {\n /**\n * Built-in skill tools (`load_skill`, `read_skill_file`) injected into\n * any agent that has a visible skill catalog. Executed in-process by the\n * agents plugin against the agent's resolved catalog; read-only, so they\n * bypass the approval gate.\n */\n source: \"skill\";\n builtin: \"load_skill\" | \"read_skill_file\";\n catalog: ResolvedSkillCatalog;\n def: AgentToolDefinition;\n };\n\nexport interface RegisteredAgent {\n name: string;\n instructions: string;\n adapter: AgentAdapter;\n toolIndex: Map<string, ResolvedToolEntry>;\n baseSystemPrompt?: BaseSystemPromptOption;\n maxSteps?: number;\n maxTokens?: number;\n /** Mirrors `AgentDefinition.generationParams`. */\n generationParams?: GenerationParams;\n /** Mirrors `AgentDefinition.ephemeral` — skip thread persistence. */\n ephemeral?: boolean;\n /**\n * Resolved per-agent skill catalog (visibility + collision rules applied).\n * Present when any skill is visible to this agent; drives the always-on\n * prompt catalog and `load_skill` dispatch.\n */\n skills?: ResolvedSkillCatalog;\n}\n\n/**\n * Type guard for `ToolkitEntry` — used by the agents plugin to differentiate\n * toolkit references from inline tools in a mixed `tools` record.\n */\nexport function isToolkitEntry(value: unknown): value is ToolkitEntry {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as { __toolkitRef?: unknown }).__toolkitRef === true\n );\n}\n"],"mappings":";;;;;AA6ZA,SAAgB,eAAe,OAAuC;AACpE,QACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB"}
@@ -1,3 +1,3 @@
1
- import { DEFAULT_LIMIT, FILTER_OPERATORS, IN_CAP, MAX_INCLUDES, MAX_LIMIT, MAX_OFFSET, MAX_WHERE_CONDITIONS, MAX_WHERE_DEPTH, MAX_WHERE_GROUP_ITEMS, isFilterOperator } from "./wire.js";
1
+ import { DEFAULT_LIMIT, FILTER_OPERATORS, IN_CAP, MAX_INCLUDES, MAX_INCLUDE_DEPTH, MAX_INCLUDE_NODES, MAX_LIMIT, MAX_OFFSET, MAX_WHERE_CONDITIONS, MAX_WHERE_DEPTH, MAX_WHERE_GROUP_ITEMS, isFilterOperator } from "./wire.js";
2
2
 
3
3
  export { };
@@ -1 +1 @@
1
- {"version":3,"file":"wire.d.ts","names":[],"sources":["../../../src/database/contract/wire.ts"],"mappings":";;KAkBY,OAAA;;KAEA,cAAA"}
1
+ {"version":3,"file":"wire.d.ts","names":[],"sources":["../../../src/database/contract/wire.ts"],"mappings":";;KAsBY,OAAA;;KAEA,cAAA"}
@@ -15,6 +15,10 @@ const MAX_WHERE_DEPTH = 5;
15
15
  const MAX_WHERE_GROUP_ITEMS = 20;
16
16
  /** Max column conditions accepted across one runtime predicate tree. */
17
17
  const MAX_WHERE_CONDITIONS = 50;
18
+ /** Max number of relation edges one include path may traverse. */
19
+ const MAX_INCLUDE_DEPTH = 2;
20
+ /** Max number of relation nodes across a complete include tree. */
21
+ const MAX_INCLUDE_NODES = 25;
18
22
  /** Filter operators usable in the runtime WHERE translator and the `where` spec type. */
19
23
  const FILTER_OPERATORS = Object.freeze([
20
24
  "eq",
@@ -33,5 +37,5 @@ function isFilterOperator(token) {
33
37
  }
34
38
 
35
39
  //#endregion
36
- export { DEFAULT_LIMIT, FILTER_OPERATORS, IN_CAP, MAX_INCLUDES, MAX_LIMIT, MAX_OFFSET, MAX_WHERE_CONDITIONS, MAX_WHERE_DEPTH, MAX_WHERE_GROUP_ITEMS, isFilterOperator };
40
+ export { DEFAULT_LIMIT, FILTER_OPERATORS, IN_CAP, MAX_INCLUDES, MAX_INCLUDE_DEPTH, MAX_INCLUDE_NODES, MAX_LIMIT, MAX_OFFSET, MAX_WHERE_CONDITIONS, MAX_WHERE_DEPTH, MAX_WHERE_GROUP_ITEMS, isFilterOperator };
37
41
  //# sourceMappingURL=wire.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"wire.js","names":[],"sources":["../../../src/database/contract/wire.ts"],"sourcesContent":["/** Max number of values allowed in an `in.(…)` list. */\nexport const IN_CAP = 100;\n/** Hard ceiling for a runtime query limit. */\nexport const MAX_LIMIT = 500;\n/** Hard ceiling for a runtime query offset. OFFSET scans are unbounded in cost. */\nexport const MAX_OFFSET = 10_000;\n/** Default page size when no `.limit()` is supplied. */\nexport const DEFAULT_LIMIT = 50;\n/** Max number of relations resolvable in a single `.include()`. */\nexport const MAX_INCLUDES = 10;\n/** Max nesting depth of `and`/`or` groups in one runtime predicate. */\nexport const MAX_WHERE_DEPTH = 5;\n/** Max members accepted by one runtime `and`/`or` group. */\nexport const MAX_WHERE_GROUP_ITEMS = 20;\n/** Max column conditions accepted across one runtime predicate tree. */\nexport const MAX_WHERE_CONDITIONS = 50;\n\n/** Scalar values accepted by primary-key operations. */\nexport type IdValue = string | number | bigint;\n/** Ordering accepted by typed clients and the runtime adapter. */\nexport type OrderDirection = \"asc\" | \"desc\";\n/** Filter operators usable in the runtime WHERE translator and the `where` spec type. */\nexport const FILTER_OPERATORS = Object.freeze([\n \"eq\",\n \"neq\",\n \"gt\",\n \"gte\",\n \"lt\",\n \"lte\",\n \"like\",\n \"ilike\",\n \"in\",\n \"is\",\n] as const);\n\nexport type FilterOperator = (typeof FILTER_OPERATORS)[number];\n\nexport function isFilterOperator(token: string): token is FilterOperator {\n return (FILTER_OPERATORS as readonly string[]).includes(token);\n}\n"],"mappings":";;AACA,MAAa,SAAS;;AAEtB,MAAa,YAAY;;AAEzB,MAAa,aAAa;;AAE1B,MAAa,gBAAgB;;AAE7B,MAAa,eAAe;;AAE5B,MAAa,kBAAkB;;AAE/B,MAAa,wBAAwB;;AAErC,MAAa,uBAAuB;;AAOpC,MAAa,mBAAmB,OAAO,OAAO;CAC5C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAU;AAIX,SAAgB,iBAAiB,OAAwC;AACvE,QAAQ,iBAAuC,SAAS,MAAM"}
1
+ {"version":3,"file":"wire.js","names":[],"sources":["../../../src/database/contract/wire.ts"],"sourcesContent":["/** Max number of values allowed in an `in.(…)` list. */\nexport const IN_CAP = 100;\n/** Hard ceiling for a runtime query limit. */\nexport const MAX_LIMIT = 500;\n/** Hard ceiling for a runtime query offset. OFFSET scans are unbounded in cost. */\nexport const MAX_OFFSET = 10_000;\n/** Default page size when no `.limit()` is supplied. */\nexport const DEFAULT_LIMIT = 50;\n/** Max number of relations resolvable in a single `.include()`. */\nexport const MAX_INCLUDES = 10;\n/** Max nesting depth of `and`/`or` groups in one runtime predicate. */\nexport const MAX_WHERE_DEPTH = 5;\n/** Max members accepted by one runtime `and`/`or` group. */\nexport const MAX_WHERE_GROUP_ITEMS = 20;\n/** Max column conditions accepted across one runtime predicate tree. */\nexport const MAX_WHERE_CONDITIONS = 50;\n/** Max number of relation edges one include path may traverse. */\nexport const MAX_INCLUDE_DEPTH = 2;\n/** Max number of relation nodes across a complete include tree. */\nexport const MAX_INCLUDE_NODES = 25;\n\n/** Scalar values accepted by primary-key operations. */\nexport type IdValue = string | number | bigint;\n/** Ordering accepted by typed clients and the runtime adapter. */\nexport type OrderDirection = \"asc\" | \"desc\";\n/** Filter operators usable in the runtime WHERE translator and the `where` spec type. */\nexport const FILTER_OPERATORS = Object.freeze([\n \"eq\",\n \"neq\",\n \"gt\",\n \"gte\",\n \"lt\",\n \"lte\",\n \"like\",\n \"ilike\",\n \"in\",\n \"is\",\n] as const);\n\nexport type FilterOperator = (typeof FILTER_OPERATORS)[number];\n\nexport function isFilterOperator(token: string): token is FilterOperator {\n return (FILTER_OPERATORS as readonly string[]).includes(token);\n}\n"],"mappings":";;AACA,MAAa,SAAS;;AAEtB,MAAa,YAAY;;AAEzB,MAAa,aAAa;;AAE1B,MAAa,gBAAgB;;AAE7B,MAAa,eAAe;;AAE5B,MAAa,kBAAkB;;AAE/B,MAAa,wBAAwB;;AAErC,MAAa,uBAAuB;;AAEpC,MAAa,oBAAoB;;AAEjC,MAAa,oBAAoB;;AAOjC,MAAa,mBAAmB,OAAO,OAAO;CAC5C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAU;AAIX,SAAgB,iBAAiB,OAAwC;AACvE,QAAQ,iBAAuC,SAAS,MAAM"}
@@ -9,6 +9,10 @@ const definitions = {
9
9
  message: "Invalid database request",
10
10
  statusCode: 400
11
11
  },
12
+ NOT_FOUND: {
13
+ message: "Database record not found",
14
+ statusCode: 404
15
+ },
12
16
  CONFLICT: {
13
17
  message: "Database conflict",
14
18
  statusCode: 409
@@ -21,6 +25,10 @@ const definitions = {
21
25
  message: "Database operation temporarily unavailable",
22
26
  statusCode: 503
23
27
  },
28
+ PAYLOAD_TOO_LARGE: {
29
+ message: "Database response is too large",
30
+ statusCode: 413
31
+ },
24
32
  INTERNAL: {
25
33
  message: "Database operation failed",
26
34
  statusCode: 500
@@ -33,7 +41,9 @@ const definitions = {
33
41
  const categoryByStatus = {
34
42
  400: "INVALID_REQUEST",
35
43
  403: "FORBIDDEN",
44
+ 404: "NOT_FOUND",
36
45
  409: "CONFLICT",
46
+ 413: "PAYLOAD_TOO_LARGE",
37
47
  503: "TRANSIENT"
38
48
  };
39
49
  /** AppKit-facing database failure with stable metadata and no driver details. */
@@ -41,11 +51,12 @@ var DatabasePluginError = class extends AppKitError {
41
51
  code = "DATABASE_PLUGIN_ERROR";
42
52
  isRetryable;
43
53
  statusCode;
44
- constructor(category, phase, runtimeMessage) {
54
+ constructor(category, phase, runtimeMessage, details) {
45
55
  const definition = definitions[category];
46
56
  super(phase === "runtime" && runtimeMessage ? runtimeMessage : definition.message, { clientMessage: definition.message });
47
57
  this.category = category;
48
58
  this.phase = phase;
59
+ this.details = details;
49
60
  this.statusCode = definition.statusCode;
50
61
  this.isRetryable = category === "TRANSIENT";
51
62
  this.name = "DatabasePluginError";
@@ -55,10 +66,35 @@ var DatabasePluginError = class extends AppKitError {
55
66
  function invalidDatabaseRequest(runtimeMessage) {
56
67
  return new DatabasePluginError("INVALID_REQUEST", "runtime", runtimeMessage);
57
68
  }
69
+ /** Refuse to publish a plugin whose configuration cannot be honored. */
70
+ function databaseSetupFailed() {
71
+ return new DatabasePluginError("SETUP_FAILED", "setup");
72
+ }
73
+ /** Reject untrusted request input, naming the field but never its value. */
74
+ function invalidDatabaseInput(path, message) {
75
+ return new DatabasePluginError("INVALID_REQUEST", "read", void 0, [{
76
+ path,
77
+ message
78
+ }]);
79
+ }
80
+ /**
81
+ * Name an unknown failure without its payload. A driver error carries the SQL
82
+ * text and its bound row values (for example a `DrizzleQueryError` retains
83
+ * `query` and `params`), so only constructor names walk into the log.
84
+ */
85
+ function describeUnclassifiedError(error) {
86
+ const names = [];
87
+ let current = error;
88
+ for (let depth = 0; depth < 5 && current instanceof Error; depth++) {
89
+ names.push(current.name);
90
+ current = current.cause;
91
+ }
92
+ return names.length > 0 ? names.join(" <- ") : typeof error;
93
+ }
58
94
  /** Add operation context without retaining an unknown error's details. */
59
95
  function classifyDatabaseError(error, phase) {
60
96
  if (error instanceof DatabasePluginError) return error.phase === phase ? error : new DatabasePluginError(error.category, phase);
61
- logger.error("Unclassified database error during %s: %O", phase, error);
97
+ logger.error("Unclassified database error during %s (%s)", phase, describeUnclassifiedError(error));
62
98
  return new DatabasePluginError("INTERNAL", phase);
63
99
  }
64
100
  /** Restore the safe database category carried through `Plugin.execute()`. */
@@ -67,5 +103,5 @@ function databaseErrorFromStatus(status, phase) {
67
103
  }
68
104
 
69
105
  //#endregion
70
- export { DatabasePluginError, classifyDatabaseError, databaseErrorFromStatus, invalidDatabaseRequest };
106
+ export { DatabasePluginError, classifyDatabaseError, databaseErrorFromStatus, databaseSetupFailed, invalidDatabaseInput, invalidDatabaseRequest };
71
107
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../../src/database/errors.ts"],"sourcesContent":["import { AppKitError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\n\nconst logger = createLogger(\"database\");\n\nexport type DatabaseErrorCategory =\n | \"INVALID_REQUEST\"\n | \"CONFLICT\"\n | \"FORBIDDEN\"\n | \"TRANSIENT\"\n | \"INTERNAL\"\n | \"SETUP_FAILED\";\n\ntype DatabaseErrorPhase =\n | \"setup\"\n | \"shutdown\"\n | \"read\"\n | \"write\"\n | \"transaction\"\n | \"runtime\";\n\nconst definitions: Record<\n DatabaseErrorCategory,\n { readonly message: string; readonly statusCode: number }\n> = {\n INVALID_REQUEST: { message: \"Invalid database request\", statusCode: 400 },\n CONFLICT: { message: \"Database conflict\", statusCode: 409 },\n FORBIDDEN: { message: \"Database operation forbidden\", statusCode: 403 },\n TRANSIENT: {\n message: \"Database operation temporarily unavailable\",\n statusCode: 503,\n },\n INTERNAL: { message: \"Database operation failed\", statusCode: 500 },\n SETUP_FAILED: { message: \"Database setup failed\", statusCode: 500 },\n};\n\nconst categoryByStatus: Readonly<Record<number, DatabaseErrorCategory>> = {\n 400: \"INVALID_REQUEST\",\n 403: \"FORBIDDEN\",\n 409: \"CONFLICT\",\n 503: \"TRANSIENT\",\n};\n\n/** AppKit-facing database failure with stable metadata and no driver details. */\nexport class DatabasePluginError extends AppKitError {\n readonly code = \"DATABASE_PLUGIN_ERROR\";\n readonly isRetryable: boolean;\n readonly statusCode: number;\n\n constructor(\n readonly category: DatabaseErrorCategory,\n readonly phase: DatabaseErrorPhase,\n runtimeMessage?: string,\n ) {\n const definition = definitions[category];\n // Plugin boundaries replace runtime diagnostics with the stable message.\n super(\n phase === \"runtime\" && runtimeMessage\n ? runtimeMessage\n : definition.message,\n {\n clientMessage: definition.message,\n },\n );\n this.statusCode = definition.statusCode;\n this.isRetryable = category === \"TRANSIENT\";\n this.name = \"DatabasePluginError\";\n }\n}\n\n/** Keep runtime diagnostics internal until a plugin boundary classifies them. */\nexport function invalidDatabaseRequest(\n runtimeMessage?: string,\n): DatabasePluginError {\n return new DatabasePluginError(\"INVALID_REQUEST\", \"runtime\", runtimeMessage);\n}\n\n/** Add operation context without retaining an unknown error's details. */\nexport function classifyDatabaseError(\n error: unknown,\n phase: DatabaseErrorPhase,\n): DatabasePluginError {\n if (error instanceof DatabasePluginError) {\n return error.phase === phase\n ? error\n : new DatabasePluginError(error.category, phase);\n }\n logger.error(\"Unclassified database error during %s: %O\", phase, error);\n return new DatabasePluginError(\"INTERNAL\", phase);\n}\n\n/** Restore the safe database category carried through `Plugin.execute()`. */\nexport function databaseErrorFromStatus(\n status: number,\n phase: DatabaseErrorPhase,\n): DatabasePluginError {\n const category = categoryByStatus[status] ?? \"INTERNAL\";\n return new DatabasePluginError(category, phase);\n}\n"],"mappings":";;;;;AAGA,MAAM,SAAS,aAAa,WAAW;AAkBvC,MAAM,cAGF;CACF,iBAAiB;EAAE,SAAS;EAA4B,YAAY;EAAK;CACzE,UAAU;EAAE,SAAS;EAAqB,YAAY;EAAK;CAC3D,WAAW;EAAE,SAAS;EAAgC,YAAY;EAAK;CACvE,WAAW;EACT,SAAS;EACT,YAAY;EACb;CACD,UAAU;EAAE,SAAS;EAA6B,YAAY;EAAK;CACnE,cAAc;EAAE,SAAS;EAAyB,YAAY;EAAK;CACpE;AAED,MAAM,mBAAoE;CACxE,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACN;;AAGD,IAAa,sBAAb,cAAyC,YAAY;CACnD,AAAS,OAAO;CAChB,AAAS;CACT,AAAS;CAET,YACE,AAAS,UACT,AAAS,OACT,gBACA;EACA,MAAM,aAAa,YAAY;AAE/B,QACE,UAAU,aAAa,iBACnB,iBACA,WAAW,SACf,EACE,eAAe,WAAW,SAC3B,CACF;EAbQ;EACA;AAaT,OAAK,aAAa,WAAW;AAC7B,OAAK,cAAc,aAAa;AAChC,OAAK,OAAO;;;;AAKhB,SAAgB,uBACd,gBACqB;AACrB,QAAO,IAAI,oBAAoB,mBAAmB,WAAW,eAAe;;;AAI9E,SAAgB,sBACd,OACA,OACqB;AACrB,KAAI,iBAAiB,oBACnB,QAAO,MAAM,UAAU,QACnB,QACA,IAAI,oBAAoB,MAAM,UAAU,MAAM;AAEpD,QAAO,MAAM,6CAA6C,OAAO,MAAM;AACvE,QAAO,IAAI,oBAAoB,YAAY,MAAM;;;AAInD,SAAgB,wBACd,QACA,OACqB;AAErB,QAAO,IAAI,oBADM,iBAAiB,WAAW,YACJ,MAAM"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../../src/database/errors.ts"],"sourcesContent":["import { AppKitError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\n\nconst logger = createLogger(\"database\");\n\nexport type DatabaseErrorCategory =\n | \"INVALID_REQUEST\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"FORBIDDEN\"\n | \"TRANSIENT\"\n | \"PAYLOAD_TOO_LARGE\"\n | \"INTERNAL\"\n | \"SETUP_FAILED\";\n\n/** Which request field a rejection concerns; it never carries caller values. */\nexport interface DatabaseErrorDetail {\n readonly path: readonly string[];\n readonly message: string;\n}\n\ntype DatabaseErrorPhase =\n | \"setup\"\n | \"shutdown\"\n | \"read\"\n | \"write\"\n | \"transaction\"\n | \"runtime\";\n\nconst definitions: Record<\n DatabaseErrorCategory,\n { readonly message: string; readonly statusCode: number }\n> = {\n INVALID_REQUEST: { message: \"Invalid database request\", statusCode: 400 },\n NOT_FOUND: { message: \"Database record not found\", statusCode: 404 },\n CONFLICT: { message: \"Database conflict\", statusCode: 409 },\n FORBIDDEN: { message: \"Database operation forbidden\", statusCode: 403 },\n TRANSIENT: {\n message: \"Database operation temporarily unavailable\",\n statusCode: 503,\n },\n PAYLOAD_TOO_LARGE: {\n message: \"Database response is too large\",\n statusCode: 413,\n },\n INTERNAL: { message: \"Database operation failed\", statusCode: 500 },\n SETUP_FAILED: { message: \"Database setup failed\", statusCode: 500 },\n};\n\nconst categoryByStatus: Readonly<Record<number, DatabaseErrorCategory>> = {\n 400: \"INVALID_REQUEST\",\n 403: \"FORBIDDEN\",\n 404: \"NOT_FOUND\",\n 409: \"CONFLICT\",\n 413: \"PAYLOAD_TOO_LARGE\",\n 503: \"TRANSIENT\",\n};\n\n/** AppKit-facing database failure with stable metadata and no driver details. */\nexport class DatabasePluginError extends AppKitError {\n readonly code = \"DATABASE_PLUGIN_ERROR\";\n readonly isRetryable: boolean;\n readonly statusCode: number;\n\n constructor(\n readonly category: DatabaseErrorCategory,\n readonly phase: DatabaseErrorPhase,\n runtimeMessage?: string,\n readonly details?: readonly DatabaseErrorDetail[],\n ) {\n const definition = definitions[category];\n // Plugin boundaries replace runtime diagnostics with the stable message.\n super(\n phase === \"runtime\" && runtimeMessage\n ? runtimeMessage\n : definition.message,\n {\n clientMessage: definition.message,\n },\n );\n this.statusCode = definition.statusCode;\n this.isRetryable = category === \"TRANSIENT\";\n this.name = \"DatabasePluginError\";\n }\n}\n\n/** Keep runtime diagnostics internal until a plugin boundary classifies them. */\nexport function invalidDatabaseRequest(\n runtimeMessage?: string,\n): DatabasePluginError {\n return new DatabasePluginError(\"INVALID_REQUEST\", \"runtime\", runtimeMessage);\n}\n\n/** Refuse to publish a plugin whose configuration cannot be honored. */\nexport function databaseSetupFailed(): DatabasePluginError {\n return new DatabasePluginError(\"SETUP_FAILED\", \"setup\");\n}\n\n/** Reject untrusted request input, naming the field but never its value. */\nexport function invalidDatabaseInput(\n path: readonly string[],\n message: string,\n): DatabasePluginError {\n return new DatabasePluginError(\"INVALID_REQUEST\", \"read\", undefined, [\n { path, message },\n ]);\n}\n\n/**\n * Name an unknown failure without its payload. A driver error carries the SQL\n * text and its bound row values (for example a `DrizzleQueryError` retains\n * `query` and `params`), so only constructor names walk into the log.\n */\nfunction describeUnclassifiedError(error: unknown): string {\n const names: string[] = [];\n let current: unknown = error;\n for (let depth = 0; depth < 5 && current instanceof Error; depth++) {\n names.push(current.name);\n current = current.cause;\n }\n return names.length > 0 ? names.join(\" <- \") : typeof error;\n}\n\n/** Add operation context without retaining an unknown error's details. */\nexport function classifyDatabaseError(\n error: unknown,\n phase: DatabaseErrorPhase,\n): DatabasePluginError {\n if (error instanceof DatabasePluginError) {\n return error.phase === phase\n ? error\n : new DatabasePluginError(error.category, phase);\n }\n logger.error(\n \"Unclassified database error during %s (%s)\",\n phase,\n describeUnclassifiedError(error),\n );\n return new DatabasePluginError(\"INTERNAL\", phase);\n}\n\n/** Restore the safe database category carried through `Plugin.execute()`. */\nexport function databaseErrorFromStatus(\n status: number,\n phase: DatabaseErrorPhase,\n): DatabasePluginError {\n const category = categoryByStatus[status] ?? \"INTERNAL\";\n return new DatabasePluginError(category, phase);\n}\n"],"mappings":";;;;;AAGA,MAAM,SAAS,aAAa,WAAW;AA0BvC,MAAM,cAGF;CACF,iBAAiB;EAAE,SAAS;EAA4B,YAAY;EAAK;CACzE,WAAW;EAAE,SAAS;EAA6B,YAAY;EAAK;CACpE,UAAU;EAAE,SAAS;EAAqB,YAAY;EAAK;CAC3D,WAAW;EAAE,SAAS;EAAgC,YAAY;EAAK;CACvE,WAAW;EACT,SAAS;EACT,YAAY;EACb;CACD,mBAAmB;EACjB,SAAS;EACT,YAAY;EACb;CACD,UAAU;EAAE,SAAS;EAA6B,YAAY;EAAK;CACnE,cAAc;EAAE,SAAS;EAAyB,YAAY;EAAK;CACpE;AAED,MAAM,mBAAoE;CACxE,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACN;;AAGD,IAAa,sBAAb,cAAyC,YAAY;CACnD,AAAS,OAAO;CAChB,AAAS;CACT,AAAS;CAET,YACE,AAAS,UACT,AAAS,OACT,gBACA,AAAS,SACT;EACA,MAAM,aAAa,YAAY;AAE/B,QACE,UAAU,aAAa,iBACnB,iBACA,WAAW,SACf,EACE,eAAe,WAAW,SAC3B,CACF;EAdQ;EACA;EAEA;AAYT,OAAK,aAAa,WAAW;AAC7B,OAAK,cAAc,aAAa;AAChC,OAAK,OAAO;;;;AAKhB,SAAgB,uBACd,gBACqB;AACrB,QAAO,IAAI,oBAAoB,mBAAmB,WAAW,eAAe;;;AAI9E,SAAgB,sBAA2C;AACzD,QAAO,IAAI,oBAAoB,gBAAgB,QAAQ;;;AAIzD,SAAgB,qBACd,MACA,SACqB;AACrB,QAAO,IAAI,oBAAoB,mBAAmB,QAAQ,QAAW,CACnE;EAAE;EAAM;EAAS,CAClB,CAAC;;;;;;;AAQJ,SAAS,0BAA0B,OAAwB;CACzD,MAAM,QAAkB,EAAE;CAC1B,IAAI,UAAmB;AACvB,MAAK,IAAI,QAAQ,GAAG,QAAQ,KAAK,mBAAmB,OAAO,SAAS;AAClE,QAAM,KAAK,QAAQ,KAAK;AACxB,YAAU,QAAQ;;AAEpB,QAAO,MAAM,SAAS,IAAI,MAAM,KAAK,OAAO,GAAG,OAAO;;;AAIxD,SAAgB,sBACd,OACA,OACqB;AACrB,KAAI,iBAAiB,oBACnB,QAAO,MAAM,UAAU,QACnB,QACA,IAAI,oBAAoB,MAAM,UAAU,MAAM;AAEpD,QAAO,MACL,8CACA,OACA,0BAA0B,MAAM,CACjC;AACD,QAAO,IAAI,oBAAoB,YAAY,MAAM;;;AAInD,SAAgB,wBACd,QACA,OACqB;AAErB,QAAO,IAAI,oBADM,iBAAiB,WAAW,YACJ,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"data-path.js","names":[],"sources":["../../../src/database/runtime/data-path.ts"],"sourcesContent":["import {\n DEFAULT_LIMIT,\n type FilterOperator,\n type IdValue,\n MAX_LIMIT,\n MAX_OFFSET,\n type OrderDirection,\n} from \"../contract\";\nimport { invalidDatabaseRequest } from \"../errors\";\nimport type { AppKitTable, ColumnMeta } from \"../schema-builder\";\n\nexport type { IdValue, OrderDirection };\nexport type ScalarValue = string | number | bigint | boolean | null;\n/** Operators for one column; array operands are reserved for `in`. */\nexport type FilterOps = Partial<\n Record<FilterOperator, ScalarValue | readonly ScalarValue[]>\n>;\nexport type WhereValue = ScalarValue | readonly ScalarValue[] | FilterOps;\n/** Direct-column predicates with explicit `and` and `or` predicate groups. */\nexport type WhereClause = Readonly<\n Record<string, WhereValue | readonly WhereClause[]>\n>;\n\nexport type OrderSpec = Readonly<Record<string, OrderDirection>>;\n\nexport interface IncludeOptions {\n readonly select?: readonly string[];\n readonly where?: WhereClause;\n readonly order?: OrderSpec;\n readonly limit?: number;\n}\n\n/** Selection and bounds for one declared relation edge. */\nexport type IncludeSpec = Readonly<Record<string, boolean | IncludeOptions>>;\n\n/** A bounded root read; adapters apply defaults and validate explicit bounds. */\nexport interface QuerySpec {\n readonly where?: WhereClause;\n readonly order?: OrderSpec;\n readonly select?: readonly string[];\n readonly include?: IncludeSpec;\n readonly limit?: number;\n readonly offset?: number;\n}\n\nexport type Row = Record<string, unknown>;\n\n/** Combine predicates without making callers understand the wire shape. */\nexport function andWhere(\n existing: WhereClause | undefined,\n next: WhereClause,\n): WhereClause {\n return existing === undefined ? next : { and: [existing, next] };\n}\n\n/** Internal AppKit execution port; field names are schema-owned identifiers. */\nexport interface DataPath {\n /** Read a bounded collection from one finalized table. */\n select(table: AppKitTable, spec: QuerySpec): Promise<Row[]>;\n /** Read by the sole primary key while preserving supported query state. */\n findOne(\n table: AppKitTable,\n id: IdValue,\n spec?: Pick<QuerySpec, \"where\" | \"select\" | \"include\">,\n ): Promise<Row | null>;\n count(table: AppKitTable, where?: WhereClause): Promise<number>;\n /** Return exactly one inserted row; zero or many is an invariant failure. */\n insert(table: AppKitTable, values: Row): Promise<Row>;\n /** Return null for zero updated rows and reject more than one. */\n update(table: AppKitTable, id: IdValue, values: Row): Promise<Row | null>;\n /** Return exactly one row for a validated primary-key or unique conflict. */\n upsert(table: AppKitTable, values: Row, onConflict: string): Promise<Row>;\n /** Return false for zero deleted rows, true for one, and reject many. */\n delete(table: AppKitTable, id: IdValue): Promise<boolean>;\n /** Execute tagged SQL whose interpolations are parameter values, not SQL. */\n raw<T = Row>(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ): Promise<T[]>;\n /** Run the callback with one transaction-bound DataPath. */\n transaction<T>(callback: (tx: DataPath) => Promise<T>): Promise<T>;\n}\n\n/** Validate an explicit root or relation row limit. */\nexport function validateLimit(limit: number): number {\n if (!Number.isInteger(limit) || limit < 0 || limit > MAX_LIMIT) {\n throw invalidDatabaseRequest(\n `limit must be an integer between 0 and ${MAX_LIMIT}`,\n );\n }\n return limit;\n}\n\n/** Apply the conservative collection default when no limit is supplied. */\nexport function limitOrDefault(limit?: number): number {\n return limit === undefined ? DEFAULT_LIMIT : validateLimit(limit);\n}\n\n/** Validate an explicit root or relation row offset. */\nexport function validateOffset(offset: number): number {\n if (!Number.isSafeInteger(offset) || offset < 0 || offset > MAX_OFFSET) {\n throw invalidDatabaseRequest(\n `offset must be an integer between 0 and ${MAX_OFFSET}`,\n );\n }\n return offset;\n}\n\n/** Resolve the sole primary key required by keyed operations. */\nexport function primaryKeyMeta(table: AppKitTable): ColumnMeta {\n const primaryKeys = Object.values(table.$columns).filter(\n (column) => column.primaryKey,\n );\n if (primaryKeys.length !== 1) {\n throw invalidDatabaseRequest(`Table \"${table.$name}\" has no primary key`);\n }\n return primaryKeys[0];\n}\n\n/** Resolve an upsert target that PostgreSQL can use for conflict detection. */\nexport function conflictTargetMeta(\n table: AppKitTable,\n columnName: string,\n): ColumnMeta {\n const column = table.$columns[columnName];\n if (!column || (!column.primaryKey && !column.unique)) {\n throw invalidDatabaseRequest(\n `Column \"${table.$name}.${columnName}\" is not a conflict target`,\n );\n }\n return column;\n}\n"],"mappings":";;;;;;AAgDA,SAAgB,SACd,UACA,MACa;AACb,QAAO,aAAa,SAAY,OAAO,EAAE,KAAK,CAAC,UAAU,KAAK,EAAE;;;AAgClE,SAAgB,cAAc,OAAuB;AACnD,KAAI,CAAC,OAAO,UAAU,MAAM,IAAI,QAAQ,KAAK,QAAQ,UACnD,OAAM,uBACJ,0CAA0C,YAC3C;AAEH,QAAO;;;AAIT,SAAgB,eAAe,OAAwB;AACrD,QAAO,UAAU,SAAY,gBAAgB,cAAc,MAAM;;;AAInE,SAAgB,eAAe,QAAwB;AACrD,KAAI,CAAC,OAAO,cAAc,OAAO,IAAI,SAAS,KAAK,SAAS,WAC1D,OAAM,uBACJ,2CAA2C,aAC5C;AAEH,QAAO;;;AAIT,SAAgB,eAAe,OAAgC;CAC7D,MAAM,cAAc,OAAO,OAAO,MAAM,SAAS,CAAC,QAC/C,WAAW,OAAO,WACpB;AACD,KAAI,YAAY,WAAW,EACzB,OAAM,uBAAuB,UAAU,MAAM,MAAM,sBAAsB;AAE3E,QAAO,YAAY;;;AAIrB,SAAgB,mBACd,OACA,YACY;CACZ,MAAM,SAAS,MAAM,SAAS;AAC9B,KAAI,CAAC,UAAW,CAAC,OAAO,cAAc,CAAC,OAAO,OAC5C,OAAM,uBACJ,WAAW,MAAM,MAAM,GAAG,WAAW,4BACtC;AAEH,QAAO"}
1
+ {"version":3,"file":"data-path.js","names":[],"sources":["../../../src/database/runtime/data-path.ts"],"sourcesContent":["import {\n DEFAULT_LIMIT,\n type FilterOperator,\n type IdValue,\n MAX_LIMIT,\n MAX_OFFSET,\n type OrderDirection,\n} from \"../contract\";\nimport { invalidDatabaseRequest } from \"../errors\";\nimport type { AppKitTable, ColumnMeta } from \"../schema-builder\";\n\nexport type { IdValue, OrderDirection };\nexport type ScalarValue = string | number | bigint | boolean | null;\n/** Operators for one column; array operands are reserved for `in`. */\nexport type FilterOps = Partial<\n Record<FilterOperator, ScalarValue | readonly ScalarValue[]>\n>;\nexport type WhereValue = ScalarValue | readonly ScalarValue[] | FilterOps;\n/** Direct-column predicates with explicit `and` and `or` predicate groups. */\nexport type WhereClause = Readonly<\n Record<string, WhereValue | readonly WhereClause[]>\n>;\n\nexport type OrderSpec = Readonly<Record<string, OrderDirection>>;\n\nexport interface IncludeOptions {\n readonly select?: readonly string[];\n readonly where?: WhereClause;\n readonly order?: OrderSpec;\n readonly limit?: number;\n readonly include?: IncludeSpec;\n}\n\n/** Selection and bounds for one declared relation edge. */\nexport type IncludeSpec = Readonly<Record<string, boolean | IncludeOptions>>;\n\n/** A bounded root read; adapters apply defaults and validate explicit bounds. */\nexport interface QuerySpec {\n readonly where?: WhereClause;\n readonly order?: OrderSpec;\n readonly select?: readonly string[];\n readonly include?: IncludeSpec;\n readonly limit?: number;\n readonly offset?: number;\n}\n\nexport type Row = Record<string, unknown>;\n\n/** Combine predicates without making callers understand the wire shape. */\nexport function andWhere(\n existing: WhereClause | undefined,\n next: WhereClause,\n): WhereClause {\n return existing === undefined ? next : { and: [existing, next] };\n}\n\n/** Internal AppKit execution port; field names are schema-owned identifiers. */\nexport interface DataPath {\n /** Read a bounded collection from one finalized table. */\n select(table: AppKitTable, spec: QuerySpec): Promise<Row[]>;\n /** Read by the sole primary key while preserving supported query state. */\n findOne(\n table: AppKitTable,\n id: IdValue,\n spec?: Pick<QuerySpec, \"where\" | \"select\" | \"include\">,\n ): Promise<Row | null>;\n count(table: AppKitTable, where?: WhereClause): Promise<number>;\n /** Return exactly one inserted row; zero or many is an invariant failure. */\n insert(table: AppKitTable, values: Row): Promise<Row>;\n /** Return null for zero updated rows and reject more than one. */\n update(table: AppKitTable, id: IdValue, values: Row): Promise<Row | null>;\n /** Return exactly one row for a validated primary-key or unique conflict. */\n upsert(table: AppKitTable, values: Row, onConflict: string): Promise<Row>;\n /** Return false for zero deleted rows, true for one, and reject many. */\n delete(table: AppKitTable, id: IdValue): Promise<boolean>;\n /** Execute tagged SQL whose interpolations are parameter values, not SQL. */\n raw<T = Row>(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ): Promise<T[]>;\n /** Run the callback with one transaction-bound DataPath. */\n transaction<T>(callback: (tx: DataPath) => Promise<T>): Promise<T>;\n}\n\n/** Validate an explicit root or relation row limit. */\nexport function validateLimit(limit: number): number {\n if (!Number.isInteger(limit) || limit < 0 || limit > MAX_LIMIT) {\n throw invalidDatabaseRequest(\n `limit must be an integer between 0 and ${MAX_LIMIT}`,\n );\n }\n return limit;\n}\n\n/** Apply the conservative collection default when no limit is supplied. */\nexport function limitOrDefault(limit?: number): number {\n return limit === undefined ? DEFAULT_LIMIT : validateLimit(limit);\n}\n\n/** Validate an explicit root or relation row offset. */\nexport function validateOffset(offset: number): number {\n if (!Number.isSafeInteger(offset) || offset < 0 || offset > MAX_OFFSET) {\n throw invalidDatabaseRequest(\n `offset must be an integer between 0 and ${MAX_OFFSET}`,\n );\n }\n return offset;\n}\n\n/** Resolve the sole primary key required by keyed operations. */\nexport function primaryKeyMeta(table: AppKitTable): ColumnMeta {\n const primaryKeys = Object.values(table.$columns).filter(\n (column) => column.primaryKey,\n );\n if (primaryKeys.length !== 1) {\n throw invalidDatabaseRequest(`Table \"${table.$name}\" has no primary key`);\n }\n return primaryKeys[0];\n}\n\n/** Resolve an upsert target that PostgreSQL can use for conflict detection. */\nexport function conflictTargetMeta(\n table: AppKitTable,\n columnName: string,\n): ColumnMeta {\n const column = table.$columns[columnName];\n if (!column || (!column.primaryKey && !column.unique)) {\n throw invalidDatabaseRequest(\n `Column \"${table.$name}.${columnName}\" is not a conflict target`,\n );\n }\n return column;\n}\n"],"mappings":";;;;;;AAiDA,SAAgB,SACd,UACA,MACa;AACb,QAAO,aAAa,SAAY,OAAO,EAAE,KAAK,CAAC,UAAU,KAAK,EAAE;;;AAgClE,SAAgB,cAAc,OAAuB;AACnD,KAAI,CAAC,OAAO,UAAU,MAAM,IAAI,QAAQ,KAAK,QAAQ,UACnD,OAAM,uBACJ,0CAA0C,YAC3C;AAEH,QAAO;;;AAIT,SAAgB,eAAe,OAAwB;AACrD,QAAO,UAAU,SAAY,gBAAgB,cAAc,MAAM;;;AAInE,SAAgB,eAAe,QAAwB;AACrD,KAAI,CAAC,OAAO,cAAc,OAAO,IAAI,SAAS,KAAK,SAAS,WAC1D,OAAM,uBACJ,2CAA2C,aAC5C;AAEH,QAAO;;;AAIT,SAAgB,eAAe,OAAgC;CAC7D,MAAM,cAAc,OAAO,OAAO,MAAM,SAAS,CAAC,QAC/C,WAAW,OAAO,WACpB;AACD,KAAI,YAAY,WAAW,EACzB,OAAM,uBAAuB,UAAU,MAAM,MAAM,sBAAsB;AAE3E,QAAO,YAAY;;;AAIrB,SAAgB,mBACd,OACA,YACY;CACZ,MAAM,SAAS,MAAM,SAAS;AAC9B,KAAI,CAAC,UAAW,CAAC,OAAO,cAAc,CAAC,OAAO,OAC5C,OAAM,uBACJ,WAAW,MAAM,MAAM,GAAG,WAAW,4BACtC;AAEH,QAAO"}
@@ -65,7 +65,7 @@ function sqlStateOf(error) {
65
65
  /** Classify SQLSTATE without retaining the driver error or its properties. */
66
66
  function classifyDriverError(error) {
67
67
  const code = sqlStateOf(error);
68
- const category = code === "40001" || code === "40P01" ? "TRANSIENT" : code === "42501" ? "FORBIDDEN" : code?.startsWith("23") ? "CONFLICT" : "INTERNAL";
68
+ const category = code === "40001" || code === "40P01" || code === "57014" ? "TRANSIENT" : code === "42501" ? "FORBIDDEN" : code?.startsWith("23") ? "CONFLICT" : "INTERNAL";
69
69
  logger.error("Database driver error classified as %s (SQLSTATE %s)", category, code ?? "unknown");
70
70
  return new DatabasePluginError(category, "runtime");
71
71
  }