@vertesia/tools-sdk 1.2.0 → 1.4.0-dev.20260615.042549Z

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 (271) hide show
  1. package/lib/{types/ActivityCollection.d.ts → ActivityCollection.d.ts} +7 -7
  2. package/lib/ActivityCollection.d.ts.map +1 -0
  3. package/lib/{esm/ActivityCollection.js → ActivityCollection.js} +6 -6
  4. package/lib/ActivityCollection.js.map +1 -0
  5. package/lib/{types/ContentTypesCollection.d.ts → ContentTypesCollection.d.ts} +2 -2
  6. package/lib/ContentTypesCollection.d.ts.map +1 -0
  7. package/lib/{esm/ContentTypesCollection.js → ContentTypesCollection.js} +3 -3
  8. package/lib/ContentTypesCollection.js.map +1 -0
  9. package/lib/{types/InteractionCollection.d.ts → InteractionCollection.d.ts} +2 -2
  10. package/lib/InteractionCollection.d.ts.map +1 -0
  11. package/lib/{esm/InteractionCollection.js → InteractionCollection.js} +3 -3
  12. package/lib/InteractionCollection.js.map +1 -0
  13. package/lib/{types/RenderingTemplateCollection.d.ts → RenderingTemplateCollection.d.ts} +1 -1
  14. package/lib/RenderingTemplateCollection.d.ts.map +1 -0
  15. package/lib/{esm/RenderingTemplateCollection.js → RenderingTemplateCollection.js} +3 -3
  16. package/lib/RenderingTemplateCollection.js.map +1 -0
  17. package/lib/{types/SkillCollection.d.ts → SkillCollection.d.ts} +5 -5
  18. package/lib/SkillCollection.d.ts.map +1 -0
  19. package/lib/{esm/SkillCollection.js → SkillCollection.js} +68 -51
  20. package/lib/SkillCollection.js.map +1 -0
  21. package/lib/{types/ToolCollection.d.ts → ToolCollection.d.ts} +10 -10
  22. package/lib/ToolCollection.d.ts.map +1 -0
  23. package/lib/{esm/ToolCollection.js → ToolCollection.js} +21 -18
  24. package/lib/ToolCollection.js.map +1 -0
  25. package/lib/ToolRegistry.d.ts +22 -0
  26. package/lib/ToolRegistry.d.ts.map +1 -0
  27. package/lib/{esm/ToolRegistry.js → ToolRegistry.js} +6 -36
  28. package/lib/ToolRegistry.js.map +1 -0
  29. package/lib/{types/auth.d.ts → auth.d.ts} +5 -5
  30. package/lib/auth.d.ts.map +1 -0
  31. package/lib/{esm/auth.js → auth.js} +23 -17
  32. package/lib/auth.js.map +1 -0
  33. package/lib/build/validate.d.ts.map +1 -0
  34. package/lib/build/validate.js.map +1 -0
  35. package/lib/copy-assets.d.ts.map +1 -0
  36. package/lib/{esm/copy-assets.js → copy-assets.js} +4 -7
  37. package/lib/copy-assets.js.map +1 -0
  38. package/lib/index.d.ts +16 -0
  39. package/lib/index.d.ts.map +1 -0
  40. package/lib/index.js +15 -0
  41. package/lib/index.js.map +1 -0
  42. package/lib/server/activities.d.ts +4 -0
  43. package/lib/server/activities.d.ts.map +1 -0
  44. package/lib/{esm/server → server}/activities.js +12 -9
  45. package/lib/server/activities.js.map +1 -0
  46. package/lib/server/app-package.d.ts +12 -0
  47. package/lib/server/app-package.d.ts.map +1 -0
  48. package/lib/server/app-package.js +197 -0
  49. package/lib/server/app-package.js.map +1 -0
  50. package/lib/server/content-types.d.ts +4 -0
  51. package/lib/server/content-types.d.ts.map +1 -0
  52. package/lib/server/content-types.js +100 -0
  53. package/lib/server/content-types.js.map +1 -0
  54. package/lib/server/dashboards.d.ts +4 -0
  55. package/lib/server/dashboards.d.ts.map +1 -0
  56. package/lib/server/dashboards.js +25 -0
  57. package/lib/server/dashboards.js.map +1 -0
  58. package/lib/server/interactions.d.ts +4 -0
  59. package/lib/server/interactions.d.ts.map +1 -0
  60. package/lib/{esm/server → server}/interactions.js +16 -16
  61. package/lib/server/interactions.js.map +1 -0
  62. package/lib/server/mcp.d.ts +4 -0
  63. package/lib/server/mcp.d.ts.map +1 -0
  64. package/lib/{esm/server → server}/mcp.js +5 -4
  65. package/lib/server/mcp.js.map +1 -0
  66. package/lib/server/processes.d.ts +4 -0
  67. package/lib/server/processes.d.ts.map +1 -0
  68. package/lib/server/processes.js +26 -0
  69. package/lib/server/processes.js.map +1 -0
  70. package/lib/server/site.d.ts +4 -0
  71. package/lib/server/site.d.ts.map +1 -0
  72. package/lib/{esm/server → server}/site.js +4 -2
  73. package/lib/server/site.js.map +1 -0
  74. package/lib/server/skills.d.ts +4 -0
  75. package/lib/server/skills.d.ts.map +1 -0
  76. package/lib/{esm/server → server}/skills.js +14 -12
  77. package/lib/server/skills.js.map +1 -0
  78. package/lib/server/templates.d.ts +4 -0
  79. package/lib/server/templates.d.ts.map +1 -0
  80. package/lib/{esm/server → server}/templates.js +10 -10
  81. package/lib/server/templates.js.map +1 -0
  82. package/lib/server/tools.d.ts +4 -0
  83. package/lib/server/tools.d.ts.map +1 -0
  84. package/lib/{esm/server → server}/tools.js +7 -9
  85. package/lib/server/tools.js.map +1 -0
  86. package/lib/{types/server → server}/types.d.ts +21 -16
  87. package/lib/server/types.d.ts.map +1 -0
  88. package/lib/{cjs → server}/types.js.map +1 -1
  89. package/lib/server/widgets.d.ts +4 -0
  90. package/lib/server/widgets.d.ts.map +1 -0
  91. package/lib/server/widgets.js.map +1 -0
  92. package/lib/{types/server.d.ts → server.d.ts} +3 -3
  93. package/lib/server.d.ts.map +1 -0
  94. package/lib/{esm/server.js → server.js} +28 -22
  95. package/lib/server.js.map +1 -0
  96. package/lib/site/styles.d.ts.map +1 -0
  97. package/lib/{esm/site → site}/styles.js.map +1 -1
  98. package/lib/{types/site → site}/templates.d.ts +9 -9
  99. package/lib/site/templates.d.ts.map +1 -0
  100. package/lib/{esm/site → site}/templates.js +152 -98
  101. package/lib/site/templates.js.map +1 -0
  102. package/lib/{types/types.d.ts → types.d.ts} +29 -16
  103. package/lib/types.d.ts.map +1 -0
  104. package/lib/types.js.map +1 -0
  105. package/lib/utils.d.ts.map +1 -0
  106. package/lib/{esm/utils.js → utils.js} +11 -5
  107. package/lib/utils.js.map +1 -0
  108. package/package.json +21 -23
  109. package/src/ActivityCollection.test.ts +60 -59
  110. package/src/ActivityCollection.ts +27 -20
  111. package/src/ContentTypesCollection.ts +6 -9
  112. package/src/InteractionCollection.ts +6 -8
  113. package/src/RenderingTemplateCollection.ts +49 -51
  114. package/src/SkillCollection.ts +91 -72
  115. package/src/ToolCollection.ts +52 -43
  116. package/src/ToolRegistry.ts +20 -50
  117. package/src/auth.ts +35 -27
  118. package/src/copy-assets.ts +5 -12
  119. package/src/index.ts +15 -15
  120. package/src/server/activities.test.ts +70 -67
  121. package/src/server/activities.ts +17 -13
  122. package/src/server/app-package.test.ts +140 -0
  123. package/src/server/app-package.ts +115 -52
  124. package/src/server/content-types.test.ts +64 -0
  125. package/src/server/content-types.ts +53 -25
  126. package/src/server/dashboards.ts +31 -0
  127. package/src/server/interactions.ts +29 -28
  128. package/src/server/mcp.ts +16 -16
  129. package/src/server/processes.ts +35 -0
  130. package/src/server/site.ts +7 -15
  131. package/src/server/skills.ts +19 -18
  132. package/src/server/templates.ts +82 -80
  133. package/src/server/tools.ts +12 -16
  134. package/src/server/types.ts +29 -20
  135. package/src/server/widgets.ts +5 -9
  136. package/src/server.ts +55 -47
  137. package/src/site/styles.ts +1 -1
  138. package/src/site/templates.ts +259 -157
  139. package/src/types.ts +55 -31
  140. package/src/utils.ts +11 -6
  141. package/lib/cjs/ActivityCollection.js +0 -93
  142. package/lib/cjs/ActivityCollection.js.map +0 -1
  143. package/lib/cjs/ContentTypesCollection.js +0 -43
  144. package/lib/cjs/ContentTypesCollection.js.map +0 -1
  145. package/lib/cjs/InteractionCollection.js +0 -43
  146. package/lib/cjs/InteractionCollection.js.map +0 -1
  147. package/lib/cjs/RenderingTemplateCollection.js +0 -43
  148. package/lib/cjs/RenderingTemplateCollection.js.map +0 -1
  149. package/lib/cjs/SkillCollection.js +0 -384
  150. package/lib/cjs/SkillCollection.js.map +0 -1
  151. package/lib/cjs/ToolCollection.js +0 -221
  152. package/lib/cjs/ToolCollection.js.map +0 -1
  153. package/lib/cjs/ToolRegistry.js +0 -95
  154. package/lib/cjs/ToolRegistry.js.map +0 -1
  155. package/lib/cjs/auth.js +0 -131
  156. package/lib/cjs/auth.js.map +0 -1
  157. package/lib/cjs/build/validate.js +0 -7
  158. package/lib/cjs/build/validate.js.map +0 -1
  159. package/lib/cjs/copy-assets.js +0 -84
  160. package/lib/cjs/copy-assets.js.map +0 -1
  161. package/lib/cjs/index.js +0 -34
  162. package/lib/cjs/index.js.map +0 -1
  163. package/lib/cjs/package.json +0 -3
  164. package/lib/cjs/server/activities.js +0 -103
  165. package/lib/cjs/server/activities.js.map +0 -1
  166. package/lib/cjs/server/app-package.js +0 -154
  167. package/lib/cjs/server/app-package.js.map +0 -1
  168. package/lib/cjs/server/content-types.js +0 -73
  169. package/lib/cjs/server/content-types.js.map +0 -1
  170. package/lib/cjs/server/interactions.js +0 -101
  171. package/lib/cjs/server/interactions.js.map +0 -1
  172. package/lib/cjs/server/mcp.js +0 -45
  173. package/lib/cjs/server/mcp.js.map +0 -1
  174. package/lib/cjs/server/site.js +0 -48
  175. package/lib/cjs/server/site.js.map +0 -1
  176. package/lib/cjs/server/skills.js +0 -124
  177. package/lib/cjs/server/skills.js.map +0 -1
  178. package/lib/cjs/server/templates.js +0 -67
  179. package/lib/cjs/server/templates.js.map +0 -1
  180. package/lib/cjs/server/tools.js +0 -87
  181. package/lib/cjs/server/tools.js.map +0 -1
  182. package/lib/cjs/server/types.js +0 -3
  183. package/lib/cjs/server/types.js.map +0 -1
  184. package/lib/cjs/server/widgets.js +0 -27
  185. package/lib/cjs/server/widgets.js.map +0 -1
  186. package/lib/cjs/server.js +0 -142
  187. package/lib/cjs/server.js.map +0 -1
  188. package/lib/cjs/site/styles.js +0 -692
  189. package/lib/cjs/site/styles.js.map +0 -1
  190. package/lib/cjs/site/templates.js +0 -1320
  191. package/lib/cjs/site/templates.js.map +0 -1
  192. package/lib/cjs/types.js +0 -3
  193. package/lib/cjs/utils.js +0 -44
  194. package/lib/cjs/utils.js.map +0 -1
  195. package/lib/esm/ActivityCollection.js.map +0 -1
  196. package/lib/esm/ContentTypesCollection.js.map +0 -1
  197. package/lib/esm/InteractionCollection.js.map +0 -1
  198. package/lib/esm/RenderingTemplateCollection.js.map +0 -1
  199. package/lib/esm/SkillCollection.js.map +0 -1
  200. package/lib/esm/ToolCollection.js.map +0 -1
  201. package/lib/esm/ToolRegistry.js.map +0 -1
  202. package/lib/esm/auth.js.map +0 -1
  203. package/lib/esm/build/validate.js.map +0 -1
  204. package/lib/esm/copy-assets.js.map +0 -1
  205. package/lib/esm/index.js +0 -14
  206. package/lib/esm/index.js.map +0 -1
  207. package/lib/esm/server/activities.js.map +0 -1
  208. package/lib/esm/server/app-package.js +0 -151
  209. package/lib/esm/server/app-package.js.map +0 -1
  210. package/lib/esm/server/content-types.js +0 -70
  211. package/lib/esm/server/content-types.js.map +0 -1
  212. package/lib/esm/server/interactions.js.map +0 -1
  213. package/lib/esm/server/mcp.js.map +0 -1
  214. package/lib/esm/server/site.js.map +0 -1
  215. package/lib/esm/server/skills.js.map +0 -1
  216. package/lib/esm/server/templates.js.map +0 -1
  217. package/lib/esm/server/tools.js.map +0 -1
  218. package/lib/esm/server/types.js.map +0 -1
  219. package/lib/esm/server/widgets.js.map +0 -1
  220. package/lib/esm/server.js.map +0 -1
  221. package/lib/esm/site/templates.js.map +0 -1
  222. package/lib/esm/types.js.map +0 -1
  223. package/lib/esm/utils.js.map +0 -1
  224. package/lib/types/ActivityCollection.d.ts.map +0 -1
  225. package/lib/types/ContentTypesCollection.d.ts.map +0 -1
  226. package/lib/types/InteractionCollection.d.ts.map +0 -1
  227. package/lib/types/RenderingTemplateCollection.d.ts.map +0 -1
  228. package/lib/types/SkillCollection.d.ts.map +0 -1
  229. package/lib/types/ToolCollection.d.ts.map +0 -1
  230. package/lib/types/ToolRegistry.d.ts +0 -22
  231. package/lib/types/ToolRegistry.d.ts.map +0 -1
  232. package/lib/types/auth.d.ts.map +0 -1
  233. package/lib/types/build/validate.d.ts.map +0 -1
  234. package/lib/types/copy-assets.d.ts.map +0 -1
  235. package/lib/types/index.d.ts +0 -14
  236. package/lib/types/index.d.ts.map +0 -1
  237. package/lib/types/server/activities.d.ts +0 -4
  238. package/lib/types/server/activities.d.ts.map +0 -1
  239. package/lib/types/server/app-package.d.ts +0 -4
  240. package/lib/types/server/app-package.d.ts.map +0 -1
  241. package/lib/types/server/content-types.d.ts +0 -4
  242. package/lib/types/server/content-types.d.ts.map +0 -1
  243. package/lib/types/server/interactions.d.ts +0 -4
  244. package/lib/types/server/interactions.d.ts.map +0 -1
  245. package/lib/types/server/mcp.d.ts +0 -4
  246. package/lib/types/server/mcp.d.ts.map +0 -1
  247. package/lib/types/server/site.d.ts +0 -4
  248. package/lib/types/server/site.d.ts.map +0 -1
  249. package/lib/types/server/skills.d.ts +0 -4
  250. package/lib/types/server/skills.d.ts.map +0 -1
  251. package/lib/types/server/templates.d.ts +0 -4
  252. package/lib/types/server/templates.d.ts.map +0 -1
  253. package/lib/types/server/tools.d.ts +0 -4
  254. package/lib/types/server/tools.d.ts.map +0 -1
  255. package/lib/types/server/types.d.ts.map +0 -1
  256. package/lib/types/server/widgets.d.ts +0 -4
  257. package/lib/types/server/widgets.d.ts.map +0 -1
  258. package/lib/types/server.d.ts.map +0 -1
  259. package/lib/types/site/styles.d.ts.map +0 -1
  260. package/lib/types/site/templates.d.ts.map +0 -1
  261. package/lib/types/types.d.ts.map +0 -1
  262. package/lib/types/utils.d.ts.map +0 -1
  263. /package/lib/{types/build → build}/validate.d.ts +0 -0
  264. /package/lib/{esm/build → build}/validate.js +0 -0
  265. /package/lib/{types/copy-assets.d.ts → copy-assets.d.ts} +0 -0
  266. /package/lib/{esm/server → server}/types.js +0 -0
  267. /package/lib/{esm/server → server}/widgets.js +0 -0
  268. /package/lib/{types/site → site}/styles.d.ts +0 -0
  269. /package/lib/{esm/site → site}/styles.js +0 -0
  270. /package/lib/{esm/types.js → types.js} +0 -0
  271. /package/lib/{types/utils.d.ts → utils.d.ts} +0 -0
@@ -0,0 +1,140 @@
1
+ import { PromptRole } from '@llumiverse/common';
2
+ import { PROCESS_DEFINITION_FORMAT_VERSION, TemplateType } from '@vertesia/common';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { ContentTypesCollection } from '../ContentTypesCollection.js';
5
+ import { InteractionCollection } from '../InteractionCollection.js';
6
+ import { buildAppPackage } from './app-package.js';
7
+ import type { ToolServerConfig } from './types.js';
8
+
9
+ describe('buildAppPackage', () => {
10
+ it('builds the same package artifact inventory used by the package route', async () => {
11
+ const config = {
12
+ interactions: [
13
+ new InteractionCollection({
14
+ name: 'claims',
15
+ interactions: [
16
+ {
17
+ name: 'review',
18
+ title: 'Review Claim',
19
+ prompts: [
20
+ {
21
+ role: PromptRole.user,
22
+ content: '{{user_prompt}}',
23
+ content_type: TemplateType.handlebars,
24
+ },
25
+ ],
26
+ },
27
+ ],
28
+ }),
29
+ ],
30
+ types: [
31
+ new ContentTypesCollection({
32
+ name: 'claims',
33
+ types: [
34
+ {
35
+ name: 'claim',
36
+ object_schema: {
37
+ type: 'object',
38
+ properties: {
39
+ status: { type: 'string' },
40
+ },
41
+ },
42
+ },
43
+ ],
44
+ }),
45
+ ],
46
+ processes: [
47
+ {
48
+ id: 'claims:intake',
49
+ name: 'Claims Intake',
50
+ definition: {
51
+ format_version: PROCESS_DEFINITION_FORMAT_VERSION,
52
+ process: 'claims_intake',
53
+ initial: 'done',
54
+ context: {
55
+ schema: {
56
+ type: 'object',
57
+ additionalProperties: true,
58
+ },
59
+ initial: {},
60
+ },
61
+ nodes: {
62
+ done: { type: 'final', title: 'Done' },
63
+ },
64
+ },
65
+ },
66
+ ],
67
+ dashboards: [
68
+ {
69
+ id: 'claims:ops',
70
+ title: 'Claims Ops',
71
+ spec: {},
72
+ },
73
+ ],
74
+ uiConfig: {
75
+ src: '/lib/plugin.js',
76
+ available_in: ['app_portal'],
77
+ },
78
+ } satisfies ToolServerConfig;
79
+
80
+ const pkg = await buildAppPackage(config, {
81
+ origin: 'https://apps.example.test',
82
+ scope: 'all',
83
+ });
84
+
85
+ expect(pkg.interactions?.map((interaction) => interaction.id)).toEqual(['claims:review']);
86
+ expect(pkg.types?.map((type) => type.id)).toEqual(['claim']);
87
+ expect(pkg.processes?.map((process) => process.id)).toEqual(['claims:intake']);
88
+ expect(pkg.dashboards?.map((dashboard) => dashboard.id)).toEqual(['claims:ops']);
89
+ expect(pkg.ui?.src).toBe('https://apps.example.test/lib/plugin.js');
90
+ });
91
+
92
+ it('honors package scopes', async () => {
93
+ const config = {
94
+ interactions: [
95
+ new InteractionCollection({
96
+ name: 'claims',
97
+ interactions: [{ name: 'review', prompts: [] }],
98
+ }),
99
+ ],
100
+ types: [
101
+ new ContentTypesCollection({
102
+ name: 'claims',
103
+ types: [{ name: 'claim' }],
104
+ }),
105
+ ],
106
+ } satisfies ToolServerConfig;
107
+
108
+ const pkg = await buildAppPackage(config, { scope: 'types' });
109
+
110
+ expect(pkg.types?.map((type) => type.id)).toEqual(['claim']);
111
+ expect(pkg.interactions).toBeUndefined();
112
+ });
113
+ });
114
+
115
+ describe('buildAppPackage type identity', () => {
116
+ it('exposes the bare type name as the public id (collections are code organization only)', async () => {
117
+ const config = {
118
+ types: [
119
+ new ContentTypesCollection({ name: 'claims', types: [{ name: 'claim' }] }),
120
+ new ContentTypesCollection({ name: 'audit', types: [{ name: 'audit_event' }] }),
121
+ ],
122
+ } satisfies ToolServerConfig;
123
+
124
+ const pkg = await buildAppPackage(config, { scope: 'types' });
125
+ expect(pkg.types?.map((type) => type.id)).toEqual(['claim', 'audit_event']);
126
+ });
127
+
128
+ it('fails the package build when a type name is duplicated across collections', async () => {
129
+ const config = {
130
+ types: [
131
+ new ContentTypesCollection({ name: 'claims', types: [{ name: 'claim' }] }),
132
+ new ContentTypesCollection({ name: 'legacy', types: [{ name: 'claim' }] }),
133
+ ],
134
+ } satisfies ToolServerConfig;
135
+
136
+ await expect(buildAppPackage(config, { scope: 'types' })).rejects.toThrow(
137
+ /Duplicate content type name 'claim'/,
138
+ );
139
+ });
140
+ });
@@ -1,32 +1,45 @@
1
- import { AppPackage, AppPackageScope, AppWidgetInfo, CatalogInteractionRef, InCodeTypeDefinition, RemoteActivityDefinition } from "@vertesia/common";
2
- import { Context, Hono } from "hono";
3
- import { ToolUseContext } from "../types.js";
4
- import { ToolServerConfig } from "./types.js";
1
+ import type {
2
+ AppDashboardDefinition,
3
+ AppPackage,
4
+ AppPackageScope,
5
+ AppWidgetInfo,
6
+ CatalogInteractionRef,
7
+ InCodeProcessDefinition,
8
+ InCodeTypeDefinition,
9
+ RemoteActivityDefinition,
10
+ } from '@vertesia/common';
11
+ import type { Context, Hono } from 'hono';
12
+ import type { ToolUseContext } from '../types.js';
13
+ import type { ToolServerConfig } from './types.js';
5
14
 
6
15
  function getRequestPayload<T>(c: Context): Promise<T | undefined> {
7
- return c.req.method === "POST" ? c.req.json<T>() : Promise.resolve(undefined);
16
+ return c.req.method === 'POST' ? c.req.json<T>() : Promise.resolve(undefined);
8
17
  }
9
18
 
10
- const builders: Record<Exclude<AppPackageScope, 'all'>, (pkg: AppPackage, config: ToolServerConfig, c: Context) => Promise<void>> = {
11
- async tools(pkg: AppPackage, config: ToolServerConfig, c: Context) {
19
+ export interface BuildAppPackageOptions {
20
+ scope?: AppPackageScope | AppPackageScope[] | string;
21
+ origin?: string;
22
+ toolUseContext?: ToolUseContext;
23
+ }
24
+
25
+ type AppPackageBuilder = (pkg: AppPackage, config: ToolServerConfig, options: BuildAppPackageOptions) => Promise<void>;
26
+
27
+ const builders: Record<Exclude<AppPackageScope, 'all'>, AppPackageBuilder> = {
28
+ async tools(pkg: AppPackage, config: ToolServerConfig, options: BuildAppPackageOptions) {
12
29
  const { tools: toolCollections = [], skills: skillCollections = [] } = config;
13
30
 
14
- const filterContext = await getRequestPayload<ToolUseContext>(c);
31
+ const filterContext = options.toolUseContext;
15
32
 
16
33
  // Aggregate all tools from all collections
17
- const allTools = toolCollections.flatMap(collection =>
18
- collection.getToolDefinitions(filterContext)
19
- );
34
+ const allTools = toolCollections.flatMap((collection) => collection.getToolDefinitions(filterContext));
20
35
 
21
36
  // same for skills
22
- const allSkills = skillCollections.flatMap(collection =>
23
- collection.getToolDefinitions(filterContext)
24
- );
37
+ const allSkills = skillCollections.flatMap((collection) => collection.getToolDefinitions(filterContext));
25
38
 
26
39
  // Deduplicate by tool name (skills listed first take priority)
27
40
  const seen = new Set<string>();
28
41
  const combined = allSkills.concat(allTools);
29
- pkg.tools = combined.filter(tool => {
42
+ pkg.tools = combined.filter((tool) => {
30
43
  if (seen.has(tool.name)) {
31
44
  console.warn(`[app-package] Duplicate tool name "${tool.name}", skipping`);
32
45
  return false;
@@ -37,11 +50,11 @@ const builders: Record<Exclude<AppPackageScope, 'all'>, (pkg: AppPackage, config
37
50
  },
38
51
  async interactions(pkg: AppPackage, config: ToolServerConfig) {
39
52
  const allInteractions: CatalogInteractionRef[] = [];
40
- for (const coll of (config.interactions || [])) {
53
+ for (const coll of config.interactions || []) {
41
54
  for (const inter of coll.interactions) {
42
55
  allInteractions.push({
43
- type: "app",
44
- id: coll.name + ":" + inter.name,
56
+ type: 'app',
57
+ id: `${coll.name}:${inter.name}`,
45
58
  name: inter.name,
46
59
  title: inter.title || inter.name,
47
60
  description: inter.description,
@@ -52,26 +65,56 @@ const builders: Record<Exclude<AppPackageScope, 'all'>, (pkg: AppPackage, config
52
65
  pkg.interactions = allInteractions;
53
66
  },
54
67
  async types(pkg: AppPackage, config: ToolServerConfig) {
68
+ // A type's public id is its BARE name: stored objects reference it as the portable
69
+ // `app:<app>:<type>` string, so the in-code collection (a code-organization grouping,
70
+ // unlike tool/skill collections which are semantic) must NOT leak into the id.
71
+ // Names must therefore be unique across collections — fail the package build otherwise.
55
72
  const allTypes: InCodeTypeDefinition[] = [];
73
+ const seen = new Map<string, string>();
56
74
  for (const coll of config.types || []) {
57
75
  for (const type of coll.types) {
58
- allTypes.push({
59
- ...type,
60
- id: coll.name + ":" + type.name
61
- });
76
+ const existing = seen.get(type.name);
77
+ if (existing) {
78
+ throw new Error(
79
+ `Duplicate content type name '${type.name}' (collections '${existing}' and '${coll.name}'). ` +
80
+ `Type names must be unique across collections so the portable 'app:<app>:${type.name}' ref resolves.`,
81
+ );
82
+ }
83
+ seen.set(type.name, coll.name);
84
+ allTypes.push({ ...type, id: type.name });
62
85
  }
63
86
  }
64
87
  pkg.types = allTypes;
65
88
  },
89
+ async processes(pkg: AppPackage, config: ToolServerConfig) {
90
+ const allProcesses: InCodeProcessDefinition[] = [];
91
+ for (const process of config.processes || []) {
92
+ allProcesses.push(process);
93
+ }
94
+ pkg.processes = allProcesses;
95
+ },
66
96
  async templates(pkg: AppPackage, config: ToolServerConfig) {
67
97
  const basePath = `${config.prefix || '/api'}/templates`;
68
- pkg.templates = (config.templates || []).flatMap(coll =>
98
+ pkg.templates = (config.templates || []).flatMap((coll) =>
69
99
  coll.templates.map(({ instructions: _, ...ref }) => ({
70
100
  ...ref,
71
101
  path: `${basePath}/${coll.name}/${ref.name}`,
72
- }))
102
+ })),
73
103
  );
74
104
  },
105
+ async dashboards(pkg: AppPackage, config: ToolServerConfig) {
106
+ const seen = new Set<string>();
107
+ const dashboards: AppDashboardDefinition[] = [];
108
+ for (const dashboard of config.dashboards || []) {
109
+ if (seen.has(dashboard.id)) {
110
+ console.warn(`[app-package] Duplicate dashboard id "${dashboard.id}", skipping`);
111
+ continue;
112
+ }
113
+ seen.add(dashboard.id);
114
+ dashboards.push(dashboard);
115
+ }
116
+ pkg.dashboards = dashboards;
117
+ },
75
118
  async widgets(pkg: AppPackage, config: ToolServerConfig) {
76
119
  const { skills: skillCollections = [] } = config;
77
120
  const widgets: Record<string, AppWidgetInfo> = {};
@@ -81,20 +124,20 @@ const builders: Record<Exclude<AppPackageScope, 'all'>, (pkg: AppPackage, config
81
124
  widgets[skill.name] = {
82
125
  skill: skill.name,
83
126
  collection: coll.name,
84
- url: `/widgets/${skill.widgets[0]}.js`
127
+ url: `/widgets/${skill.widgets[0]}.js`,
85
128
  } satisfies AppWidgetInfo;
86
129
  }
87
130
  }
88
131
  }
89
132
  pkg.widgets = widgets;
90
133
  },
91
- async ui(pkg: AppPackage, config: ToolServerConfig, c: Context) {
134
+ async ui(pkg: AppPackage, config: ToolServerConfig, options: BuildAppPackageOptions) {
92
135
  if (config.uiConfig) {
93
136
  pkg.ui = { ...config.uiConfig };
94
- const origin = new URL(c.req.url).origin;
137
+ const origin = options.origin || 'http://localhost';
95
138
  pkg.ui.src = new URL(pkg.ui.src, origin).toString();
96
139
  if (!pkg.ui.isolation) {
97
- pkg.ui.isolation = "shadow";
140
+ pkg.ui.isolation = 'shadow';
98
141
  }
99
142
  }
100
143
  },
@@ -112,56 +155,77 @@ const builders: Record<Exclude<AppPackageScope, 'all'>, (pkg: AppPackage, config
112
155
  }
113
156
  pkg.activities = allActivities;
114
157
  },
115
- }
158
+ };
116
159
 
160
+ function normalizeScopes(scope: BuildAppPackageOptions['scope']): Set<AppPackageScope> {
161
+ const values = Array.isArray(scope) ? scope : typeof scope === 'string' ? scope.split(',') : [scope || 'all'];
162
+ return new Set(values.filter(Boolean) as AppPackageScope[]);
163
+ }
117
164
 
118
- async function handlePackageRequest(c: Context, config: ToolServerConfig) {
119
- const scope = c.req.query('scope') || 'all';
165
+ export async function buildAppPackage(
166
+ config: ToolServerConfig,
167
+ options: BuildAppPackageOptions = {},
168
+ ): Promise<AppPackage> {
120
169
  const pkg: AppPackage = {};
121
170
 
122
- const scopes = new Set<AppPackageScope>(scope.split(',') as AppPackageScope[]);
123
- // TODO build pkg based on the query param scope
171
+ const scopes = normalizeScopes(options.scope);
124
172
  if (scopes.has('all')) {
125
- await builders.tools(pkg, config, c);
126
- await builders.interactions(pkg, config, c);
127
- await builders.types(pkg, config, c);
128
- await builders.templates(pkg, config, c);
129
- await builders.widgets(pkg, config, c);
130
- await builders.ui(pkg, config, c);
131
- await builders.settings(pkg, config, c);
132
- await builders.activities(pkg, config, c);
173
+ await builders.tools(pkg, config, options);
174
+ await builders.interactions(pkg, config, options);
175
+ await builders.types(pkg, config, options);
176
+ await builders.processes(pkg, config, options);
177
+ await builders.templates(pkg, config, options);
178
+ await builders.dashboards(pkg, config, options);
179
+ await builders.widgets(pkg, config, options);
180
+ await builders.ui(pkg, config, options);
181
+ await builders.settings(pkg, config, options);
182
+ await builders.activities(pkg, config, options);
133
183
  } else {
134
184
  if (scopes.has('tools')) {
135
- await builders.tools(pkg, config, c);
185
+ await builders.tools(pkg, config, options);
136
186
  }
137
187
  if (scopes.has('interactions')) {
138
- await builders.interactions(pkg, config, c);
188
+ await builders.interactions(pkg, config, options);
139
189
  }
140
190
  if (scopes.has('types')) {
141
- await builders.types(pkg, config, c);
191
+ await builders.types(pkg, config, options);
192
+ }
193
+ if (scopes.has('processes')) {
194
+ await builders.processes(pkg, config, options);
142
195
  }
143
196
  if (scopes.has('templates')) {
144
- await builders.templates(pkg, config, c);
197
+ await builders.templates(pkg, config, options);
198
+ }
199
+ if (scopes.has('dashboards')) {
200
+ await builders.dashboards(pkg, config, options);
145
201
  }
146
202
  if (scopes.has('widgets')) {
147
- await builders.widgets(pkg, config, c);
203
+ await builders.widgets(pkg, config, options);
148
204
  }
149
205
  if (scopes.has('ui')) {
150
- await builders.ui(pkg, config, c);
206
+ await builders.ui(pkg, config, options);
151
207
  }
152
208
  if (scopes.has('settings')) {
153
- await builders.settings(pkg, config, c);
209
+ await builders.settings(pkg, config, options);
154
210
  }
155
211
  if (scopes.has('activities')) {
156
- await builders.activities(pkg, config, c);
212
+ await builders.activities(pkg, config, options);
157
213
  }
158
214
  }
159
215
 
216
+ return pkg;
217
+ }
218
+
219
+ async function handlePackageRequest(c: Context, config: ToolServerConfig) {
220
+ const pkg = await buildAppPackage(config, {
221
+ scope: c.req.query('scope') || 'all',
222
+ origin: new URL(c.req.url).origin,
223
+ toolUseContext: await getRequestPayload<ToolUseContext>(c),
224
+ });
160
225
  return c.json(pkg);
161
226
  }
162
227
 
163
228
  export function createPackageRoute(app: Hono, basePath: string, config: ToolServerConfig) {
164
-
165
229
  app.get(basePath, (c: Context) => {
166
230
  return handlePackageRequest(c, config);
167
231
  });
@@ -169,5 +233,4 @@ export function createPackageRoute(app: Hono, basePath: string, config: ToolServ
169
233
  app.post(basePath, (c: Context) => {
170
234
  return handlePackageRequest(c, config);
171
235
  });
172
-
173
- }
236
+ }
@@ -0,0 +1,64 @@
1
+ import { Hono } from 'hono';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { ContentTypesCollection } from '../ContentTypesCollection.js';
4
+ import { createContentTypesRoute } from './content-types.js';
5
+ import type { ToolServerConfig } from './types.js';
6
+
7
+ function makeApp(config: ToolServerConfig): Hono {
8
+ const app = new Hono();
9
+ createContentTypesRoute(app, '/api/types', config);
10
+ return app;
11
+ }
12
+
13
+ const config = {
14
+ types: [
15
+ new ContentTypesCollection({
16
+ name: 'contracts',
17
+ types: [{ name: 'contract' }, { name: 'review_note' }],
18
+ }),
19
+ new ContentTypesCollection({
20
+ name: 'audit',
21
+ types: [{ name: 'audit_event' }],
22
+ }),
23
+ ],
24
+ } satisfies ToolServerConfig;
25
+
26
+ describe('content types route', () => {
27
+ it('lists all types with bare ids (collection is not part of the type identity)', async () => {
28
+ const res = await makeApp(config).request('/api/types');
29
+ expect(res.status).toBe(200);
30
+ const body = (await res.json()) as { types: Array<{ id: string }> };
31
+ expect(body.types.map((t) => t.id)).toEqual(['contract', 'review_note', 'audit_event']);
32
+ });
33
+
34
+ it('resolves the canonical bare type name (the app:<app>:<type> ref convention)', async () => {
35
+ const res = await makeApp(config).request('/api/types/contract');
36
+ expect(res.status).toBe(200);
37
+ const body = (await res.json()) as { id: string; name: string };
38
+ expect(body.id).toBe('contract');
39
+ expect(body.name).toBe('contract');
40
+ });
41
+
42
+ it('still resolves the legacy collection:type alias, returning the bare id', async () => {
43
+ const res = await makeApp(config).request('/api/types/contracts:contract');
44
+ expect(res.status).toBe(200);
45
+ const body = (await res.json()) as { id: string };
46
+ expect(body.id).toBe('contract');
47
+ });
48
+
49
+ it('409s on an ambiguous bare name so the collision is loud, not silent', async () => {
50
+ const ambiguous = {
51
+ types: [
52
+ new ContentTypesCollection({ name: 'a', types: [{ name: 'note' }] }),
53
+ new ContentTypesCollection({ name: 'b', types: [{ name: 'note' }] }),
54
+ ],
55
+ } satisfies ToolServerConfig;
56
+ const res = await makeApp(ambiguous).request('/api/types/note');
57
+ expect(res.status).toBe(409);
58
+ });
59
+
60
+ it('404s for an unknown type name', async () => {
61
+ const res = await makeApp(config).request('/api/types/nope');
62
+ expect(res.status).toBe(404);
63
+ });
64
+ });
@@ -1,22 +1,24 @@
1
1
  // ================== Content Type Endpoints ==================
2
2
 
3
- import { InCodeTypeDefinition } from "@vertesia/common";
4
- import { Context, Hono } from "hono";
5
- import { HTTPException } from "hono/http-exception";
6
- import { ContentTypesCollection } from "../ContentTypesCollection.js";
7
- import { ToolServerConfig } from "./types.js";
8
- import { toPathName } from "../utils.js";
3
+ import type { InCodeTypeDefinition } from '@vertesia/common';
4
+ import { type Context, Hono } from 'hono';
5
+ import { HTTPException } from 'hono/http-exception';
6
+ import type { ContentTypesCollection } from '../ContentTypesCollection.js';
7
+ import { toPathName } from '../utils.js';
8
+ import type { ToolServerConfig } from './types.js';
9
9
 
10
10
  export function createContentTypesRoute(app: Hono, basePath: string, config: ToolServerConfig) {
11
11
  const { types = [] } = config;
12
12
 
13
- // GET /api/types - Returns all interactions from all collections
13
+ // GET /api/types - Returns all content types from all collections.
14
+ // A type's public id is its declared BARE name, verbatim (`app:<app>:<type>` once
15
+ // app-prefixed): collections organize code, they are not part of the type identity.
14
16
  app.get(basePath, (c) => {
15
17
  const allTypes: InCodeTypeDefinition[] = [];
16
18
 
17
19
  for (const coll of types) {
18
20
  for (const type of coll.types) {
19
- allTypes.push({ ...type, id: coll.name + ":" + toPathName(type.name) });
21
+ allTypes.push({ ...type, id: type.name });
20
22
  }
21
23
  }
22
24
 
@@ -24,7 +26,7 @@ export function createContentTypesRoute(app: Hono, basePath: string, config: Too
24
26
  title: 'All Content Types',
25
27
  description: 'All available content types across all collections',
26
28
  types: allTypes,
27
- collections: types.map(i => ({
29
+ collections: types.map((i) => ({
28
30
  name: i.name,
29
31
  title: i.title,
30
32
  description: i.description,
@@ -37,53 +39,79 @@ export function createContentTypesRoute(app: Hono, basePath: string, config: Too
37
39
  app.route(`${basePath}/${coll.name}`, createContentTypeEndpoints(coll));
38
40
  }
39
41
 
40
- // GET /api/types/:name - Direct access to content type
42
+ // GET /api/types/:name - Direct access to content type.
43
+ // Canonical form is the BARE type name (`<type>`), matching the portable
44
+ // `app:<app>:<type>` ref convention used by objects.create/search — the
45
+ // collection is an in-code grouping, not part of the type's public id.
46
+ // `<collection>:<type>` is also accepted as a backward-compatible alias.
41
47
  app.get(`${basePath}/:name`, async (c) => {
42
48
  const name = c.req.param('name');
43
49
  const parts = name.split(':');
50
+ if (parts.length === 1) {
51
+ // Match the declared name verbatim; fall back to the toPathName variant for
52
+ // leniency with legacy refs that used the path-mangled form.
53
+ const matches = types.flatMap((coll) =>
54
+ coll.types
55
+ .filter((t) => t.name === name || toPathName(t.name) === name)
56
+ .map((t) => ({ coll, type: t })),
57
+ );
58
+ if (matches.length === 1) {
59
+ return c.json({ ...matches[0].type, id: matches[0].type.name });
60
+ }
61
+ if (matches.length > 1) {
62
+ const colls = matches.map((m) => m.coll.name).join(', ');
63
+ throw new HTTPException(409, {
64
+ message:
65
+ `Ambiguous content type name '${name}': defined in collections ${colls}. ` +
66
+ 'Type names must be unique across collections so the portable ' +
67
+ `'app:<app>:${name}' ref resolves; rename one of them, or address it as '<collection>:<type>'.`,
68
+ });
69
+ }
70
+ throw new HTTPException(404, {
71
+ message: `No content type found with name: ${name}`,
72
+ });
73
+ }
44
74
  if (parts.length !== 2) {
45
75
  throw new HTTPException(400, {
46
- message: "Invalid content type name. Expected format 'collection:type'"
76
+ message: "Invalid content type name. Expected '<type>' or 'collection:type'",
47
77
  });
48
78
  }
49
79
  const collName = parts[0];
50
- const typeName = toPathName(parts[1]);
51
- const ctype = types.find(t => t.name === collName)?.getTypeByName(typeName);
80
+ const typeName = parts[1];
81
+ const ctype = types
82
+ .find((t) => t.name === collName)
83
+ ?.types.find((t) => t.name === typeName || toPathName(t.name) === toPathName(typeName));
52
84
  if (ctype) {
53
- return c.json({ ...ctype, id: collName + ":" + typeName });
85
+ // Alias lookup still returns the canonical bare id.
86
+ return c.json({ ...ctype, id: ctype.name });
54
87
  }
55
88
 
56
89
  throw new HTTPException(404, {
57
- message: "No content type found with name: " + name
90
+ message: `No content type found with name: ${name}`,
58
91
  });
59
92
  });
60
-
61
93
  }
62
94
 
63
-
64
-
65
-
66
95
  function createContentTypeEndpoints(coll: ContentTypesCollection): Hono {
67
96
  const endpoint = new Hono();
68
97
 
69
98
  endpoint.get('/', (c: Context) => {
70
- return c.json(coll.types.map(t => ({ ...t, id: coll.name + ":" + toPathName(t.name) })));
99
+ return c.json(coll.types.map((t) => ({ ...t, id: t.name })));
71
100
  });
72
101
 
73
102
  endpoint.get('/:name', (c: Context) => {
74
103
  const name = c.req.param('name');
75
- const ctype = coll.types.find(t => toPathName(t.name) === name);
104
+ const ctype = coll.types.find((t) => t.name === name || toPathName(t.name) === name);
76
105
  if (!ctype) {
77
106
  throw new HTTPException(404, {
78
- message: "No content type found with name: " + name
107
+ message: `No content type found with name: ${name}`,
79
108
  });
80
109
  }
81
110
  return c.json({
82
111
  ...ctype,
83
- id: coll.name + ":" + toPathName(ctype.name)
112
+ id: ctype.name,
84
113
  });
85
114
  });
86
115
 
87
-
88
116
  return endpoint;
89
- }
117
+ }
@@ -0,0 +1,31 @@
1
+ import type { AppDashboardDefinition } from '@vertesia/common';
2
+ import type { Hono } from 'hono';
3
+ import { HTTPException } from 'hono/http-exception';
4
+ import type { ToolServerConfig } from './types.js';
5
+
6
+ export function createDashboardsRoute(app: Hono, basePath: string, config: ToolServerConfig) {
7
+ const { dashboards = [] } = config;
8
+
9
+ app.get(basePath, (c) => {
10
+ return c.json({
11
+ title: 'All Dashboards',
12
+ description: 'All available dashboard definitions',
13
+ dashboards,
14
+ });
15
+ });
16
+
17
+ app.get(`${basePath}/:id`, (c) => {
18
+ const id = c.req.param('id');
19
+ const dashboard = findDashboard(dashboards, id);
20
+ if (!dashboard) {
21
+ throw new HTTPException(404, {
22
+ message: `No dashboard found with id: ${id}`,
23
+ });
24
+ }
25
+ return c.json(dashboard);
26
+ });
27
+ }
28
+
29
+ function findDashboard(dashboards: AppDashboardDefinition[], id: string): AppDashboardDefinition | undefined {
30
+ return dashboards.find((dashboard) => dashboard.id === id || dashboard.name === id || dashboard.title === id);
31
+ }