@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.
- package/CLAUDE.md +71 -0
- package/README.md +78 -0
- package/bun.lock +2928 -0
- package/bunfig.toml +3 -0
- package/commands/audit-docs.ts +740 -0
- package/commands/build-scaffolds.ts +154 -0
- package/commands/generate-api-docs.ts +114 -0
- package/commands/update-introspection.ts +67 -0
- package/docs/CLI.md +335 -0
- package/docs/README.md +88 -0
- package/docs/TABLE-OF-CONTENTS.md +157 -0
- package/docs/apis/clients/elevenlabs.md +84 -0
- package/docs/apis/clients/graph.md +56 -0
- package/docs/apis/clients/openai.md +69 -0
- package/docs/apis/clients/rest.md +41 -0
- package/docs/apis/clients/websocket.md +107 -0
- package/docs/apis/features/agi/assistant.md +471 -0
- package/docs/apis/features/agi/assistants-manager.md +154 -0
- package/docs/apis/features/agi/claude-code.md +602 -0
- package/docs/apis/features/agi/conversation-history.md +352 -0
- package/docs/apis/features/agi/conversation.md +333 -0
- package/docs/apis/features/agi/docs-reader.md +121 -0
- package/docs/apis/features/agi/openai-codex.md +318 -0
- package/docs/apis/features/agi/openapi.md +138 -0
- package/docs/apis/features/agi/semantic-search.md +387 -0
- package/docs/apis/features/agi/skills-library.md +216 -0
- package/docs/apis/features/node/container-link.md +133 -0
- package/docs/apis/features/node/content-db.md +313 -0
- package/docs/apis/features/node/disk-cache.md +379 -0
- package/docs/apis/features/node/dns.md +651 -0
- package/docs/apis/features/node/docker.md +705 -0
- package/docs/apis/features/node/downloader.md +81 -0
- package/docs/apis/features/node/esbuild.md +59 -0
- package/docs/apis/features/node/file-manager.md +182 -0
- package/docs/apis/features/node/fs.md +581 -0
- package/docs/apis/features/node/git.md +330 -0
- package/docs/apis/features/node/google-auth.md +174 -0
- package/docs/apis/features/node/google-calendar.md +187 -0
- package/docs/apis/features/node/google-docs.md +151 -0
- package/docs/apis/features/node/google-drive.md +225 -0
- package/docs/apis/features/node/google-sheets.md +179 -0
- package/docs/apis/features/node/grep.md +290 -0
- package/docs/apis/features/node/helpers.md +135 -0
- package/docs/apis/features/node/ink.md +334 -0
- package/docs/apis/features/node/ipc-socket.md +260 -0
- package/docs/apis/features/node/json-tree.md +86 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
- package/docs/apis/features/node/networking.md +281 -0
- package/docs/apis/features/node/nlp.md +133 -0
- package/docs/apis/features/node/opener.md +97 -0
- package/docs/apis/features/node/os.md +118 -0
- package/docs/apis/features/node/package-finder.md +402 -0
- package/docs/apis/features/node/postgres.md +212 -0
- package/docs/apis/features/node/proc.md +430 -0
- package/docs/apis/features/node/process-manager.md +210 -0
- package/docs/apis/features/node/python.md +278 -0
- package/docs/apis/features/node/repl.md +88 -0
- package/docs/apis/features/node/runpod.md +673 -0
- package/docs/apis/features/node/secure-shell.md +169 -0
- package/docs/apis/features/node/semantic-search.md +401 -0
- package/docs/apis/features/node/sqlite.md +211 -0
- package/docs/apis/features/node/telegram.md +254 -0
- package/docs/apis/features/node/tts.md +118 -0
- package/docs/apis/features/node/ui.md +703 -0
- package/docs/apis/features/node/vault.md +64 -0
- package/docs/apis/features/node/vm.md +84 -0
- package/docs/apis/features/node/window-manager.md +337 -0
- package/docs/apis/features/node/yaml-tree.md +85 -0
- package/docs/apis/features/node/yaml.md +176 -0
- package/docs/apis/features/web/asset-loader.md +47 -0
- package/docs/apis/features/web/container-link.md +133 -0
- package/docs/apis/features/web/esbuild.md +59 -0
- package/docs/apis/features/web/helpers.md +135 -0
- package/docs/apis/features/web/network.md +30 -0
- package/docs/apis/features/web/speech.md +55 -0
- package/docs/apis/features/web/vault.md +64 -0
- package/docs/apis/features/web/vm.md +84 -0
- package/docs/apis/features/web/voice.md +67 -0
- package/docs/apis/servers/express.md +127 -0
- package/docs/apis/servers/mcp.md +213 -0
- package/docs/apis/servers/websocket.md +99 -0
- package/docs/documentation-audit.md +134 -0
- package/docs/examples/content-db.md +77 -0
- package/docs/examples/disk-cache.md +83 -0
- package/docs/examples/docker.md +101 -0
- package/docs/examples/downloader.md +70 -0
- package/docs/examples/esbuild.md +80 -0
- package/docs/examples/file-manager.md +82 -0
- package/docs/examples/fs.md +83 -0
- package/docs/examples/git.md +85 -0
- package/docs/examples/google-auth.md +88 -0
- package/docs/examples/google-calendar.md +94 -0
- package/docs/examples/google-docs.md +82 -0
- package/docs/examples/google-drive.md +96 -0
- package/docs/examples/google-sheets.md +95 -0
- package/docs/examples/grep.md +85 -0
- package/docs/examples/ink-blocks.md +75 -0
- package/docs/examples/ink-renderer.md +41 -0
- package/docs/examples/ink.md +103 -0
- package/docs/examples/ipc-socket.md +103 -0
- package/docs/examples/json-tree.md +91 -0
- package/docs/examples/launcher-app-command-listener.md +120 -0
- package/docs/examples/networking.md +58 -0
- package/docs/examples/nlp.md +91 -0
- package/docs/examples/opener.md +78 -0
- package/docs/examples/os.md +72 -0
- package/docs/examples/package-finder.md +89 -0
- package/docs/examples/port-exposer.md +89 -0
- package/docs/examples/postgres.md +91 -0
- package/docs/examples/proc.md +81 -0
- package/docs/examples/process-manager.md +79 -0
- package/docs/examples/python.md +91 -0
- package/docs/examples/repl.md +93 -0
- package/docs/examples/runpod.md +119 -0
- package/docs/examples/secure-shell.md +92 -0
- package/docs/examples/sqlite.md +86 -0
- package/docs/examples/telegram.md +77 -0
- package/docs/examples/tts.md +86 -0
- package/docs/examples/ui.md +80 -0
- package/docs/examples/vault.md +70 -0
- package/docs/examples/vm.md +86 -0
- package/docs/examples/window-manager.md +125 -0
- package/docs/examples/yaml-tree.md +93 -0
- package/docs/examples/yaml.md +104 -0
- package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
- package/docs/ideas/container-use-api.md +9 -0
- package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
- package/docs/ideas/feature-stacks.md +22 -0
- package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
- package/docs/ideas/mcp-design.md +9 -0
- package/docs/ideas/web-container-debugging-feature.md +13 -0
- package/docs/introspection-audit.md +49 -0
- package/docs/introspection.md +154 -0
- package/docs/mcp/readme.md +162 -0
- package/docs/models.ts +38 -0
- package/docs/philosophy.md +85 -0
- package/docs/principles.md +7 -0
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
- package/docs/prompts/mcp-test-easy-command.md +27 -0
- package/docs/reports/assistant-bugs.md +38 -0
- package/docs/reports/attach-pattern-usage.md +18 -0
- package/docs/reports/code-audit-results.md +391 -0
- package/docs/reports/introspection-audit-tasks.md +378 -0
- package/docs/reports/luca-mcp-improvements.md +128 -0
- package/docs/scaffolds/client.md +140 -0
- package/docs/scaffolds/command.md +106 -0
- package/docs/scaffolds/endpoint.md +176 -0
- package/docs/scaffolds/feature.md +148 -0
- package/docs/scaffolds/server.md +187 -0
- package/docs/tasks/web-container-helper-discovery.md +71 -0
- package/docs/todos.md +1 -0
- package/docs/tutorials/01-getting-started.md +106 -0
- package/docs/tutorials/02-container.md +210 -0
- package/docs/tutorials/03-scripts.md +194 -0
- package/docs/tutorials/04-features-overview.md +196 -0
- package/docs/tutorials/05-state-and-events.md +171 -0
- package/docs/tutorials/06-servers.md +157 -0
- package/docs/tutorials/07-endpoints.md +198 -0
- package/docs/tutorials/08-commands.md +171 -0
- package/docs/tutorials/09-clients.md +162 -0
- package/docs/tutorials/10-creating-features.md +198 -0
- package/docs/tutorials/11-contentbase.md +191 -0
- package/docs/tutorials/12-assistants.md +215 -0
- package/docs/tutorials/13-introspection.md +147 -0
- package/docs/tutorials/14-type-system.md +174 -0
- package/docs/tutorials/15-project-patterns.md +222 -0
- package/docs/tutorials/16-google-features.md +534 -0
- package/docs/tutorials/17-tui-blocks.md +530 -0
- package/docs/tutorials/18-semantic-search.md +334 -0
- package/index.ts +1 -0
- package/luca.console.ts +9 -0
- package/main.py +6 -0
- package/package.json +154 -0
- package/pyproject.toml +7 -0
- package/scripts/animations/chrome-glitch.ts +55 -0
- package/scripts/animations/index.ts +16 -0
- package/scripts/animations/neon-pulse.ts +64 -0
- package/scripts/animations/types.ts +6 -0
- package/scripts/build-web.ts +28 -0
- package/scripts/examples/ask-luca-expert.ts +42 -0
- package/scripts/examples/assistant-questions.ts +12 -0
- package/scripts/examples/excalidraw-expert.ts +75 -0
- package/scripts/examples/expert-chat.ts +0 -0
- package/scripts/examples/file-manager.ts +14 -0
- package/scripts/examples/ideas.ts +12 -0
- package/scripts/examples/interactive-chat.ts +20 -0
- package/scripts/examples/openai-tool-calls.ts +113 -0
- package/scripts/examples/opening-a-web-browser.ts +5 -0
- package/scripts/examples/telegram-bot.ts +79 -0
- package/scripts/examples/telegram-ink-ui.ts +302 -0
- package/scripts/examples/using-assistant-with-mcp.ts +560 -0
- package/scripts/examples/using-claude-code.ts +10 -0
- package/scripts/examples/using-contentdb.ts +35 -0
- package/scripts/examples/using-conversations.ts +35 -0
- package/scripts/examples/using-disk-cache.ts +10 -0
- package/scripts/examples/using-docker-shell.ts +75 -0
- package/scripts/examples/using-elevenlabs.ts +25 -0
- package/scripts/examples/using-google-calendar.ts +57 -0
- package/scripts/examples/using-google-docs.ts +74 -0
- package/scripts/examples/using-google-drive.ts +74 -0
- package/scripts/examples/using-google-sheets.ts +89 -0
- package/scripts/examples/using-nlp.ts +55 -0
- package/scripts/examples/using-ollama.ts +10 -0
- package/scripts/examples/using-openai-codex.ts +23 -0
- package/scripts/examples/using-postgres.ts +55 -0
- package/scripts/examples/using-runpod.ts +32 -0
- package/scripts/examples/using-tts.ts +40 -0
- package/scripts/examples/vm-loading-esm-modules.ts +16 -0
- package/scripts/scaffold.ts +391 -0
- package/scripts/scratch.ts +15 -0
- package/scripts/test-command-listener.ts +123 -0
- package/scripts/test-window-manager-lifecycle.ts +86 -0
- package/scripts/test-window-manager.ts +43 -0
- package/scripts/update-introspection-data.ts +58 -0
- package/src/agi/README.md +14 -0
- package/src/agi/container.server.ts +114 -0
- package/src/agi/endpoints/ask.ts +60 -0
- package/src/agi/endpoints/conversations/[id].ts +45 -0
- package/src/agi/endpoints/conversations.ts +31 -0
- package/src/agi/endpoints/experts.ts +37 -0
- package/src/agi/features/assistant.ts +767 -0
- package/src/agi/features/assistants-manager.ts +260 -0
- package/src/agi/features/claude-code.ts +1111 -0
- package/src/agi/features/conversation-history.ts +497 -0
- package/src/agi/features/conversation.ts +799 -0
- package/src/agi/features/openai-codex.ts +631 -0
- package/src/agi/features/openapi.ts +438 -0
- package/src/agi/features/skills-library.ts +425 -0
- package/src/agi/index.ts +6 -0
- package/src/agi/lib/token-counter.ts +122 -0
- package/src/browser.ts +25 -0
- package/src/bus.ts +100 -0
- package/src/cli/cli.ts +70 -0
- package/src/client.ts +461 -0
- package/src/clients/civitai/index.ts +541 -0
- package/src/clients/client-template.ts +41 -0
- package/src/clients/comfyui/index.ts +597 -0
- package/src/clients/elevenlabs/index.ts +291 -0
- package/src/clients/openai/index.ts +451 -0
- package/src/clients/supabase/index.ts +366 -0
- package/src/command.ts +164 -0
- package/src/commands/chat.ts +182 -0
- package/src/commands/console.ts +192 -0
- package/src/commands/describe.ts +433 -0
- package/src/commands/eval.ts +116 -0
- package/src/commands/help.ts +214 -0
- package/src/commands/index.ts +14 -0
- package/src/commands/mcp.ts +64 -0
- package/src/commands/prompt.ts +807 -0
- package/src/commands/run.ts +257 -0
- package/src/commands/sandbox-mcp.ts +439 -0
- package/src/commands/scaffold.ts +79 -0
- package/src/commands/serve.ts +172 -0
- package/src/container.ts +781 -0
- package/src/endpoint.ts +340 -0
- package/src/feature.ts +75 -0
- package/src/hash-object.ts +97 -0
- package/src/helper.ts +543 -0
- package/src/introspection/generated.agi.ts +23388 -0
- package/src/introspection/generated.node.ts +18899 -0
- package/src/introspection/generated.web.ts +2021 -0
- package/src/introspection/index.ts +256 -0
- package/src/introspection/scan.ts +912 -0
- package/src/node/container.ts +354 -0
- package/src/node/feature.ts +13 -0
- package/src/node/features/container-link.ts +558 -0
- package/src/node/features/content-db.ts +475 -0
- package/src/node/features/disk-cache.ts +382 -0
- package/src/node/features/dns.ts +655 -0
- package/src/node/features/docker.ts +912 -0
- package/src/node/features/downloader.ts +92 -0
- package/src/node/features/esbuild.ts +68 -0
- package/src/node/features/file-manager.ts +357 -0
- package/src/node/features/fs.ts +534 -0
- package/src/node/features/git.ts +492 -0
- package/src/node/features/google-auth.ts +502 -0
- package/src/node/features/google-calendar.ts +300 -0
- package/src/node/features/google-docs.ts +404 -0
- package/src/node/features/google-drive.ts +339 -0
- package/src/node/features/google-sheets.ts +279 -0
- package/src/node/features/grep.ts +406 -0
- package/src/node/features/helpers.ts +374 -0
- package/src/node/features/ink.ts +490 -0
- package/src/node/features/ipc-socket.ts +459 -0
- package/src/node/features/json-tree.ts +188 -0
- package/src/node/features/launcher-app-command-listener.ts +388 -0
- package/src/node/features/networking.ts +925 -0
- package/src/node/features/nlp.ts +211 -0
- package/src/node/features/opener.ts +166 -0
- package/src/node/features/os.ts +157 -0
- package/src/node/features/package-finder.ts +539 -0
- package/src/node/features/port-exposer.ts +342 -0
- package/src/node/features/postgres.ts +273 -0
- package/src/node/features/proc.ts +502 -0
- package/src/node/features/process-manager.ts +542 -0
- package/src/node/features/python.ts +444 -0
- package/src/node/features/repl.ts +194 -0
- package/src/node/features/runpod.ts +802 -0
- package/src/node/features/secure-shell.ts +248 -0
- package/src/node/features/semantic-search.ts +924 -0
- package/src/node/features/sqlite.ts +289 -0
- package/src/node/features/telegram.ts +342 -0
- package/src/node/features/tts.ts +184 -0
- package/src/node/features/ui.ts +857 -0
- package/src/node/features/vault.ts +164 -0
- package/src/node/features/vm.ts +312 -0
- package/src/node/features/window-manager.ts +804 -0
- package/src/node/features/yaml-tree.ts +149 -0
- package/src/node/features/yaml.ts +132 -0
- package/src/node.ts +70 -0
- package/src/react/index.ts +175 -0
- package/src/registry.ts +199 -0
- package/src/scaffolds/generated.ts +1613 -0
- package/src/scaffolds/template.ts +37 -0
- package/src/schemas/base.ts +255 -0
- package/src/server.ts +135 -0
- package/src/servers/express.ts +209 -0
- package/src/servers/mcp.ts +805 -0
- package/src/servers/socket.ts +120 -0
- package/src/state.ts +101 -0
- package/src/web/clients/socket.ts +82 -0
- package/src/web/container.ts +74 -0
- package/src/web/extension.ts +30 -0
- package/src/web/feature.ts +12 -0
- package/src/web/features/asset-loader.ts +64 -0
- package/src/web/features/container-link.ts +385 -0
- package/src/web/features/esbuild.ts +79 -0
- package/src/web/features/helpers.ts +267 -0
- package/src/web/features/network.ts +61 -0
- package/src/web/features/speech.ts +87 -0
- package/src/web/features/vault.ts +189 -0
- package/src/web/features/vm.ts +78 -0
- package/src/web/features/voice-recognition.ts +129 -0
- package/src/web/shims/isomorphic-vm.ts +149 -0
- package/test/bus.test.ts +134 -0
- package/test/clients-servers.test.ts +216 -0
- package/test/container-link.test.ts +274 -0
- package/test/features.test.ts +160 -0
- package/test/integration.test.ts +787 -0
- package/test/node-container.test.ts +121 -0
- package/test/rate-limit.test.ts +272 -0
- package/test/semantic-search.test.ts +550 -0
- package/test/state.test.ts +121 -0
- package/test-integration/assistant.test.ts +138 -0
- package/test-integration/assistants-manager.test.ts +123 -0
- package/test-integration/claude-code.test.ts +98 -0
- package/test-integration/conversation-history.test.ts +205 -0
- package/test-integration/conversation.test.ts +137 -0
- package/test-integration/elevenlabs.test.ts +55 -0
- package/test-integration/google-services.test.ts +80 -0
- package/test-integration/helpers.ts +89 -0
- package/test-integration/openai-codex.test.ts +93 -0
- package/test-integration/runpod.test.ts +58 -0
- package/test-integration/server-endpoints.test.ts +97 -0
- package/test-integration/skills-library.test.ts +157 -0
- package/test-integration/telegram.test.ts +46 -0
- package/tsconfig.json +58 -0
- 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)
|