@soederpop/luca 0.0.2

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 (358) hide show
  1. package/CLAUDE.md +71 -0
  2. package/README.md +78 -0
  3. package/bun.lock +2928 -0
  4. package/bunfig.toml +3 -0
  5. package/commands/audit-docs.ts +740 -0
  6. package/commands/build-scaffolds.ts +154 -0
  7. package/commands/generate-api-docs.ts +114 -0
  8. package/commands/update-introspection.ts +67 -0
  9. package/docs/CLI.md +335 -0
  10. package/docs/README.md +88 -0
  11. package/docs/TABLE-OF-CONTENTS.md +157 -0
  12. package/docs/apis/clients/elevenlabs.md +84 -0
  13. package/docs/apis/clients/graph.md +56 -0
  14. package/docs/apis/clients/openai.md +69 -0
  15. package/docs/apis/clients/rest.md +41 -0
  16. package/docs/apis/clients/websocket.md +107 -0
  17. package/docs/apis/features/agi/assistant.md +471 -0
  18. package/docs/apis/features/agi/assistants-manager.md +154 -0
  19. package/docs/apis/features/agi/claude-code.md +602 -0
  20. package/docs/apis/features/agi/conversation-history.md +352 -0
  21. package/docs/apis/features/agi/conversation.md +333 -0
  22. package/docs/apis/features/agi/docs-reader.md +121 -0
  23. package/docs/apis/features/agi/openai-codex.md +318 -0
  24. package/docs/apis/features/agi/openapi.md +138 -0
  25. package/docs/apis/features/agi/semantic-search.md +387 -0
  26. package/docs/apis/features/agi/skills-library.md +216 -0
  27. package/docs/apis/features/node/container-link.md +133 -0
  28. package/docs/apis/features/node/content-db.md +313 -0
  29. package/docs/apis/features/node/disk-cache.md +379 -0
  30. package/docs/apis/features/node/dns.md +651 -0
  31. package/docs/apis/features/node/docker.md +705 -0
  32. package/docs/apis/features/node/downloader.md +81 -0
  33. package/docs/apis/features/node/esbuild.md +59 -0
  34. package/docs/apis/features/node/file-manager.md +182 -0
  35. package/docs/apis/features/node/fs.md +581 -0
  36. package/docs/apis/features/node/git.md +330 -0
  37. package/docs/apis/features/node/google-auth.md +174 -0
  38. package/docs/apis/features/node/google-calendar.md +187 -0
  39. package/docs/apis/features/node/google-docs.md +151 -0
  40. package/docs/apis/features/node/google-drive.md +225 -0
  41. package/docs/apis/features/node/google-sheets.md +179 -0
  42. package/docs/apis/features/node/grep.md +290 -0
  43. package/docs/apis/features/node/helpers.md +135 -0
  44. package/docs/apis/features/node/ink.md +334 -0
  45. package/docs/apis/features/node/ipc-socket.md +260 -0
  46. package/docs/apis/features/node/json-tree.md +86 -0
  47. package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
  48. package/docs/apis/features/node/networking.md +281 -0
  49. package/docs/apis/features/node/nlp.md +133 -0
  50. package/docs/apis/features/node/opener.md +97 -0
  51. package/docs/apis/features/node/os.md +118 -0
  52. package/docs/apis/features/node/package-finder.md +402 -0
  53. package/docs/apis/features/node/postgres.md +212 -0
  54. package/docs/apis/features/node/proc.md +430 -0
  55. package/docs/apis/features/node/process-manager.md +210 -0
  56. package/docs/apis/features/node/python.md +278 -0
  57. package/docs/apis/features/node/repl.md +88 -0
  58. package/docs/apis/features/node/runpod.md +673 -0
  59. package/docs/apis/features/node/secure-shell.md +169 -0
  60. package/docs/apis/features/node/semantic-search.md +401 -0
  61. package/docs/apis/features/node/sqlite.md +211 -0
  62. package/docs/apis/features/node/telegram.md +254 -0
  63. package/docs/apis/features/node/tts.md +118 -0
  64. package/docs/apis/features/node/ui.md +703 -0
  65. package/docs/apis/features/node/vault.md +64 -0
  66. package/docs/apis/features/node/vm.md +84 -0
  67. package/docs/apis/features/node/window-manager.md +337 -0
  68. package/docs/apis/features/node/yaml-tree.md +85 -0
  69. package/docs/apis/features/node/yaml.md +176 -0
  70. package/docs/apis/features/web/asset-loader.md +47 -0
  71. package/docs/apis/features/web/container-link.md +133 -0
  72. package/docs/apis/features/web/esbuild.md +59 -0
  73. package/docs/apis/features/web/helpers.md +135 -0
  74. package/docs/apis/features/web/network.md +30 -0
  75. package/docs/apis/features/web/speech.md +55 -0
  76. package/docs/apis/features/web/vault.md +64 -0
  77. package/docs/apis/features/web/vm.md +84 -0
  78. package/docs/apis/features/web/voice.md +67 -0
  79. package/docs/apis/servers/express.md +127 -0
  80. package/docs/apis/servers/mcp.md +213 -0
  81. package/docs/apis/servers/websocket.md +99 -0
  82. package/docs/documentation-audit.md +134 -0
  83. package/docs/examples/content-db.md +77 -0
  84. package/docs/examples/disk-cache.md +83 -0
  85. package/docs/examples/docker.md +101 -0
  86. package/docs/examples/downloader.md +70 -0
  87. package/docs/examples/esbuild.md +80 -0
  88. package/docs/examples/file-manager.md +82 -0
  89. package/docs/examples/fs.md +83 -0
  90. package/docs/examples/git.md +85 -0
  91. package/docs/examples/google-auth.md +88 -0
  92. package/docs/examples/google-calendar.md +94 -0
  93. package/docs/examples/google-docs.md +82 -0
  94. package/docs/examples/google-drive.md +96 -0
  95. package/docs/examples/google-sheets.md +95 -0
  96. package/docs/examples/grep.md +85 -0
  97. package/docs/examples/ink-blocks.md +75 -0
  98. package/docs/examples/ink-renderer.md +41 -0
  99. package/docs/examples/ink.md +103 -0
  100. package/docs/examples/ipc-socket.md +103 -0
  101. package/docs/examples/json-tree.md +91 -0
  102. package/docs/examples/launcher-app-command-listener.md +120 -0
  103. package/docs/examples/networking.md +58 -0
  104. package/docs/examples/nlp.md +91 -0
  105. package/docs/examples/opener.md +78 -0
  106. package/docs/examples/os.md +72 -0
  107. package/docs/examples/package-finder.md +89 -0
  108. package/docs/examples/port-exposer.md +89 -0
  109. package/docs/examples/postgres.md +91 -0
  110. package/docs/examples/proc.md +81 -0
  111. package/docs/examples/process-manager.md +79 -0
  112. package/docs/examples/python.md +91 -0
  113. package/docs/examples/repl.md +93 -0
  114. package/docs/examples/runpod.md +119 -0
  115. package/docs/examples/secure-shell.md +92 -0
  116. package/docs/examples/sqlite.md +86 -0
  117. package/docs/examples/telegram.md +77 -0
  118. package/docs/examples/tts.md +86 -0
  119. package/docs/examples/ui.md +80 -0
  120. package/docs/examples/vault.md +70 -0
  121. package/docs/examples/vm.md +86 -0
  122. package/docs/examples/window-manager.md +125 -0
  123. package/docs/examples/yaml-tree.md +93 -0
  124. package/docs/examples/yaml.md +104 -0
  125. package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
  126. package/docs/ideas/container-use-api.md +9 -0
  127. package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
  128. package/docs/ideas/feature-stacks.md +22 -0
  129. package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
  130. package/docs/ideas/mcp-design.md +9 -0
  131. package/docs/ideas/web-container-debugging-feature.md +13 -0
  132. package/docs/introspection-audit.md +49 -0
  133. package/docs/introspection.md +154 -0
  134. package/docs/mcp/readme.md +162 -0
  135. package/docs/models.ts +38 -0
  136. package/docs/philosophy.md +85 -0
  137. package/docs/principles.md +7 -0
  138. package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
  139. package/docs/prompts/mcp-test-easy-command.md +27 -0
  140. package/docs/reports/assistant-bugs.md +38 -0
  141. package/docs/reports/attach-pattern-usage.md +18 -0
  142. package/docs/reports/code-audit-results.md +391 -0
  143. package/docs/reports/introspection-audit-tasks.md +378 -0
  144. package/docs/reports/luca-mcp-improvements.md +128 -0
  145. package/docs/scaffolds/client.md +140 -0
  146. package/docs/scaffolds/command.md +106 -0
  147. package/docs/scaffolds/endpoint.md +176 -0
  148. package/docs/scaffolds/feature.md +148 -0
  149. package/docs/scaffolds/server.md +187 -0
  150. package/docs/tasks/web-container-helper-discovery.md +71 -0
  151. package/docs/todos.md +1 -0
  152. package/docs/tutorials/01-getting-started.md +106 -0
  153. package/docs/tutorials/02-container.md +210 -0
  154. package/docs/tutorials/03-scripts.md +194 -0
  155. package/docs/tutorials/04-features-overview.md +196 -0
  156. package/docs/tutorials/05-state-and-events.md +171 -0
  157. package/docs/tutorials/06-servers.md +157 -0
  158. package/docs/tutorials/07-endpoints.md +198 -0
  159. package/docs/tutorials/08-commands.md +171 -0
  160. package/docs/tutorials/09-clients.md +162 -0
  161. package/docs/tutorials/10-creating-features.md +198 -0
  162. package/docs/tutorials/11-contentbase.md +191 -0
  163. package/docs/tutorials/12-assistants.md +215 -0
  164. package/docs/tutorials/13-introspection.md +147 -0
  165. package/docs/tutorials/14-type-system.md +174 -0
  166. package/docs/tutorials/15-project-patterns.md +222 -0
  167. package/docs/tutorials/16-google-features.md +534 -0
  168. package/docs/tutorials/17-tui-blocks.md +530 -0
  169. package/docs/tutorials/18-semantic-search.md +334 -0
  170. package/index.ts +1 -0
  171. package/luca.console.ts +9 -0
  172. package/main.py +6 -0
  173. package/package.json +154 -0
  174. package/pyproject.toml +7 -0
  175. package/scripts/animations/chrome-glitch.ts +55 -0
  176. package/scripts/animations/index.ts +16 -0
  177. package/scripts/animations/neon-pulse.ts +64 -0
  178. package/scripts/animations/types.ts +6 -0
  179. package/scripts/build-web.ts +28 -0
  180. package/scripts/examples/ask-luca-expert.ts +42 -0
  181. package/scripts/examples/assistant-questions.ts +12 -0
  182. package/scripts/examples/excalidraw-expert.ts +75 -0
  183. package/scripts/examples/expert-chat.ts +0 -0
  184. package/scripts/examples/file-manager.ts +14 -0
  185. package/scripts/examples/ideas.ts +12 -0
  186. package/scripts/examples/interactive-chat.ts +20 -0
  187. package/scripts/examples/openai-tool-calls.ts +113 -0
  188. package/scripts/examples/opening-a-web-browser.ts +5 -0
  189. package/scripts/examples/telegram-bot.ts +79 -0
  190. package/scripts/examples/telegram-ink-ui.ts +302 -0
  191. package/scripts/examples/using-assistant-with-mcp.ts +560 -0
  192. package/scripts/examples/using-claude-code.ts +10 -0
  193. package/scripts/examples/using-contentdb.ts +35 -0
  194. package/scripts/examples/using-conversations.ts +35 -0
  195. package/scripts/examples/using-disk-cache.ts +10 -0
  196. package/scripts/examples/using-docker-shell.ts +75 -0
  197. package/scripts/examples/using-elevenlabs.ts +25 -0
  198. package/scripts/examples/using-google-calendar.ts +57 -0
  199. package/scripts/examples/using-google-docs.ts +74 -0
  200. package/scripts/examples/using-google-drive.ts +74 -0
  201. package/scripts/examples/using-google-sheets.ts +89 -0
  202. package/scripts/examples/using-nlp.ts +55 -0
  203. package/scripts/examples/using-ollama.ts +10 -0
  204. package/scripts/examples/using-openai-codex.ts +23 -0
  205. package/scripts/examples/using-postgres.ts +55 -0
  206. package/scripts/examples/using-runpod.ts +32 -0
  207. package/scripts/examples/using-tts.ts +40 -0
  208. package/scripts/examples/vm-loading-esm-modules.ts +16 -0
  209. package/scripts/scaffold.ts +391 -0
  210. package/scripts/scratch.ts +15 -0
  211. package/scripts/test-command-listener.ts +123 -0
  212. package/scripts/test-window-manager-lifecycle.ts +86 -0
  213. package/scripts/test-window-manager.ts +43 -0
  214. package/scripts/update-introspection-data.ts +58 -0
  215. package/src/agi/README.md +14 -0
  216. package/src/agi/container.server.ts +114 -0
  217. package/src/agi/endpoints/ask.ts +60 -0
  218. package/src/agi/endpoints/conversations/[id].ts +45 -0
  219. package/src/agi/endpoints/conversations.ts +31 -0
  220. package/src/agi/endpoints/experts.ts +37 -0
  221. package/src/agi/features/assistant.ts +767 -0
  222. package/src/agi/features/assistants-manager.ts +260 -0
  223. package/src/agi/features/claude-code.ts +1111 -0
  224. package/src/agi/features/conversation-history.ts +497 -0
  225. package/src/agi/features/conversation.ts +799 -0
  226. package/src/agi/features/openai-codex.ts +631 -0
  227. package/src/agi/features/openapi.ts +438 -0
  228. package/src/agi/features/skills-library.ts +425 -0
  229. package/src/agi/index.ts +6 -0
  230. package/src/agi/lib/token-counter.ts +122 -0
  231. package/src/browser.ts +25 -0
  232. package/src/bus.ts +100 -0
  233. package/src/cli/cli.ts +70 -0
  234. package/src/client.ts +461 -0
  235. package/src/clients/civitai/index.ts +541 -0
  236. package/src/clients/client-template.ts +41 -0
  237. package/src/clients/comfyui/index.ts +597 -0
  238. package/src/clients/elevenlabs/index.ts +291 -0
  239. package/src/clients/openai/index.ts +451 -0
  240. package/src/clients/supabase/index.ts +366 -0
  241. package/src/command.ts +164 -0
  242. package/src/commands/chat.ts +182 -0
  243. package/src/commands/console.ts +192 -0
  244. package/src/commands/describe.ts +433 -0
  245. package/src/commands/eval.ts +116 -0
  246. package/src/commands/help.ts +214 -0
  247. package/src/commands/index.ts +14 -0
  248. package/src/commands/mcp.ts +64 -0
  249. package/src/commands/prompt.ts +807 -0
  250. package/src/commands/run.ts +257 -0
  251. package/src/commands/sandbox-mcp.ts +439 -0
  252. package/src/commands/scaffold.ts +79 -0
  253. package/src/commands/serve.ts +172 -0
  254. package/src/container.ts +781 -0
  255. package/src/endpoint.ts +340 -0
  256. package/src/feature.ts +75 -0
  257. package/src/hash-object.ts +97 -0
  258. package/src/helper.ts +543 -0
  259. package/src/introspection/generated.agi.ts +23388 -0
  260. package/src/introspection/generated.node.ts +18899 -0
  261. package/src/introspection/generated.web.ts +2021 -0
  262. package/src/introspection/index.ts +256 -0
  263. package/src/introspection/scan.ts +912 -0
  264. package/src/node/container.ts +354 -0
  265. package/src/node/feature.ts +13 -0
  266. package/src/node/features/container-link.ts +558 -0
  267. package/src/node/features/content-db.ts +475 -0
  268. package/src/node/features/disk-cache.ts +382 -0
  269. package/src/node/features/dns.ts +655 -0
  270. package/src/node/features/docker.ts +912 -0
  271. package/src/node/features/downloader.ts +92 -0
  272. package/src/node/features/esbuild.ts +68 -0
  273. package/src/node/features/file-manager.ts +357 -0
  274. package/src/node/features/fs.ts +534 -0
  275. package/src/node/features/git.ts +492 -0
  276. package/src/node/features/google-auth.ts +502 -0
  277. package/src/node/features/google-calendar.ts +300 -0
  278. package/src/node/features/google-docs.ts +404 -0
  279. package/src/node/features/google-drive.ts +339 -0
  280. package/src/node/features/google-sheets.ts +279 -0
  281. package/src/node/features/grep.ts +406 -0
  282. package/src/node/features/helpers.ts +374 -0
  283. package/src/node/features/ink.ts +490 -0
  284. package/src/node/features/ipc-socket.ts +459 -0
  285. package/src/node/features/json-tree.ts +188 -0
  286. package/src/node/features/launcher-app-command-listener.ts +388 -0
  287. package/src/node/features/networking.ts +925 -0
  288. package/src/node/features/nlp.ts +211 -0
  289. package/src/node/features/opener.ts +166 -0
  290. package/src/node/features/os.ts +157 -0
  291. package/src/node/features/package-finder.ts +539 -0
  292. package/src/node/features/port-exposer.ts +342 -0
  293. package/src/node/features/postgres.ts +273 -0
  294. package/src/node/features/proc.ts +502 -0
  295. package/src/node/features/process-manager.ts +542 -0
  296. package/src/node/features/python.ts +444 -0
  297. package/src/node/features/repl.ts +194 -0
  298. package/src/node/features/runpod.ts +802 -0
  299. package/src/node/features/secure-shell.ts +248 -0
  300. package/src/node/features/semantic-search.ts +924 -0
  301. package/src/node/features/sqlite.ts +289 -0
  302. package/src/node/features/telegram.ts +342 -0
  303. package/src/node/features/tts.ts +184 -0
  304. package/src/node/features/ui.ts +857 -0
  305. package/src/node/features/vault.ts +164 -0
  306. package/src/node/features/vm.ts +312 -0
  307. package/src/node/features/window-manager.ts +804 -0
  308. package/src/node/features/yaml-tree.ts +149 -0
  309. package/src/node/features/yaml.ts +132 -0
  310. package/src/node.ts +70 -0
  311. package/src/react/index.ts +175 -0
  312. package/src/registry.ts +199 -0
  313. package/src/scaffolds/generated.ts +1613 -0
  314. package/src/scaffolds/template.ts +37 -0
  315. package/src/schemas/base.ts +255 -0
  316. package/src/server.ts +135 -0
  317. package/src/servers/express.ts +209 -0
  318. package/src/servers/mcp.ts +805 -0
  319. package/src/servers/socket.ts +120 -0
  320. package/src/state.ts +101 -0
  321. package/src/web/clients/socket.ts +82 -0
  322. package/src/web/container.ts +74 -0
  323. package/src/web/extension.ts +30 -0
  324. package/src/web/feature.ts +12 -0
  325. package/src/web/features/asset-loader.ts +64 -0
  326. package/src/web/features/container-link.ts +385 -0
  327. package/src/web/features/esbuild.ts +79 -0
  328. package/src/web/features/helpers.ts +267 -0
  329. package/src/web/features/network.ts +61 -0
  330. package/src/web/features/speech.ts +87 -0
  331. package/src/web/features/vault.ts +189 -0
  332. package/src/web/features/vm.ts +78 -0
  333. package/src/web/features/voice-recognition.ts +129 -0
  334. package/src/web/shims/isomorphic-vm.ts +149 -0
  335. package/test/bus.test.ts +134 -0
  336. package/test/clients-servers.test.ts +216 -0
  337. package/test/container-link.test.ts +274 -0
  338. package/test/features.test.ts +160 -0
  339. package/test/integration.test.ts +787 -0
  340. package/test/node-container.test.ts +121 -0
  341. package/test/rate-limit.test.ts +272 -0
  342. package/test/semantic-search.test.ts +550 -0
  343. package/test/state.test.ts +121 -0
  344. package/test-integration/assistant.test.ts +138 -0
  345. package/test-integration/assistants-manager.test.ts +123 -0
  346. package/test-integration/claude-code.test.ts +98 -0
  347. package/test-integration/conversation-history.test.ts +205 -0
  348. package/test-integration/conversation.test.ts +137 -0
  349. package/test-integration/elevenlabs.test.ts +55 -0
  350. package/test-integration/google-services.test.ts +80 -0
  351. package/test-integration/helpers.ts +89 -0
  352. package/test-integration/openai-codex.test.ts +93 -0
  353. package/test-integration/runpod.test.ts +58 -0
  354. package/test-integration/server-endpoints.test.ts +97 -0
  355. package/test-integration/skills-library.test.ts +157 -0
  356. package/test-integration/telegram.test.ts +46 -0
  357. package/tsconfig.json +58 -0
  358. package/uv.lock +8 -0
@@ -0,0 +1,438 @@
1
+ import { Feature, features } from '../../feature.js'
2
+ import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
3
+ import { z } from 'zod'
4
+ import { camelCase } from 'lodash-es'
5
+
6
+ declare module '../../feature.js' {
7
+ interface AvailableFeatures {
8
+ openapi: typeof OpenAPI
9
+ }
10
+ }
11
+
12
+ export const OpenAPIStateSchema = FeatureStateSchema.extend({
13
+ loaded: z.boolean().default(false).describe('Whether the OpenAPI spec has been fetched and parsed'),
14
+ title: z.string().default('').describe('The API title from the spec info block'),
15
+ version: z.string().default('').describe('The API version from the spec info block'),
16
+ endpointCount: z.number().default(0).describe('Number of parsed endpoints in the spec'),
17
+ })
18
+
19
+ export const OpenAPIOptionsSchema = FeatureOptionsSchema.extend({
20
+ url: z.string().optional().describe('URL to the OpenAPI/Swagger spec or the API server base URL')
21
+ })
22
+
23
+ export type OpenAPIOptions = z.infer<typeof OpenAPIOptionsSchema>
24
+ export type OpenAPIState = z.infer<typeof OpenAPIStateSchema>
25
+
26
+
27
+
28
+ export interface EndpointInfo {
29
+ /** Human-friendly camelCase name derived from operationId */
30
+ name: string
31
+ /** Original operationId from the spec */
32
+ operationId: string
33
+ /** HTTP method (get, post, put, delete, patch, etc.) */
34
+ method: string
35
+ /** URL path template, e.g. /pets/{petId} */
36
+ path: string
37
+ /** Summary from the spec */
38
+ summary: string
39
+ /** Longer description from the spec */
40
+ description: string
41
+ /** Tags for grouping */
42
+ tags: string[]
43
+ /** Parameter definitions from the spec */
44
+ parameters: OpenAPIParameter[]
45
+ /** Request body schema if present */
46
+ requestBody: any
47
+ /** Response definitions keyed by status code */
48
+ responses: Record<string, any>
49
+ /** Whether the endpoint is deprecated */
50
+ deprecated: boolean
51
+ }
52
+
53
+ export interface OpenAPIParameter {
54
+ name: string
55
+ in: 'query' | 'path' | 'header' | 'cookie'
56
+ description: string
57
+ required: boolean
58
+ schema: any
59
+ }
60
+
61
+ export interface OpenAIFunctionDef {
62
+ name: string
63
+ description: string
64
+ parameters: {
65
+ type: 'object'
66
+ properties: Record<string, any>
67
+ required: string[]
68
+ }
69
+ }
70
+
71
+ export interface OpenAIToolDef {
72
+ type: 'function'
73
+ function: OpenAIFunctionDef
74
+ }
75
+
76
+ /**
77
+ * The OpenAPI feature loads an OpenAPI/Swagger spec from a URL and provides
78
+ * inspection and conversion utilities.
79
+ *
80
+ * Works in both browser and node environments since it uses fetch.
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const api = container.feature('openapi', { url: 'https://petstore.swagger.io/v2' })
85
+ * await api.load()
86
+ *
87
+ * // Inspect all endpoints
88
+ * api.endpoints
89
+ *
90
+ * // Get a single endpoint by its friendly name
91
+ * api.endpoint('getPetById')
92
+ *
93
+ * // Convert to OpenAI tool definitions
94
+ * api.toTools()
95
+ *
96
+ * // Convert a single endpoint to a function definition
97
+ * api.toFunction('getPetById')
98
+ * ```
99
+ */
100
+ export class OpenAPI extends Feature<OpenAPIState, OpenAPIOptions> {
101
+ static override shortcut = 'features.openapi' as const
102
+ static override description = 'Load and inspect OpenAPI specs, convert endpoints to OpenAI tool/function definitions'
103
+ static override stateSchema = OpenAPIStateSchema
104
+ static override optionsSchema = OpenAPIOptionsSchema
105
+
106
+ /** Raw parsed spec document */
107
+ private _spec: any = null
108
+
109
+ /** Parsed endpoint map keyed by friendly name */
110
+ private _endpoints: Map<string, EndpointInfo> = new Map()
111
+
112
+ /** @returns Default state with loaded=false and empty metadata fields. */
113
+ override get initialState(): OpenAPIState {
114
+ return { enabled: false, loaded: false, title: '', version: '', endpointCount: 0 }
115
+ }
116
+
117
+ /** The base server URL derived from options, normalizing the openapi.json suffix */
118
+ get serverUrl(): string {
119
+ return this.options.url!.replace(/\/openapi\.json\/?$/, '').replace(/\/swagger\.json\/?$/, '').replace(/\/$/, '')
120
+ }
121
+
122
+ /** The URL that will be fetched for the spec document */
123
+ get specUrl(): string {
124
+ const url = this.options.url!
125
+ if (/\.(json|yaml|yml)(\?.*)?$/.test(url)) return url
126
+ return `${this.serverUrl}/openapi.json`
127
+ }
128
+
129
+ /** The raw spec object. Null before load() is called. */
130
+ get spec() {
131
+ return this._spec
132
+ }
133
+
134
+ /**
135
+ * Fetches and parses the OpenAPI spec from the configured URL.
136
+ * Populates `endpoints`, updates state with spec metadata.
137
+ *
138
+ * @returns {Promise<this>} This instance, for chaining
139
+ */
140
+ async load(): Promise<this> {
141
+ const response = await fetch(this.specUrl)
142
+
143
+ if (!response.ok) {
144
+ throw new Error(`Failed to load OpenAPI spec from ${this.specUrl}: ${response.status} ${response.statusText}`)
145
+ }
146
+
147
+ this._spec = await response.json()
148
+ this._endpoints = buildEndpointMap(this._spec)
149
+
150
+ this.setState({
151
+ loaded: true,
152
+ title: this._spec.info?.title || '',
153
+ version: this._spec.info?.version || '',
154
+ endpointCount: this._endpoints.size,
155
+ })
156
+
157
+ this.emit('loaded', this._spec)
158
+ return this
159
+ }
160
+
161
+ /** All parsed endpoints as an array */
162
+ get endpoints(): EndpointInfo[] {
163
+ return Array.from(this._endpoints.values())
164
+ }
165
+
166
+ /** All endpoint friendly names */
167
+ get endpointNames(): string[] {
168
+ return Array.from(this._endpoints.keys())
169
+ }
170
+
171
+ /** Map of endpoints grouped by tag */
172
+ get endpointsByTag(): Record<string, EndpointInfo[]> {
173
+ const result: Record<string, EndpointInfo[]> = {}
174
+ for (const ep of this._endpoints.values()) {
175
+ const tags = ep.tags.length ? ep.tags : ['untagged']
176
+ for (const tag of tags) {
177
+ if (!result[tag]) result[tag] = []
178
+ result[tag].push(ep)
179
+ }
180
+ }
181
+ return result
182
+ }
183
+
184
+ /**
185
+ * Get a single endpoint by its friendly name or operationId.
186
+ *
187
+ * @param {string} name - The friendly name or operationId to look up
188
+ * @returns {EndpointInfo | undefined} The endpoint info, or undefined if not found
189
+ */
190
+ endpoint(name: string): EndpointInfo | undefined {
191
+ return this._endpoints.get(name)
192
+ || this.endpoints.find((ep) => ep.operationId === name)
193
+ }
194
+
195
+ /**
196
+ * Convert all endpoints into OpenAI-compatible tool definitions.
197
+ *
198
+ * @param {Function} [filter] - Optional predicate to select which endpoints to include
199
+ * @returns {OpenAIToolDef[]} Array of tool definitions ready for the OpenAI tools parameter
200
+ */
201
+ toTools(filter?: (ep: EndpointInfo) => boolean): OpenAIToolDef[] {
202
+ const eps = filter ? this.endpoints.filter(filter) : this.endpoints
203
+ return eps.map((ep) => ({
204
+ type: 'function' as const,
205
+ function: endpointToFunction(ep),
206
+ }))
207
+ }
208
+
209
+ /**
210
+ * Convert a single endpoint (by name) to an OpenAI-compatible tool definition.
211
+ *
212
+ * @param {string} name - The endpoint friendly name or operationId
213
+ * @returns {OpenAIToolDef | undefined} The tool definition, or undefined if not found
214
+ */
215
+ toTool(name: string): OpenAIToolDef | undefined {
216
+ const ep = this.endpoint(name)
217
+ if (!ep) return undefined
218
+ return { type: 'function', function: endpointToFunction(ep) }
219
+ }
220
+
221
+ /**
222
+ * Convert all endpoints into OpenAI-compatible function definitions.
223
+ *
224
+ * @param {Function} [filter] - Optional predicate to select which endpoints to include
225
+ * @returns {OpenAIFunctionDef[]} Array of function definitions
226
+ */
227
+ toFunctions(filter?: (ep: EndpointInfo) => boolean): OpenAIFunctionDef[] {
228
+ const eps = filter ? this.endpoints.filter(filter) : this.endpoints
229
+ return eps.map(endpointToFunction)
230
+ }
231
+
232
+ /**
233
+ * Convert a single endpoint (by name) to an OpenAI function definition.
234
+ *
235
+ * @param {string} name - The endpoint friendly name or operationId
236
+ * @returns {OpenAIFunctionDef | undefined} The function definition, or undefined if not found
237
+ */
238
+ toFunction(name: string): OpenAIFunctionDef | undefined {
239
+ const ep = this.endpoint(name)
240
+ if (!ep) return undefined
241
+ return endpointToFunction(ep)
242
+ }
243
+
244
+ /**
245
+ * Return a compact JSON summary of all endpoints, useful for logging or REPL inspection.
246
+ *
247
+ * @returns {{ title: string, version: string, serverUrl: string, endpointCount: number, endpoints: object[] }} Serializable summary
248
+ */
249
+ toJSON() {
250
+ return {
251
+ title: this.state.get('title'),
252
+ version: this.state.get('version'),
253
+ serverUrl: this.serverUrl,
254
+ endpointCount: this._endpoints.size,
255
+ endpoints: this.endpoints.map((ep) => ({
256
+ name: ep.name,
257
+ method: ep.method.toUpperCase(),
258
+ path: ep.path,
259
+ summary: ep.summary,
260
+ tags: ep.tags,
261
+ deprecated: ep.deprecated,
262
+ })),
263
+ }
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Derive a human-friendly camelCase name from an operationId, or synthesize one from method + path.
269
+ */
270
+ function friendlyName(operationId: string | undefined, method: string, path: string): string {
271
+ if (operationId) return camelCase(operationId)
272
+
273
+ // Synthesize: GET /pets/{petId} -> getPetsPetId
274
+ const cleaned = path
275
+ .replace(/\{(\w+)\}/g, '$1')
276
+ .replace(/[^a-zA-Z0-9]+/g, ' ')
277
+ .trim()
278
+
279
+ return camelCase(`${method} ${cleaned}`)
280
+ }
281
+
282
+ /**
283
+ * Walk the spec paths and build a Map<friendlyName, EndpointInfo>.
284
+ */
285
+ function buildEndpointMap(spec: any): Map<string, EndpointInfo> {
286
+ const map = new Map<string, EndpointInfo>()
287
+ const paths = spec.paths || {}
288
+
289
+ for (const [path, pathItem] of Object.entries<any>(paths)) {
290
+ // Shared parameters at the path level
291
+ const sharedParams: OpenAPIParameter[] = (pathItem.parameters || []).map(normalizeParam)
292
+
293
+ for (const method of ['get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'trace']) {
294
+ const operation = pathItem[method]
295
+ if (!operation) continue
296
+
297
+ const name = friendlyName(operation.operationId, method, path)
298
+ const opParams: OpenAPIParameter[] = (operation.parameters || []).map(normalizeParam)
299
+
300
+ // Merge path-level params with operation-level (operation overrides by name+in)
301
+ const paramKey = (p: OpenAPIParameter) => `${p.in}:${p.name}`
302
+ const merged = new Map<string, OpenAPIParameter>()
303
+ for (const p of sharedParams) merged.set(paramKey(p), p)
304
+ for (const p of opParams) merged.set(paramKey(p), p)
305
+
306
+ const endpoint: EndpointInfo = {
307
+ name,
308
+ operationId: operation.operationId || '',
309
+ method,
310
+ path,
311
+ summary: operation.summary || '',
312
+ description: operation.description || '',
313
+ tags: operation.tags || [],
314
+ parameters: Array.from(merged.values()),
315
+ requestBody: operation.requestBody || null,
316
+ responses: operation.responses || {},
317
+ deprecated: !!operation.deprecated,
318
+ }
319
+
320
+ map.set(name, endpoint)
321
+ }
322
+ }
323
+
324
+ return map
325
+ }
326
+
327
+ function normalizeParam(raw: any): OpenAPIParameter {
328
+ return {
329
+ name: raw.name || '',
330
+ in: raw.in || 'query',
331
+ description: raw.description || '',
332
+ required: !!raw.required,
333
+ schema: raw.schema || {},
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Convert an EndpointInfo into an OpenAI-compatible function definition.
339
+ *
340
+ * Merges path, query, and header params + requestBody properties into a single
341
+ * flat `parameters` object, which is the format OpenAI expects.
342
+ */
343
+ function endpointToFunction(ep: EndpointInfo): OpenAIFunctionDef {
344
+ const properties: Record<string, any> = {}
345
+ const required: string[] = []
346
+
347
+ for (const param of ep.parameters) {
348
+ properties[param.name] = {
349
+ ...schemaToJsonSchema(param.schema),
350
+ description: param.description || `${param.in} parameter`,
351
+ }
352
+ if (param.required) required.push(param.name)
353
+ }
354
+
355
+ // Merge requestBody properties (for application/json)
356
+ const bodySchema = ep.requestBody?.content?.['application/json']?.schema
357
+ if (bodySchema) {
358
+ if (bodySchema.properties) {
359
+ for (const [key, val] of Object.entries<any>(bodySchema.properties)) {
360
+ properties[key] = schemaToJsonSchema(val)
361
+ }
362
+ if (bodySchema.required) {
363
+ for (const r of bodySchema.required) {
364
+ if (!required.includes(r)) required.push(r)
365
+ }
366
+ }
367
+ } else {
368
+ // If the body is a single schema without properties, expose it as "body"
369
+ properties['body'] = {
370
+ ...schemaToJsonSchema(bodySchema),
371
+ description: 'Request body',
372
+ }
373
+ }
374
+ }
375
+
376
+ const description = [ep.summary, ep.description]
377
+ .filter(Boolean)
378
+ .join(' — ')
379
+ || `${ep.method.toUpperCase()} ${ep.path}`
380
+
381
+ return {
382
+ name: ep.name,
383
+ description,
384
+ parameters: {
385
+ type: 'object',
386
+ properties,
387
+ required,
388
+ },
389
+ }
390
+ }
391
+
392
+ /**
393
+ * Lightweight conversion of an OpenAPI schema fragment to a JSON Schema-compatible
394
+ * fragment suitable for OpenAI function calling.
395
+ *
396
+ * Handles the common cases: primitives, arrays, objects, enums, $ref (as opaque string).
397
+ */
398
+ function schemaToJsonSchema(schema: any): any {
399
+ if (!schema) return { type: 'string' }
400
+
401
+ // Pass through $ref as a string description since we don't resolve refs here
402
+ if (schema.$ref) {
403
+ return { type: 'string', description: `Reference: ${schema.$ref}` }
404
+ }
405
+
406
+ const result: any = {}
407
+
408
+ if (schema.type) result.type = schema.type
409
+ if (schema.description) result.description = schema.description
410
+ if (schema.enum) result.enum = schema.enum
411
+ if (schema.default !== undefined) result.default = schema.default
412
+
413
+ if (schema.type === 'array' && schema.items) {
414
+ result.items = schemaToJsonSchema(schema.items)
415
+ }
416
+
417
+ if (schema.type === 'object' && schema.properties) {
418
+ result.properties = {}
419
+ for (const [key, val] of Object.entries<any>(schema.properties)) {
420
+ result.properties[key] = schemaToJsonSchema(val)
421
+ }
422
+ if (schema.required) result.required = schema.required
423
+ }
424
+
425
+ // oneOf / anyOf / allOf pass-through
426
+ if (schema.oneOf) result.oneOf = schema.oneOf.map(schemaToJsonSchema)
427
+ if (schema.anyOf) result.anyOf = schema.anyOf.map(schemaToJsonSchema)
428
+ if (schema.allOf) result.allOf = schema.allOf.map(schemaToJsonSchema)
429
+
430
+ // Default to string if nothing was set
431
+ if (!result.type && !result.oneOf && !result.anyOf && !result.allOf) {
432
+ result.type = 'string'
433
+ }
434
+
435
+ return result
436
+ }
437
+
438
+ export default features.register('openapi', OpenAPI)