@vertesia/tools-sdk 0.81.1 → 1.0.0-dev.20260203.130115Z

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 (101) hide show
  1. package/package.json +9 -8
  2. package/src/ContentTypesCollection.ts +51 -0
  3. package/src/InteractionCollection.ts +1 -94
  4. package/src/SkillCollection.ts +88 -15
  5. package/src/ToolCollection.ts +65 -17
  6. package/src/ToolRegistry.ts +101 -9
  7. package/src/auth.ts +37 -6
  8. package/src/index.ts +7 -3
  9. package/src/server/app-package.ts +102 -0
  10. package/src/server/conyent-types.ts +71 -0
  11. package/src/server/interactions.ts +100 -0
  12. package/src/server/mcp.ts +51 -0
  13. package/src/server/site.ts +53 -0
  14. package/src/server/skills.ts +133 -0
  15. package/src/server/tools.ts +128 -0
  16. package/src/server/types.ts +87 -0
  17. package/src/server/widgets.ts +38 -0
  18. package/src/server.ts +80 -359
  19. package/src/site/styles.ts +71 -0
  20. package/src/site/templates.ts +215 -119
  21. package/src/types.ts +22 -18
  22. package/src/utils.ts +20 -0
  23. package/lib/cjs/InteractionCollection.js +0 -164
  24. package/lib/cjs/InteractionCollection.js.map +0 -1
  25. package/lib/cjs/SkillCollection.js +0 -318
  26. package/lib/cjs/SkillCollection.js.map +0 -1
  27. package/lib/cjs/ToolCollection.js +0 -192
  28. package/lib/cjs/ToolCollection.js.map +0 -1
  29. package/lib/cjs/ToolRegistry.js +0 -44
  30. package/lib/cjs/ToolRegistry.js.map +0 -1
  31. package/lib/cjs/auth.js +0 -89
  32. package/lib/cjs/auth.js.map +0 -1
  33. package/lib/cjs/build/validate.js +0 -7
  34. package/lib/cjs/build/validate.js.map +0 -1
  35. package/lib/cjs/copy-assets.js +0 -84
  36. package/lib/cjs/copy-assets.js.map +0 -1
  37. package/lib/cjs/index.js +0 -30
  38. package/lib/cjs/index.js.map +0 -1
  39. package/lib/cjs/package.json +0 -3
  40. package/lib/cjs/server.js +0 -327
  41. package/lib/cjs/server.js.map +0 -1
  42. package/lib/cjs/site/styles.js +0 -621
  43. package/lib/cjs/site/styles.js.map +0 -1
  44. package/lib/cjs/site/templates.js +0 -932
  45. package/lib/cjs/site/templates.js.map +0 -1
  46. package/lib/cjs/types.js +0 -3
  47. package/lib/cjs/types.js.map +0 -1
  48. package/lib/cjs/utils.js +0 -7
  49. package/lib/cjs/utils.js.map +0 -1
  50. package/lib/esm/InteractionCollection.js +0 -125
  51. package/lib/esm/InteractionCollection.js.map +0 -1
  52. package/lib/esm/SkillCollection.js +0 -311
  53. package/lib/esm/SkillCollection.js.map +0 -1
  54. package/lib/esm/ToolCollection.js +0 -154
  55. package/lib/esm/ToolCollection.js.map +0 -1
  56. package/lib/esm/ToolRegistry.js +0 -39
  57. package/lib/esm/ToolRegistry.js.map +0 -1
  58. package/lib/esm/auth.js +0 -82
  59. package/lib/esm/auth.js.map +0 -1
  60. package/lib/esm/build/validate.js +0 -4
  61. package/lib/esm/build/validate.js.map +0 -1
  62. package/lib/esm/copy-assets.js +0 -81
  63. package/lib/esm/copy-assets.js.map +0 -1
  64. package/lib/esm/index.js +0 -10
  65. package/lib/esm/index.js.map +0 -1
  66. package/lib/esm/server.js +0 -323
  67. package/lib/esm/server.js.map +0 -1
  68. package/lib/esm/site/styles.js +0 -618
  69. package/lib/esm/site/styles.js.map +0 -1
  70. package/lib/esm/site/templates.js +0 -920
  71. package/lib/esm/site/templates.js.map +0 -1
  72. package/lib/esm/types.js +0 -2
  73. package/lib/esm/types.js.map +0 -1
  74. package/lib/esm/utils.js +0 -4
  75. package/lib/esm/utils.js.map +0 -1
  76. package/lib/types/InteractionCollection.d.ts +0 -48
  77. package/lib/types/InteractionCollection.d.ts.map +0 -1
  78. package/lib/types/SkillCollection.d.ts +0 -111
  79. package/lib/types/SkillCollection.d.ts.map +0 -1
  80. package/lib/types/ToolCollection.d.ts +0 -61
  81. package/lib/types/ToolCollection.d.ts.map +0 -1
  82. package/lib/types/ToolRegistry.d.ts +0 -15
  83. package/lib/types/ToolRegistry.d.ts.map +0 -1
  84. package/lib/types/auth.d.ts +0 -20
  85. package/lib/types/auth.d.ts.map +0 -1
  86. package/lib/types/build/validate.d.ts +0 -2
  87. package/lib/types/build/validate.d.ts.map +0 -1
  88. package/lib/types/copy-assets.d.ts +0 -14
  89. package/lib/types/copy-assets.d.ts.map +0 -1
  90. package/lib/types/index.d.ts +0 -10
  91. package/lib/types/index.d.ts.map +0 -1
  92. package/lib/types/server.d.ts +0 -72
  93. package/lib/types/server.d.ts.map +0 -1
  94. package/lib/types/site/styles.d.ts +0 -5
  95. package/lib/types/site/styles.d.ts.map +0 -1
  96. package/lib/types/site/templates.d.ts +0 -54
  97. package/lib/types/site/templates.d.ts.map +0 -1
  98. package/lib/types/types.d.ts +0 -262
  99. package/lib/types/types.d.ts.map +0 -1
  100. package/lib/types/utils.d.ts +0 -2
  101. package/lib/types/utils.d.ts.map +0 -1
package/src/auth.ts CHANGED
@@ -35,7 +35,19 @@ export async function verifyToken(token: string) {
35
35
  }
36
36
 
37
37
 
38
- export async function authorize(ctx: Context) {
38
+ export interface EndpointOverrides {
39
+ studio?: string;
40
+ store?: string;
41
+ token?: string;
42
+ }
43
+
44
+ export interface ToolContext {
45
+ toolName?: string;
46
+ toolUseId?: string;
47
+ runId?: string;
48
+ }
49
+
50
+ export async function authorize(ctx: Context, endpointOverrides?: EndpointOverrides, toolContext?: ToolContext) {
39
51
  const auth = ctx.req.header('Authorization');
40
52
  if (!auth) {
41
53
  throw new HTTPException(401, {
@@ -55,7 +67,7 @@ export async function authorize(ctx: Context) {
55
67
  }
56
68
  try {
57
69
  const { payload } = await verifyToken(value);
58
- const session = new AuthSession(value, payload);
70
+ const session = new AuthSession(value, payload, endpointOverrides, toolContext);
59
71
  ctx.set("auth", session);
60
72
  return session;
61
73
  } catch (err: any) {
@@ -71,17 +83,36 @@ export class AuthSession implements ToolExecutionContext {
71
83
  endpoints: {
72
84
  studio: string;
73
85
  store: string;
86
+ token: string;
74
87
  };
88
+ toolContext?: ToolContext;
75
89
 
76
- constructor(public token: string, public payload: AuthTokenPayload) {
77
- this.endpoints = decodeEndpoints(payload.endpoints) as {
78
- studio: string, store: string
90
+ constructor(
91
+ public token: string,
92
+ public payload: AuthTokenPayload,
93
+ endpointOverrides?: EndpointOverrides,
94
+ toolContext?: ToolContext
95
+ ) {
96
+ const decoded = decodeEndpoints(payload.endpoints);
97
+ // Use overrides from workflow config if provided, falling back to JWT endpoints
98
+ this.endpoints = {
99
+ studio: endpointOverrides?.studio || decoded.studio,
100
+ store: endpointOverrides?.store || decoded.store,
101
+ token: endpointOverrides?.token || decoded.token || payload.iss,
79
102
  };
103
+ this.toolContext = toolContext;
80
104
  }
81
105
 
82
106
  async getClient() {
83
107
  if (!this._client) {
84
- this._client = await VertesiaClient.fromAuthToken(this.token, this.payload);
108
+ const toolInfo = this.toolContext?.toolName ? ` for ${this.toolContext.toolName}` : '';
109
+ console.log(`[VertesiaClient] Initializing client${toolInfo}`, {
110
+ tool: this.toolContext?.toolName,
111
+ toolUseId: this.toolContext?.toolUseId,
112
+ runId: this.toolContext?.runId,
113
+ endpoints: this.endpoints,
114
+ });
115
+ this._client = await VertesiaClient.fromAuthToken(this.token, this.payload, this.endpoints);
85
116
  }
86
117
  return this._client;
87
118
  }
package/src/index.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  export { authorize, AuthSession } from "./auth.js";
2
+ export * from "./ContentTypesCollection.js";
3
+ export { copyRuntimeAssets } from "./copy-assets.js";
2
4
  export * from "./InteractionCollection.js";
5
+ export * from "./server.js";
6
+ export * from "./server/types.js";
7
+ export * from "./site/templates.js";
3
8
  export * from "./SkillCollection.js";
4
9
  export * from "./ToolCollection.js";
5
10
  export * from "./ToolRegistry.js";
6
11
  export * from "./types.js";
7
- export * from "./server.js";
8
- export * from "./site/templates.js";
9
- export { copyRuntimeAssets } from "./copy-assets.js";
12
+
13
+
@@ -0,0 +1,102 @@
1
+ import { AppPackage, AppPackageScope, CatalogInteractionRef, InCodeTypeDefinition } from "@vertesia/common";
2
+ import { Context, Hono } from "hono";
3
+ import { ToolServerConfig } from "./types.js";
4
+
5
+
6
+ const builders: Record<Exclude<AppPackageScope, 'all'>, (pkg: AppPackage, config: ToolServerConfig, c: Context) => void> = {
7
+ tools(pkg: AppPackage, config: ToolServerConfig) {
8
+ const { tools: toolCollections = [], skills: skillCollections = [] } = config;
9
+
10
+ // Aggregate all tools from all collections
11
+ const allTools = toolCollections.flatMap(collection =>
12
+ collection.getToolDefinitions()
13
+ );
14
+
15
+ // same for skills
16
+ const allSkills = skillCollections.flatMap(collection =>
17
+ collection.getToolDefinitions()
18
+ );
19
+
20
+ pkg.tools = allSkills.concat(allTools);
21
+ },
22
+ interactions(pkg: AppPackage, config: ToolServerConfig) {
23
+ const allInteractions: CatalogInteractionRef[] = [];
24
+ for (const coll of (config.interactions || [])) {
25
+ for (const inter of coll.interactions) {
26
+ allInteractions.push({
27
+ type: "app",
28
+ id: coll.name + ":" + inter.name,
29
+ name: inter.name,
30
+ title: inter.title || inter.name,
31
+ description: inter.description,
32
+ tags: inter.tags || [],
33
+ });
34
+ }
35
+ }
36
+ pkg.interactions = allInteractions;
37
+ },
38
+ types(pkg: AppPackage, config: ToolServerConfig) {
39
+ const allTypes: InCodeTypeDefinition[] = [];
40
+ for (const coll of config.types || []) {
41
+ for (const type of coll.types) {
42
+ allTypes.push(type);
43
+ }
44
+ }
45
+ pkg.types = allTypes;
46
+ },
47
+ ui(pkg: AppPackage, config: ToolServerConfig, c: Context) {
48
+ if (config.uiConfig) {
49
+ pkg.ui = { ...config.uiConfig };
50
+ const origin = new URL(c.req.url).origin;
51
+ pkg.ui.src = new URL(pkg.ui.src, origin).toString();
52
+ if (!pkg.ui.isolation) {
53
+ pkg.ui.isolation = "shadow";
54
+ }
55
+ }
56
+ },
57
+ settings(pkg: AppPackage, config: ToolServerConfig) {
58
+ if (config.settings) {
59
+ pkg.settings_schema = { ...config.settings };
60
+ }
61
+ },
62
+ }
63
+
64
+
65
+ export function createPackageRoute(app: Hono, basePath: string, config: ToolServerConfig) {
66
+ const { interactions = [], tools: toolCollections = [], mcpProviders = [] } = config;
67
+
68
+ app.get(basePath, (c: Context) => {
69
+ const scope = c.req.query('scope') || 'all';
70
+ const pkg: AppPackage = {};
71
+ interactions; toolCollections; mcpProviders;
72
+
73
+ const scopes = new Set<AppPackageScope>(scope.split(',') as AppPackageScope[]);
74
+ // TODO build pkg based on the query param scope
75
+ if (scopes.has('all')) {
76
+ builders.tools(pkg, config, c);
77
+ builders.interactions(pkg, config, c);
78
+ builders.types(pkg, config, c);
79
+ builders.ui(pkg, config, c);
80
+ builders.settings(pkg, config, c);
81
+ } else {
82
+ if (scopes.has('tools')) {
83
+ builders.tools(pkg, config, c);
84
+ }
85
+ if (scopes.has('interactions')) {
86
+ builders.interactions(pkg, config, c);
87
+ }
88
+ if (scopes.has('types')) {
89
+ builders.types(pkg, config, c);
90
+ }
91
+ if (scopes.has('ui')) {
92
+ builders.ui(pkg, config, c);
93
+ }
94
+ if (scopes.has('settings')) {
95
+ builders.settings(pkg, config, c);
96
+ }
97
+ }
98
+
99
+ return c.json(pkg);
100
+ });
101
+ }
102
+
@@ -0,0 +1,71 @@
1
+ // ================== Content Type Endpoints ==================
2
+
3
+ import { InCodeTypeDefinition } from "@vertesia/common";
4
+ import { Context, Hono } from "hono";
5
+ import { ContentTypesCollection } from "../ContentTypesCollection.js";
6
+ import { ToolServerConfig } from "./types.js";
7
+ import { HTTPException } from "hono/http-exception";
8
+
9
+ export function createContentTypesRoute(app: Hono, basePath: string, config: ToolServerConfig) {
10
+ const { types = [] } = config;
11
+
12
+ // GET /api/types - Returns all interactions from all collections
13
+ app.get(basePath, (c) => {
14
+ const allTypes: InCodeTypeDefinition[] = [];
15
+
16
+ for (const coll of types) {
17
+ for (const type of coll.types) {
18
+ allTypes.push(type);
19
+ }
20
+ }
21
+
22
+ return c.json({
23
+ title: 'All Content Types',
24
+ description: 'All available content types across all collections',
25
+ types: allTypes,
26
+ collections: types.map(i => ({
27
+ name: i.name,
28
+ title: i.title,
29
+ description: i.description,
30
+ })),
31
+ });
32
+ });
33
+
34
+ // Create interaction collection endpoints
35
+ for (const coll of types) {
36
+ app.route(`${basePath}/${coll.name}`, createContentTypeEndpoints(coll));
37
+ }
38
+
39
+ // GET /api/types/:name - Direct access to content type
40
+ app.get(`${basePath}/:name`, async (c) => {
41
+ const name = c.req.param('name');
42
+
43
+ // Search across all collections for the interaction
44
+ for (const coll of types) {
45
+ const inter = coll.getTypeByName(name);
46
+ if (inter) {
47
+ return c.json(inter);
48
+ }
49
+ }
50
+
51
+ throw new HTTPException(404, {
52
+ message: "No interaction found with name: " + name
53
+ });
54
+ });
55
+
56
+ }
57
+
58
+
59
+
60
+
61
+ function createContentTypeEndpoints(coll: ContentTypesCollection): Hono {
62
+ const endpoint = new Hono();
63
+
64
+ endpoint.get('/', (c: Context) => {
65
+ return c.json(coll.types);
66
+ });
67
+
68
+
69
+
70
+ return endpoint;
71
+ }
@@ -0,0 +1,100 @@
1
+ // ================== Interaction Endpoints ==================
2
+
3
+ import { CatalogInteractionRef } from "@vertesia/common";
4
+ import { Context, Hono } from "hono";
5
+ import { HTTPException } from "hono/http-exception";
6
+ import { authorize } from "../auth.js";
7
+ import { InteractionCollection } from "../InteractionCollection.js";
8
+ import { ToolServerConfig } from "./types.js";
9
+
10
+ export function createInteractionsRoute(app: Hono, basePath: string, config: ToolServerConfig) {
11
+ const { interactions = [] } = config;
12
+
13
+ // GET /api/interactions - Returns all interactions from all collections
14
+ app.get(basePath, (c) => {
15
+ const allInteractions: CatalogInteractionRef[] = [];
16
+
17
+ for (const coll of interactions) {
18
+ for (const inter of coll.interactions) {
19
+ allInteractions.push({
20
+ type: "app",
21
+ id: coll.name + ":" + inter.name,
22
+ name: inter.name,
23
+ title: inter.title || inter.name,
24
+ description: inter.description,
25
+ tags: inter.tags || [],
26
+ });
27
+ }
28
+ }
29
+
30
+ return c.json({
31
+ title: 'All Interactions',
32
+ description: 'All available interactions across all collections',
33
+ interactions: allInteractions,
34
+ collections: interactions.map(i => ({
35
+ name: i.name,
36
+ title: i.title,
37
+ description: i.description,
38
+ })),
39
+ });
40
+ });
41
+
42
+ // Create interaction collection endpoints
43
+ for (const coll of interactions) {
44
+ app.route(`${basePath}/${coll.name}`, createInteractionEndpoints(coll));
45
+ }
46
+
47
+ // GET /api/interactions/:name - Direct access to interaction by name (searches all collections)
48
+ app.get(`${basePath}/:name`, async (c) => {
49
+ await authorize(c);
50
+ const name = c.req.param('name');
51
+
52
+ // Search across all collections for the interaction
53
+ for (const coll of interactions) {
54
+ const inter = coll.getInteractionByName(name);
55
+ if (inter) {
56
+ return c.json(inter);
57
+ }
58
+ }
59
+
60
+ throw new HTTPException(404, {
61
+ message: "No interaction found with name: " + name
62
+ });
63
+ });
64
+
65
+ }
66
+
67
+
68
+
69
+
70
+ function createInteractionEndpoints(coll: InteractionCollection): Hono {
71
+ const endpoint = new Hono();
72
+
73
+ endpoint.get('/', (c: Context) => {
74
+ return c.json(coll.interactions.map(inter => ({
75
+ type: "app",
76
+ id: coll.name + ":" + inter.name,
77
+ name: inter.name,
78
+ title: inter.title || inter.name,
79
+ description: inter.description,
80
+ tags: inter.tags || [],
81
+ } satisfies CatalogInteractionRef)));
82
+ });
83
+
84
+ endpoint.get('/:name', async (c: Context) => {
85
+ await authorize(c);
86
+ const name = c.req.param('name');
87
+ const inter = coll.getInteractionByName(name);
88
+ if (!inter) {
89
+ throw new HTTPException(404, {
90
+ message: "No interaction found with name: " + name
91
+ });
92
+ }
93
+ return c.json({
94
+ ...inter,
95
+ id: coll.name + ":" + inter.name,
96
+ });
97
+ });
98
+
99
+ return endpoint;
100
+ }
@@ -0,0 +1,51 @@
1
+ // ================== MCP Endpoints ==================
2
+
3
+ import { Context, Hono } from "hono";
4
+ import { HTTPException } from "hono/http-exception";
5
+ import { authorize } from "../auth.js";
6
+ import { ToolServerConfig } from "../index.js";
7
+ import { MCPProviderConfig } from "./types.js";
8
+
9
+
10
+
11
+ export function createMcpRoute(app: Hono, basePath: string, config: ToolServerConfig) {
12
+ const { mcpProviders = [] } = config;
13
+ // Create MCP provider endpoints
14
+ if (mcpProviders.length > 0) {
15
+ app.route(basePath, createMCPEndpoints(mcpProviders));
16
+ }
17
+
18
+ }
19
+
20
+ function createMCPEndpoints(providers: MCPProviderConfig[]): Hono {
21
+ const endpoint = new Hono();
22
+
23
+ for (const p of providers) {
24
+ endpoint.post(`/${p.name}`, async (c: Context) => {
25
+ const session = await authorize(c);
26
+ const config = await readJsonBody(c);
27
+ const info = await p.createMCPConnection(session, config);
28
+ return c.json(info);
29
+ });
30
+
31
+ endpoint.get(`/${p.name}`, (c: Context) => c.json({
32
+ name: p.name,
33
+ description: p.description,
34
+ }));
35
+ }
36
+
37
+ return endpoint;
38
+ }
39
+
40
+ async function readJsonBody(ctx: Context): Promise<Record<string, any>> {
41
+ try {
42
+ const text = await ctx.req.text();
43
+ const jsonContent = text?.trim() || '';
44
+ if (!jsonContent) return {};
45
+ return JSON.parse(jsonContent) as Record<string, any>;
46
+ } catch (err: any) {
47
+ throw new HTTPException(400, {
48
+ message: "Failed to parse JSON body: " + err.message
49
+ });
50
+ }
51
+ }
@@ -0,0 +1,53 @@
1
+ import { Hono } from "hono";
2
+ import {
3
+ contentTypeCollectionPage,
4
+ indexPage,
5
+ interactionCollectionPage,
6
+ skillCollectionPage,
7
+ toolCollectionPage
8
+ } from "../site/templates.js";
9
+ import { ToolServerConfig } from "./types.js";
10
+
11
+
12
+ export function createSiteRoute(app: Hono, basePath: string, config: ToolServerConfig) {
13
+ const {
14
+ tools = [],
15
+ interactions = [],
16
+ types = [],
17
+ skills = [],
18
+ } = config;
19
+
20
+ // Index page
21
+ app.get(`${basePath}/`, (c) => {
22
+ return c.html(indexPage(config));
23
+ });
24
+
25
+ // Tool collection pages
26
+ for (const coll of tools) {
27
+ app.get(`${basePath}/tools/${coll.name}`, (c) => {
28
+ return c.html(toolCollectionPage(coll));
29
+ });
30
+ }
31
+
32
+ // Skill collection pages
33
+ for (const coll of skills) {
34
+ app.get(`${basePath}/skills/${coll.name}`, (c) => {
35
+ return c.html(skillCollectionPage(coll));
36
+ });
37
+ }
38
+
39
+ // Interaction collection pages
40
+ for (const coll of interactions) {
41
+ app.get(`${basePath}/interactions/${coll.name}`, (c) => {
42
+ return c.html(interactionCollectionPage(coll));
43
+ });
44
+ }
45
+
46
+ // Content type collection pages
47
+ for (const coll of types) {
48
+ app.get(`${basePath}/types/${coll.name}`, (c) => {
49
+ return c.html(contentTypeCollectionPage(coll));
50
+ });
51
+ }
52
+
53
+ }
@@ -0,0 +1,133 @@
1
+ // ================== Skill Endpoints ==================
2
+
3
+ import { Context, Hono } from "hono";
4
+ import { HTTPException } from "hono/http-exception";
5
+ import { SkillCollection } from "../SkillCollection.js";
6
+ import { SkillDefinition, ToolCollectionDefinition, ToolDefinition } from "../types.js";
7
+ import { makeScriptUrl } from "../utils.js";
8
+ import { ToolContext, ToolServerConfig } from "./types.js";
9
+
10
+ export function createSkillsRoute(app: Hono, basePath: string, config: ToolServerConfig) {
11
+ const { skills = [] } = config;
12
+
13
+ // Build a map of skill name -> collection for routing
14
+ const skillToCollection = new Map<string, SkillCollection>();
15
+ for (const coll of skills) {
16
+ for (const skill of coll.getSkillDefinitions()) {
17
+ skillToCollection.set(skill.name, coll);
18
+ // Also map the learn_ prefixed name
19
+ skillToCollection.set(`learn_${skill.name}`, coll);
20
+ }
21
+ }
22
+
23
+ // GET /api/skills - Returns all skills from all collections
24
+ app.get(basePath, (c) => {
25
+ const url = new URL(c.req.url);
26
+ const allSkills: ToolDefinition[] = [];
27
+
28
+ for (const coll of skills) {
29
+ allSkills.push(...coll.getToolDefinitions());
30
+ }
31
+
32
+ return c.json({
33
+ src: `${url.origin}${url.pathname}`,
34
+ title: 'All Skills',
35
+ description: 'All available skills across all collections',
36
+ tools: allSkills,
37
+ collections: skills.map(s => ({
38
+ name: s.name,
39
+ title: s.title,
40
+ description: s.description,
41
+ })),
42
+ } satisfies ToolCollectionDefinition & { collections: any[] });
43
+ });
44
+
45
+ // POST /api/skills - Route to the correct collection based on tool_name
46
+ app.post(basePath, async (c) => {
47
+ const ctx = c as unknown as ToolContext;
48
+
49
+ // Payload is already parsed and validated by middleware
50
+ if (!ctx.payload) {
51
+ throw new HTTPException(400, {
52
+ message: 'Invalid or missing skill execution payload. Expected { tool_use: { id, tool_name, tool_input? }, metadata? }'
53
+ });
54
+ }
55
+
56
+ const toolName = ctx.payload.tool_use.tool_name;
57
+
58
+ // Find the collection for this skill
59
+ const collection = skillToCollection.get(toolName);
60
+ if (!collection) {
61
+ // Extract skill name for better error message
62
+ const skillName = toolName.startsWith('learn_') ? toolName.slice(6) : toolName;
63
+ throw new HTTPException(404, {
64
+ message: `Skill not found: ${skillName}. Available skills: ${Array.from(skillToCollection.keys()).filter(k => !k.startsWith('learn_')).join(', ')}`
65
+ });
66
+ }
67
+
68
+ // Delegate to the collection's execute method with pre-parsed payload
69
+ return collection.execute(c, ctx.payload);
70
+ });
71
+
72
+ // Create skill collection endpoints (exposed as tools)
73
+ for (const coll of skills) {
74
+ app.route(`${basePath}/${coll.name}`, createSkillEndpoints(coll));
75
+ }
76
+
77
+ }
78
+
79
+ function createSkillEndpoints(coll: SkillCollection): Hono {
80
+ const endpoint = new Hono();
81
+
82
+ // List skills as tool definitions (tool collection format)
83
+ // This allows skills to be used exactly like tools
84
+ endpoint.get('/', (c: Context) => {
85
+ const url = new URL(c.req.url);
86
+ return c.json({
87
+ src: `${url.origin}${url.pathname}`,
88
+ title: coll.title || coll.name,
89
+ description: coll.description || '',
90
+ tools: coll.getToolDefinitions()
91
+ } satisfies ToolCollectionDefinition);
92
+ });
93
+
94
+ // Get scripts for a specific skill
95
+ // Returns all scripts bundled with the skill
96
+ endpoint.get('/:name/scripts', (c: Context) => {
97
+ const name = c.req.param('name');
98
+ const skillName = name.startsWith('learn_') ? name.slice(6) : name;
99
+ const skill = coll.getSkill(skillName);
100
+ if (!skill) {
101
+ throw new HTTPException(404, {
102
+ message: `Skill not found: ${skillName}`
103
+ });
104
+ }
105
+ const url = new URL(c.req.url);
106
+ return c.json({
107
+ skill_name: skill.name,
108
+ scripts: skill.scripts ? skill.scripts.map(s => makeScriptUrl(url.origin, s)) : []
109
+ });
110
+ });
111
+
112
+
113
+ // Get a specific skill by name
114
+ endpoint.get('/:name', (c: Context) => {
115
+ const name = c.req.param('name');
116
+ // Handle both "learn_name" and "name" formats
117
+ const skillName = name.startsWith('learn_') ? name.slice(6) : name;
118
+ const skill = coll.getSkill(skillName);
119
+ if (!skill) {
120
+ throw new HTTPException(404, {
121
+ message: `Skill not found: ${skillName}`
122
+ });
123
+ }
124
+ return c.json(skill satisfies SkillDefinition);
125
+ });
126
+
127
+ // Execute skill (standard tool execution format)
128
+ endpoint.post('/', (c: Context) => {
129
+ return coll.execute(c);
130
+ });
131
+
132
+ return endpoint;
133
+ }