akanjs 3.0.0-alpha.2 → 3.0.0-alpha.21
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/README.ko.md +1 -1
- package/README.md +1 -1
- package/base/symbols.ts +4 -0
- package/client/clientRuntime.ts +8 -0
- package/client/cn.ts +7 -0
- package/client/csrTypes.ts +2 -0
- package/client/frameDebug.ts +3 -2
- package/common/Logger.ts +4 -0
- package/common/deepObjectify.ts +21 -7
- package/common/index.ts +14 -0
- package/common/mcpExposure.ts +99 -0
- package/common/pathSet.ts +17 -5
- package/common/routeConvention.ts +30 -0
- package/constant/crystalize.ts +3 -1
- package/constant/fieldInfo.ts +6 -0
- package/constant/getDefault.ts +37 -9
- package/constant/immerify.ts +1 -1
- package/constant/index.ts +2 -0
- package/constant/labelOf.ts +17 -0
- package/constant/mask.ts +60 -0
- package/constant/purify.ts +17 -11
- package/constant/types.ts +14 -12
- package/dictionary/DictionaryLookup.ts +30 -0
- package/dictionary/agent.dictionary.ts +31 -0
- package/dictionary/agentTurn.dictionary.ts +29 -0
- package/dictionary/base.dictionary.ts +8 -0
- package/dictionary/dictInfo.ts +8 -0
- package/dictionary/dictionary.ts +9 -3
- package/dictionary/index.ts +1 -0
- package/document/database.ts +21 -0
- package/document/filterMeta.ts +7 -0
- package/document/index.ts +1 -0
- package/document/into.ts +7 -1
- package/document/noDocumentError.ts +12 -0
- package/fetch/agentTurn.ts +19 -0
- package/fetch/client/fetchClient.ts +71 -13
- package/fetch/client/wsClient.ts +17 -2
- package/fetch/fetchType/endpointFetch.type.ts +10 -4
- package/fetch/index.ts +1 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-wal +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-wal +0 -0
- package/package.json +11 -9
- package/server/akanApp.ts +30 -34
- package/server/akanOption.ts +65 -7
- package/server/akanServer.ts +172 -7
- package/server/animatedImage.ts +83 -0
- package/server/artifact/routeClientCache.ts +14 -0
- package/server/assetEncoding.ts +47 -0
- package/server/devtools/signalSerializer.ts +2 -2
- package/server/devtools/types.ts +2 -2
- package/server/di/diLifecycle.ts +31 -3
- package/server/di/predefinedAdaptor.ts +6 -0
- package/server/imageOptimizer.ts +33 -46
- package/server/index.ts +1 -2
- package/server/mcp/McpAuth.ts +177 -0
- package/server/mcp/McpDispatcher.ts +245 -0
- package/server/mcp/McpEventStream.ts +76 -0
- package/server/mcp/McpExecutionContext.ts +105 -0
- package/server/mcp/McpRouter.ts +591 -0
- package/server/mcp/index.ts +5 -0
- package/server/resolver/CascadeRunner.ts +1 -1
- package/server/resolver/database.resolver.ts +42 -5
- package/server/resolver/service.resolver.ts +29 -0
- package/server/resolver/signal.resolver.ts +16 -1
- package/server/routeTreeBuilder.ts +2 -44
- package/server/rscWorker.tsx +6 -2
- package/server/subRouteIndexDocument.tsx +61 -0
- package/server/systemPageDocument.tsx +2 -2
- package/server/systemPages.tsx +49 -16
- package/server/vendor/akanjs-fetch.ts +1 -0
- package/server/webRouter.ts +159 -35
- package/service/agent.service.ts +14 -0
- package/service/base.service.ts +2 -1
- package/service/index.ts +1 -0
- package/service/predefinedAdaptor/database.adaptor.ts +78 -24
- package/service/predefinedAdaptor/deepseekLlm.ts +230 -0
- package/service/predefinedAdaptor/index.ts +3 -0
- package/service/predefinedAdaptor/insightQuery.ts +183 -0
- package/service/predefinedAdaptor/llm.adaptor.ts +76 -0
- package/service/predefinedAdaptor/role.adaptor.ts +2 -0
- package/service/predefinedAdaptor/schedule.adaptor.ts +12 -5
- package/service/serviceModule.ts +17 -0
- package/service/types.ts +4 -0
- package/signal/agent/AgentCatalogue.ts +165 -0
- package/signal/agent/index.ts +1 -0
- package/signal/agent.signal.ts +42 -0
- package/signal/agentTurn.ts +8 -0
- package/signal/agentTurnStream.ts +48 -0
- package/signal/base.signal.ts +2 -1
- package/signal/endpointInfo.ts +38 -2
- package/signal/guard.ts +13 -1
- package/signal/guards.ts +52 -1
- package/signal/index.ts +6 -0
- package/signal/mcp/McpDocument.ts +286 -0
- package/signal/mcp/McpProgress.ts +106 -0
- package/signal/mcp/McpUriTemplate.ts +85 -0
- package/signal/mcp/Msg.ts +368 -0
- package/signal/mcp/index.ts +5 -0
- package/signal/mcp/mcpProtocol.ts +120 -0
- package/signal/middleware.ts +7 -3
- package/signal/openapi/openapi.ts +32 -171
- package/signal/schema/JsonSchemaBuilder.ts +211 -0
- package/signal/schema/index.ts +1 -0
- package/signal/signalContext.ts +85 -9
- package/signal/slice.ts +9 -9
- package/signal/types.ts +1 -1
- package/store/action.ts +11 -22
- package/store/actionTag.ts +33 -0
- package/store/agent/AgentBridge.ts +323 -0
- package/store/agent/AgentContext.ts +74 -0
- package/store/agent/AgentPrompts.ts +82 -0
- package/store/agent/AgentVisibility.ts +68 -0
- package/store/agent/ScreenReader.ts +208 -0
- package/store/agent/StoreCatalogue.ts +351 -0
- package/store/agent/StoreSurfaceSource.ts +122 -0
- package/store/agent/index.ts +9 -0
- package/store/agent/storeSurface.ts +28 -0
- package/store/agent/types.ts +50 -0
- package/store/agentic/StToolBuilder.ts +199 -0
- package/store/agentic/attachAgentic.ts +22 -0
- package/store/agentic/index.ts +5 -0
- package/store/agentic/readableValue.ts +17 -0
- package/store/agentic/useStExpose.ts +18 -0
- package/store/agentic/useStState.ts +35 -0
- package/store/baseSt.ts +7 -0
- package/store/databaseStateNames.ts +31 -0
- package/store/formSetterNames.ts +21 -0
- package/store/index.ts +8 -0
- package/store/rootStore.ts +3 -1
- package/store/sliceRole.ts +36 -0
- package/store/state.ts +2 -12
- package/store/store.ts +19 -1
- package/store/storeInstance.ts +165 -20
- package/store/storeRegistry.ts +16 -1
- package/store/types.ts +18 -0
- package/store/withSelector.ts +7 -8
- package/test/registerDom.ts +4 -0
- package/types/base/symbols.d.ts +4 -0
- package/types/client/cn.d.ts +5 -0
- package/types/client/csrTypes.d.ts +2 -0
- package/types/common/Logger.d.ts +2 -0
- package/types/common/deepObjectify.d.ts +4 -2
- package/types/common/index.d.ts +2 -1
- package/types/common/mcpExposure.d.ts +60 -0
- package/types/common/routeConvention.d.ts +8 -0
- package/types/constant/fieldInfo.d.ts +1 -0
- package/types/constant/getDefault.d.ts +5 -0
- package/types/constant/index.d.ts +2 -0
- package/types/constant/labelOf.d.ts +6 -0
- package/types/constant/mask.d.ts +34 -0
- package/types/constant/purify.d.ts +2 -2
- package/types/constant/types.d.ts +2 -2
- package/types/dictionary/DictionaryLookup.d.ts +14 -0
- package/types/dictionary/agent.dictionary.d.ts +1 -0
- package/types/dictionary/agentTurn.dictionary.d.ts +1 -0
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictInfo.d.ts +6 -0
- package/types/dictionary/dictionary.d.ts +11 -9
- package/types/dictionary/index.d.ts +1 -0
- package/types/document/database.d.ts +20 -0
- package/types/document/filterMeta.d.ts +1 -0
- package/types/document/index.d.ts +1 -0
- package/types/document/into.d.ts +6 -1
- package/types/document/noDocumentError.d.ts +12 -0
- package/types/fetch/agentTurn.d.ts +593 -0
- package/types/fetch/client/fetchClient.d.ts +10 -0
- package/types/fetch/fetchType/endpointFetch.type.d.ts +5 -3
- package/types/fetch/index.d.ts +1 -0
- package/types/server/akanOption.d.ts +35 -7
- package/types/server/akanServer.d.ts +44 -1
- package/types/server/animatedImage.d.ts +7 -0
- package/types/server/artifact/routeClientCache.d.ts +6 -0
- package/types/server/assetEncoding.d.ts +7 -0
- package/types/server/devtools/types.d.ts +2 -2
- package/types/server/di/predefinedAdaptor.d.ts +4 -1
- package/types/server/index.d.ts +0 -2
- package/types/server/mcp/McpAuth.d.ts +62 -0
- package/types/server/mcp/McpDispatcher.d.ts +54 -0
- package/types/server/mcp/McpEventStream.d.ts +18 -0
- package/types/server/mcp/McpExecutionContext.d.ts +44 -0
- package/types/server/mcp/McpRouter.d.ts +60 -0
- package/types/server/mcp/index.d.ts +5 -0
- package/types/server/mcp.d.ts +1 -0
- package/types/server/subRouteIndexDocument.d.ts +8 -0
- package/types/server/systemPageDocument.d.ts +1 -0
- package/types/server/systemPages.d.ts +5 -0
- package/types/server/vendor/akanjs-fetch.d.ts +1 -0
- package/types/service/agent.service.d.ts +12 -0
- package/types/service/base.service.d.ts +4 -0
- package/types/service/index.d.ts +1 -0
- package/types/service/predefinedAdaptor/database.adaptor.d.ts +24 -2
- package/types/service/predefinedAdaptor/deepseekLlm.d.ts +64 -0
- package/types/service/predefinedAdaptor/index.d.ts +3 -0
- package/types/service/predefinedAdaptor/insightQuery.d.ts +50 -0
- package/types/service/predefinedAdaptor/llm.adaptor.d.ts +68 -0
- package/types/service/predefinedAdaptor/role.adaptor.d.ts +2 -0
- package/types/service/predefinedAdaptor/schedule.adaptor.d.ts +10 -6
- package/types/service/types.d.ts +4 -1
- package/types/signal/agent/AgentCatalogue.d.ts +100 -0
- package/types/signal/agent/index.d.ts +1 -0
- package/types/signal/agent.d.ts +1 -0
- package/types/signal/agent.signal.d.ts +29 -0
- package/types/signal/agentTurn.d.ts +9 -0
- package/types/signal/agentTurnStream.d.ts +16 -0
- package/types/signal/base.signal.d.ts +1 -1
- package/types/signal/endpointInfo.d.ts +21 -3
- package/types/signal/guard.d.ts +11 -0
- package/types/signal/guards.d.ts +22 -1
- package/types/signal/index.d.ts +6 -0
- package/types/signal/mcp/McpDocument.d.ts +65 -0
- package/types/signal/mcp/McpProgress.d.ts +40 -0
- package/types/signal/mcp/McpUriTemplate.d.ts +26 -0
- package/types/signal/mcp/Msg.d.ts +143 -0
- package/types/signal/mcp/index.d.ts +5 -0
- package/types/signal/mcp/mcpProtocol.d.ts +107 -0
- package/types/signal/mcp.d.ts +1 -0
- package/types/signal/openapi/openapi.d.ts +3 -3
- package/types/signal/schema/JsonSchemaBuilder.d.ts +47 -0
- package/types/signal/schema/index.d.ts +1 -0
- package/types/signal/schema.d.ts +1 -0
- package/types/signal/signalContext.d.ts +16 -1
- package/types/signal/slice.d.ts +8 -8
- package/types/signal/types.d.ts +1 -1
- package/types/store/actionTag.d.ts +21 -0
- package/types/store/agent/AgentBridge.d.ts +77 -0
- package/types/store/agent/AgentContext.d.ts +16 -0
- package/types/store/agent/AgentPrompts.d.ts +31 -0
- package/types/store/agent/AgentVisibility.d.ts +21 -0
- package/types/store/agent/ScreenReader.d.ts +11 -0
- package/types/store/agent/StoreCatalogue.d.ts +26 -0
- package/types/store/agent/StoreSurfaceSource.d.ts +18 -0
- package/types/store/agent/index.d.ts +9 -0
- package/types/store/agent/storeSurface.d.ts +14 -0
- package/types/store/agent/types.d.ts +49 -0
- package/types/store/agent.d.ts +1 -0
- package/types/store/agentic/StToolBuilder.d.ts +41 -0
- package/types/store/agentic/attachAgentic.d.ts +14 -0
- package/types/store/agentic/index.d.ts +5 -0
- package/types/store/agentic/readableValue.d.ts +3 -0
- package/types/store/agentic/useStExpose.d.ts +8 -0
- package/types/store/agentic/useStState.d.ts +12 -0
- package/types/store/agentic.d.ts +1 -0
- package/types/store/baseSt.d.ts +29 -22
- package/types/store/databaseStateNames.d.ts +25 -0
- package/types/store/formSetterNames.d.ts +16 -0
- package/types/store/index.d.ts +7 -0
- package/types/store/rootStore.d.ts +5 -1
- package/types/store/sliceRole.d.ts +25 -0
- package/types/store/store.d.ts +7 -2
- package/types/store/storeInstance.d.ts +33 -1
- package/types/store/storeRegistry.d.ts +5 -0
- package/types/store/types.d.ts +15 -0
- package/types/store/withSelector.d.ts +7 -8
- package/types/test/registerDom.d.ts +1 -0
- package/types/ui/Agent/Approval.d.ts +7 -0
- package/types/ui/Agent/Bubble.d.ts +13 -0
- package/types/ui/Agent/Chat.d.ts +24 -0
- package/types/ui/Agent/Context.d.ts +9 -0
- package/types/ui/Agent/Dock.d.ts +16 -0
- package/types/ui/Agent/Guide.d.ts +9 -0
- package/types/ui/Agent/Section.d.ts +10 -0
- package/types/ui/Agent/StateKey.d.ts +16 -0
- package/types/ui/Agent/Tool.d.ts +15 -0
- package/types/ui/Agent/Transcript.d.ts +13 -0
- package/types/ui/Agent/Zone.d.ts +23 -0
- package/types/ui/Agent/fetchRunner.d.ts +12 -0
- package/types/ui/Agent/index.d.ts +17 -0
- package/types/ui/Agent/index_.d.ts +1 -0
- package/types/ui/Agent/sessionHistory.d.ts +12 -0
- package/types/ui/Agent.d.ts +1 -0
- package/types/ui/Badge.d.ts +1 -1
- package/types/ui/Button.d.ts +1 -1
- package/types/ui/CsrImage.d.ts +1 -1
- package/types/ui/Dropdown.d.ts +2 -0
- package/types/ui/Image.d.ts +3 -3
- package/types/ui/Layout/BottomInset.d.ts +2 -1
- package/types/ui/Layout/index.d.ts +1 -1
- package/types/ui/Signal/style.d.ts +4 -1
- package/types/ui/UiOverride/context.d.ts +14 -2
- package/types/ui/agentAttrs.d.ts +14 -0
- package/types/ui/index.d.ts +4 -1
- package/types/ui/overlayLayer.d.ts +24 -0
- package/types/ui/recipe/badgeRecipe.d.ts +7 -3
- package/types/ui/recipe/buttonRecipe.d.ts +7 -3
- package/types/ui/recipe/inputRecipe.d.ts +5 -1
- package/types/vendor/use-agentic/AgentProvider.d.ts +18 -0
- package/types/vendor/use-agentic/AgentScope.d.ts +9 -0
- package/types/vendor/use-agentic/AgentSession.d.ts +50 -0
- package/types/vendor/use-agentic/Agentic.d.ts +21 -0
- package/types/vendor/use-agentic/AgenticSurface.d.ts +44 -0
- package/types/vendor/use-agentic/httpRunner.d.ts +15 -0
- package/types/vendor/use-agentic/index.d.ts +13 -0
- package/types/vendor/use-agentic/surfaceContext.d.ts +5 -0
- package/types/vendor/use-agentic/types.d.ts +133 -0
- package/types/vendor/use-agentic/useAgent.d.ts +4 -0
- package/types/vendor/use-agentic/useAgentGuide.d.ts +6 -0
- package/types/vendor/use-agentic/useAgentResource.d.ts +7 -0
- package/types/vendor/use-agentic/useAgentState.d.ts +13 -0
- package/types/vendor/use-agentic/useAgentTool.d.ts +16 -0
- package/types/vendor/use-agentic.d.ts +1 -0
- package/types/webkit/index.d.ts +3 -0
- package/types/webkit/lazy.d.ts +12 -0
- package/types/webkit/useCsrValues.d.ts +3 -3
- package/types/webkit/useEscapeKey.d.ts +5 -0
- package/types/webkit/useFrameRuntime.d.ts +6 -1
- package/types/webkit/useScreenScope.d.ts +18 -0
- package/ui/Agent/Approval.tsx +29 -0
- package/ui/Agent/Bubble.tsx +74 -0
- package/ui/Agent/Chat.tsx +225 -0
- package/ui/Agent/Context.tsx +38 -0
- package/ui/Agent/Dock.tsx +72 -0
- package/ui/Agent/Guide.tsx +16 -0
- package/ui/Agent/Section.tsx +24 -0
- package/ui/Agent/StateKey.tsx +44 -0
- package/ui/Agent/Tool.tsx +66 -0
- package/ui/Agent/Transcript.tsx +33 -0
- package/ui/Agent/Zone.tsx +48 -0
- package/ui/Agent/fetchRunner.ts +30 -0
- package/ui/Agent/index.ts +12 -0
- package/ui/Agent/index_.tsx +4 -0
- package/ui/Agent/sessionHistory.ts +33 -0
- package/ui/Badge.tsx +3 -3
- package/ui/BottomSheet.tsx +5 -0
- package/ui/Button.tsx +6 -2
- package/ui/Constant/Doc.tsx +1 -1
- package/ui/CsrImage.tsx +1 -1
- package/ui/Data/ListContainer.tsx +1 -1
- package/ui/DatePicker.tsx +5 -4
- package/ui/Dialog/Modal.tsx +12 -15
- package/ui/DraggableList.tsx +3 -1
- package/ui/Dropdown.tsx +33 -11
- package/ui/Field.tsx +30 -16
- package/ui/Image.tsx +3 -3
- package/ui/Input.tsx +14 -5
- package/ui/Layout/BottomInset.tsx +6 -1
- package/ui/Load/Units.tsx +11 -2
- package/ui/Load/View.tsx +7 -2
- package/ui/Loading/ProgressBar.tsx +8 -1
- package/ui/Menu.tsx +7 -8
- package/ui/Model/EditModal.tsx +37 -2
- package/ui/Model/SureToRemove.tsx +2 -1
- package/ui/Model/index_.tsx +42 -15
- package/ui/ObjectId.tsx +3 -4
- package/ui/Pagination.tsx +3 -4
- package/ui/Popconfirm.tsx +8 -7
- package/ui/Select.tsx +6 -4
- package/ui/Signal/RestApi.tsx +43 -16
- package/ui/Signal/WebSocket.tsx +1 -1
- package/ui/Signal/style.ts +6 -1
- package/ui/Switch.tsx +2 -0
- package/ui/System/CSR.tsx +6 -2
- package/ui/System/ThemeToggle.tsx +55 -33
- package/ui/ToggleSelect.tsx +4 -3
- package/ui/Tooltip.tsx +2 -1
- package/ui/UiOverride/context.ts +14 -2
- package/ui/agentAttrs.ts +19 -0
- package/ui/index.ts +10 -1
- package/ui/overlayLayer.ts +39 -0
- package/ui/recipe/badgeRecipe.ts +22 -3
- package/ui/recipe/buttonRecipe.ts +51 -2
- package/ui/recipe/factory.ts +2 -2
- package/ui/recipe/inputRecipe.ts +18 -4
- package/vendor/use-agentic/AgentProvider.tsx +39 -0
- package/vendor/use-agentic/AgentScope.tsx +20 -0
- package/vendor/use-agentic/AgentSession.ts +292 -0
- package/vendor/use-agentic/Agentic.tsx +44 -0
- package/vendor/use-agentic/AgenticSurface.ts +294 -0
- package/vendor/use-agentic/WIRE.md +57 -0
- package/vendor/use-agentic/httpRunner.ts +115 -0
- package/vendor/use-agentic/index.ts +14 -0
- package/vendor/use-agentic/surfaceContext.ts +9 -0
- package/vendor/use-agentic/types.ts +144 -0
- package/vendor/use-agentic/useAgent.ts +17 -0
- package/vendor/use-agentic/useAgentGuide.ts +16 -0
- package/vendor/use-agentic/useAgentResource.ts +30 -0
- package/vendor/use-agentic/useAgentState.ts +61 -0
- package/vendor/use-agentic/useAgentTool.ts +55 -0
- package/webkit/bootCsr.tsx +2 -33
- package/webkit/index.ts +3 -0
- package/webkit/lazy.tsx +27 -3
- package/webkit/useCsrValues.ts +121 -4
- package/webkit/useEscapeKey.tsx +42 -0
- package/webkit/useFrameRuntime.ts +65 -32
- package/webkit/useScreenScope.tsx +49 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { type Cls, FIELD_META, getNonArrayModel, PrimitiveRegistry, type PrimitiveScalar } from "akanjs/base";
|
|
2
|
-
import { type ConstantCls, type ConstantField, ConstantRegistry, type ConstantType } from "akanjs/constant";
|
|
3
1
|
import { FetchClient } from "akanjs/fetch";
|
|
4
|
-
import
|
|
2
|
+
import { Msg } from "../mcp";
|
|
3
|
+
import { type JsonSchema, JsonSchemaBuilder } from "../schema";
|
|
4
|
+
import type { SerializedArg, SerializedEndpoint, SerializedSignal } from "../types";
|
|
5
5
|
|
|
6
|
-
type OpenApiSchema = Record<string, unknown>;
|
|
7
6
|
type OpenApiParameter = Record<string, unknown>;
|
|
8
7
|
type OpenApiOperation = Record<string, unknown>;
|
|
9
8
|
type OpenApiPathItem = Record<string, OpenApiOperation>;
|
|
@@ -28,16 +27,29 @@ export interface OpenApiDocument {
|
|
|
28
27
|
servers?: { url: string; description?: string }[];
|
|
29
28
|
paths: Record<string, OpenApiPathItem>;
|
|
30
29
|
components: {
|
|
31
|
-
schemas: Record<string,
|
|
32
|
-
securitySchemes?: Record<string,
|
|
30
|
+
schemas: Record<string, JsonSchema>;
|
|
31
|
+
securitySchemes?: Record<string, JsonSchema>;
|
|
33
32
|
};
|
|
34
33
|
}
|
|
35
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Every endpoint type this app answers over HTTP, which is what an API contract has to list.
|
|
37
|
+
*
|
|
38
|
+
* `prompt` is a plain `GET` mounted whether or not the app enabled MCP, so leaving it out described an app that
|
|
39
|
+
* serves fewer routes than it does — and left this document disagreeing with the API explorer, which shows the
|
|
40
|
+
* same route. The websocket types (`pubsub`, `message`) have no HTTP surface to describe and are absent for that
|
|
41
|
+
* reason rather than by omission.
|
|
42
|
+
*/
|
|
36
43
|
const httpMethods = {
|
|
37
44
|
query: "get",
|
|
38
45
|
mutation: "post",
|
|
46
|
+
prompt: "get",
|
|
39
47
|
} as const;
|
|
40
48
|
|
|
49
|
+
const schema = new JsonSchemaBuilder();
|
|
50
|
+
/** Not a model, so it is handed to `referencedSchemas` beside them — and travels only if a prompt route cites it. */
|
|
51
|
+
const promptMessageSchemaName = "PromptMessage";
|
|
52
|
+
|
|
41
53
|
export const createOpenApiDocument = (
|
|
42
54
|
serializedSignal: Record<string, SerializedSignal>,
|
|
43
55
|
options: OpenApiDocumentOptions = {},
|
|
@@ -71,7 +83,10 @@ export const createOpenApiDocument = (
|
|
|
71
83
|
...(options.servers?.length ? { servers: options.servers } : {}),
|
|
72
84
|
paths,
|
|
73
85
|
components: {
|
|
74
|
-
schemas:
|
|
86
|
+
schemas: schema.referencedSchemas(paths, {
|
|
87
|
+
...schema.allModelSchemas(),
|
|
88
|
+
[promptMessageSchemaName]: Msg.schema,
|
|
89
|
+
}),
|
|
75
90
|
...(hasProtectedOperation
|
|
76
91
|
? {
|
|
77
92
|
securitySchemes: {
|
|
@@ -95,7 +110,7 @@ const collectRestEndpoints = (refName: string, signal: SerializedSignal): [strin
|
|
|
95
110
|
)
|
|
96
111
|
: [];
|
|
97
112
|
return [...baseEndpointEntries, ...sliceEndpointEntries, ...Object.entries(signal.endpoint)].filter(
|
|
98
|
-
([, endpoint]) => endpoint.type
|
|
113
|
+
([, endpoint]) => endpoint.type in httpMethods,
|
|
99
114
|
);
|
|
100
115
|
};
|
|
101
116
|
|
|
@@ -143,7 +158,7 @@ const createParameter = (
|
|
|
143
158
|
name: arg.name,
|
|
144
159
|
in: location,
|
|
145
160
|
required: location === "path",
|
|
146
|
-
schema:
|
|
161
|
+
schema: schema.arg(arg),
|
|
147
162
|
...(arg.example !== undefined ? { example: arg.example } : {}),
|
|
148
163
|
...(options.resolveDescription?.(`${refName}.signal.${endpointKey}.arg.${arg.name}.desc`)
|
|
149
164
|
? { description: options.resolveDescription(`${refName}.signal.${endpointKey}.arg.${arg.name}.desc`) }
|
|
@@ -152,12 +167,12 @@ const createParameter = (
|
|
|
152
167
|
|
|
153
168
|
const createRequestBody = (bodyArgs: SerializedArg[], uploadArgs: SerializedArg[]) => {
|
|
154
169
|
const required = [...bodyArgs, ...uploadArgs].filter((arg) => !arg.nullable).map((arg) => arg.name);
|
|
155
|
-
const
|
|
170
|
+
const bodySchema = {
|
|
156
171
|
type: "object",
|
|
157
172
|
properties: Object.fromEntries(
|
|
158
173
|
[...bodyArgs, ...uploadArgs].map((arg) => [
|
|
159
174
|
arg.name,
|
|
160
|
-
uploadArgs.includes(arg) ?
|
|
175
|
+
uploadArgs.includes(arg) ? schema.upload(arg) : schema.arg(arg),
|
|
161
176
|
]),
|
|
162
177
|
),
|
|
163
178
|
...(required.length ? { required } : {}),
|
|
@@ -166,7 +181,7 @@ const createRequestBody = (bodyArgs: SerializedArg[], uploadArgs: SerializedArg[
|
|
|
166
181
|
required: required.length > 0,
|
|
167
182
|
content: {
|
|
168
183
|
[uploadArgs.length ? "multipart/form-data" : "application/json"]: {
|
|
169
|
-
schema,
|
|
184
|
+
schema: bodySchema,
|
|
170
185
|
},
|
|
171
186
|
},
|
|
172
187
|
};
|
|
@@ -181,168 +196,14 @@ const createResponseContent = (endpoint: SerializedEndpoint) =>
|
|
|
181
196
|
}
|
|
182
197
|
: {
|
|
183
198
|
"application/json": {
|
|
184
|
-
|
|
199
|
+
|
|
200
|
+
schema:
|
|
201
|
+
endpoint.type === "prompt"
|
|
202
|
+
? { type: "array", items: { $ref: `#/components/schemas/${promptMessageSchemaName}` } }
|
|
203
|
+
: schema.returns(endpoint.returns),
|
|
185
204
|
},
|
|
186
205
|
};
|
|
187
206
|
|
|
188
|
-
const createReturnSchema = (returns: SerializedReturns): OpenApiSchema =>
|
|
189
|
-
withNullable(
|
|
190
|
-
withArrayDepth(createRefSchema(returns.refName, returns.modelType), returns.arrDepth ?? 0),
|
|
191
|
-
!!returns.nullable,
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
const createArgSchema = (arg: SerializedArg): OpenApiSchema => {
|
|
195
|
-
const schema = arg.enum ? createEnumSchema(arg.enum) : createRefSchema(arg.refName, arg.modelType);
|
|
196
|
-
return withNullable(withArrayDepth(schema, arg.arrDepth ?? 0), !!arg.nullable);
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const createUploadSchema = (arg: SerializedArg): OpenApiSchema => {
|
|
200
|
-
const fileSchema = { type: "string", format: "binary" };
|
|
201
|
-
return withNullable(withArrayDepth(fileSchema, arg.arrDepth ?? 0), !!arg.nullable);
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const createRefSchema = (refName: string, modelType?: ConstantType): OpenApiSchema => {
|
|
205
|
-
if (!modelType) return createPrimitiveSchema(refName);
|
|
206
|
-
const modelRef = ConstantRegistry.getModelRef(refName, modelType);
|
|
207
|
-
return { $ref: `#/components/schemas/${ConstantRegistry.getModelName(modelRef as Cls)}` };
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const createComponentSchemas = (): Record<string, OpenApiSchema> => {
|
|
211
|
-
const schemas: Record<string, OpenApiSchema> = {};
|
|
212
|
-
for (const [, database] of ConstantRegistry.database.entries()) {
|
|
213
|
-
[database.input, database.object, database.full, database.light, database.insight].forEach((modelRef) => {
|
|
214
|
-
schemas[ConstantRegistry.getModelName(modelRef)] = createModelSchema(modelRef);
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
for (const [, scalar] of ConstantRegistry.scalar.entries()) {
|
|
218
|
-
schemas[ConstantRegistry.getModelName(scalar.model)] = createModelSchema(scalar.model);
|
|
219
|
-
}
|
|
220
|
-
return schemas;
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
const createReferencedComponentSchemas = (paths: Record<string, OpenApiPathItem>): Record<string, OpenApiSchema> => {
|
|
224
|
-
const allSchemas = createComponentSchemas();
|
|
225
|
-
const referencedNames = collectSchemaRefNames(paths);
|
|
226
|
-
const pending = [...referencedNames];
|
|
227
|
-
for (let idx = 0; idx < pending.length; idx++) {
|
|
228
|
-
const schemaName = pending[idx];
|
|
229
|
-
if (!schemaName) continue;
|
|
230
|
-
const schema = allSchemas[schemaName];
|
|
231
|
-
if (!schema) continue;
|
|
232
|
-
for (const nestedName of collectSchemaRefNames(schema)) {
|
|
233
|
-
if (referencedNames.has(nestedName)) continue;
|
|
234
|
-
referencedNames.add(nestedName);
|
|
235
|
-
pending.push(nestedName);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
return Object.fromEntries(
|
|
239
|
-
[...referencedNames]
|
|
240
|
-
.sort((a, b) => a.localeCompare(b))
|
|
241
|
-
.flatMap((name) => (allSchemas[name] ? ([[name, allSchemas[name]]] as const) : [])),
|
|
242
|
-
);
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
const collectSchemaRefNames = (value: unknown): Set<string> => {
|
|
246
|
-
const refs = new Set<string>();
|
|
247
|
-
const visit = (current: unknown) => {
|
|
248
|
-
if (!current || typeof current !== "object") return;
|
|
249
|
-
if (Array.isArray(current)) {
|
|
250
|
-
current.forEach(visit);
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
const record = current as Record<string, unknown>;
|
|
254
|
-
if (typeof record.$ref === "string") {
|
|
255
|
-
const match = record.$ref.match(/^#\/components\/schemas\/(.+)$/);
|
|
256
|
-
if (match?.[1]) refs.add(match[1]);
|
|
257
|
-
}
|
|
258
|
-
Object.values(record).forEach(visit);
|
|
259
|
-
};
|
|
260
|
-
visit(value);
|
|
261
|
-
return refs;
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
const createModelSchema = (modelRef: ConstantCls): OpenApiSchema => {
|
|
265
|
-
const fields = (modelRef as { [FIELD_META]?: Record<string, ConstantField> })[FIELD_META] ?? {};
|
|
266
|
-
const properties: Record<string, OpenApiSchema> = {};
|
|
267
|
-
const required: string[] = [];
|
|
268
|
-
for (const [key, field] of Object.entries(fields)) {
|
|
269
|
-
const props = field.getProps();
|
|
270
|
-
properties[key] = createFieldSchema(field);
|
|
271
|
-
if (!props.nullable) required.push(key);
|
|
272
|
-
}
|
|
273
|
-
return {
|
|
274
|
-
type: "object",
|
|
275
|
-
properties,
|
|
276
|
-
...(required.length ? { required } : {}),
|
|
277
|
-
additionalProperties: false,
|
|
278
|
-
};
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
const createFieldSchema = (field: ConstantField): OpenApiSchema => {
|
|
282
|
-
const props = field.getProps();
|
|
283
|
-
const schema = props.enum ? createInlineEnumSchema([...props.enum.values]) : createFieldRefSchema(props);
|
|
284
|
-
return withNullable(withArrayDepth(schema, props.arrDepth), props.nullable);
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
const createFieldRefSchema = (props: ReturnType<ConstantField["getProps"]>): OpenApiSchema => {
|
|
288
|
-
if (props.isMap) {
|
|
289
|
-
const [valueRef, valueArrDepth] = getNonArrayModel(props.of as Cls | Cls[]);
|
|
290
|
-
return {
|
|
291
|
-
type: "object",
|
|
292
|
-
additionalProperties: withArrayDepth(createModelRefSchema(valueRef as Cls), valueArrDepth),
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
return createModelRefSchema(props.modelRef as Cls);
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
const createModelRefSchema = (modelRef: Cls): OpenApiSchema => {
|
|
299
|
-
if (PrimitiveRegistry.has(modelRef))
|
|
300
|
-
return createPrimitiveSchema(PrimitiveRegistry.getName(modelRef as typeof PrimitiveScalar));
|
|
301
|
-
return { $ref: `#/components/schemas/${ConstantRegistry.getModelName(modelRef)}` };
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
const createPrimitiveSchema = (refName: string): OpenApiSchema => {
|
|
305
|
-
switch (refName) {
|
|
306
|
-
case "Boolean":
|
|
307
|
-
return { type: "boolean" };
|
|
308
|
-
case "Date":
|
|
309
|
-
return { type: "string", format: "date-time" };
|
|
310
|
-
case "Float":
|
|
311
|
-
return { type: "number" };
|
|
312
|
-
case "ID":
|
|
313
|
-
return { type: "string", pattern: "^[0-9a-fA-F]{24}$" };
|
|
314
|
-
case "Int":
|
|
315
|
-
return { type: "integer" };
|
|
316
|
-
case "Upload":
|
|
317
|
-
return { type: "string", format: "binary" };
|
|
318
|
-
case "Any":
|
|
319
|
-
return {};
|
|
320
|
-
case "String":
|
|
321
|
-
default:
|
|
322
|
-
return { type: "string" };
|
|
323
|
-
}
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
const createEnumSchema = (refName: string): OpenApiSchema => {
|
|
327
|
-
const enumRef = ConstantRegistry.enum.get(refName);
|
|
328
|
-
if (!enumRef) return { type: "string", "x-akan-enum": refName };
|
|
329
|
-
return createInlineEnumSchema([...enumRef.values]);
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
const createInlineEnumSchema = (values: unknown[]): OpenApiSchema => ({
|
|
333
|
-
type: values.every((value) => typeof value === "number") ? "number" : "string",
|
|
334
|
-
enum: values,
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
const withArrayDepth = (schema: OpenApiSchema, arrDepth: number): OpenApiSchema => {
|
|
338
|
-
let current = schema;
|
|
339
|
-
for (let idx = 0; idx < arrDepth; idx++) current = { type: "array", items: current };
|
|
340
|
-
return current;
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
const withNullable = (schema: OpenApiSchema, nullable: boolean): OpenApiSchema =>
|
|
344
|
-
nullable ? { anyOf: [schema, { type: "null" }] } : schema;
|
|
345
|
-
|
|
346
207
|
const getProtectedGuards = (guards?: string[]) =>
|
|
347
208
|
(guards ?? []).filter((guard) => guard !== "None" && guard !== "Public");
|
|
348
209
|
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { type Cls, FIELD_META, getNonArrayModel, PrimitiveRegistry, type PrimitiveScalar } from "akanjs/base";
|
|
2
|
+
import { type ConstantCls, type ConstantField, ConstantRegistry, type ConstantType } from "akanjs/constant";
|
|
3
|
+
import type { SerializedArg, SerializedReturns } from "../types";
|
|
4
|
+
|
|
5
|
+
export type JsonSchema = Record<string, unknown>;
|
|
6
|
+
|
|
7
|
+
export interface JsonSchemaBuilderOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Where a model `$ref` points. OpenAPI collects every model under `components/schemas`, while MCP requires each
|
|
10
|
+
* tool schema to resolve on its own and so embeds its models in a per-schema `$defs`.
|
|
11
|
+
*/
|
|
12
|
+
refPrefix?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface JsonSchemaModelOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Drops `hidden` and `secret` fields. `SignalContext.resolveReturn` strips both from every response, so naming
|
|
18
|
+
* them describes a value the caller can never read — and on a model like `user` the names are themselves the
|
|
19
|
+
* leak: `password`, `accountId`, `phone` published as readable properties of the model. This is the one place a
|
|
20
|
+
* field the framework blocks on every value path is still visible, so it is scoped to schemas that describe a
|
|
21
|
+
* *response*. A request body is a different shape and legitimately carries both.
|
|
22
|
+
*/
|
|
23
|
+
readable?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Turns serialized signal metadata and Akan constants into JSON Schema (2020-12 by default, which is also what
|
|
28
|
+
* OpenAPI 3.1 uses). One builder per output dialect target — `new` it at the call site, it holds no shared state.
|
|
29
|
+
*/
|
|
30
|
+
export class JsonSchemaBuilder {
|
|
31
|
+
readonly #refPrefix: string;
|
|
32
|
+
constructor({ refPrefix = "#/components/schemas/" }: JsonSchemaBuilderOptions = {}) {
|
|
33
|
+
this.#refPrefix = refPrefix;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
arg(arg: SerializedArg): JsonSchema {
|
|
37
|
+
const schema = arg.enum ? this.#enum(arg.enum) : this.#ref(arg.refName, arg.modelType);
|
|
38
|
+
return JsonSchemaBuilder.#nullable(JsonSchemaBuilder.#arrayed(schema, arg.arrDepth ?? 0), !!arg.nullable);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
upload(arg: SerializedArg): JsonSchema {
|
|
42
|
+
const fileSchema = { type: "string", format: "binary" };
|
|
43
|
+
return JsonSchemaBuilder.#nullable(JsonSchemaBuilder.#arrayed(fileSchema, arg.arrDepth ?? 0), !!arg.nullable);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
returns(returns: SerializedReturns): JsonSchema {
|
|
47
|
+
return JsonSchemaBuilder.#nullable(
|
|
48
|
+
JsonSchemaBuilder.#arrayed(this.#ref(returns.refName, returns.modelType), returns.arrDepth ?? 0),
|
|
49
|
+
!!returns.nullable,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
model(modelRef: ConstantCls, { readable = false }: JsonSchemaModelOptions = {}): JsonSchema {
|
|
54
|
+
const fields = (modelRef as { [FIELD_META]?: Record<string, ConstantField> })[FIELD_META] ?? {};
|
|
55
|
+
const properties: Record<string, JsonSchema> = {};
|
|
56
|
+
const required: string[] = [];
|
|
57
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
58
|
+
const props = field.getProps();
|
|
59
|
+
if (readable && (props.fieldType === "hidden" || props.fieldType === "secret")) continue;
|
|
60
|
+
properties[key] = this.#field(field);
|
|
61
|
+
if (!props.nullable) required.push(key);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties,
|
|
66
|
+
...(required.length ? { required } : {}),
|
|
67
|
+
additionalProperties: false,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Every registered model, keyed by schema name. Callers narrow this with `referencedSchemas`. */
|
|
72
|
+
allModelSchemas(options: JsonSchemaModelOptions = {}): Record<string, JsonSchema> {
|
|
73
|
+
const schemas: Record<string, JsonSchema> = {};
|
|
74
|
+
for (const [, database] of ConstantRegistry.database.entries()) {
|
|
75
|
+
[database.input, database.object, database.full, database.light, database.insight].forEach((modelRef) => {
|
|
76
|
+
schemas[ConstantRegistry.getModelName(modelRef)] = this.model(modelRef, options);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
for (const [, scalar] of ConstantRegistry.scalar.entries()) {
|
|
80
|
+
schemas[ConstantRegistry.getModelName(scalar.model)] = this.model(scalar.model, options);
|
|
81
|
+
}
|
|
82
|
+
return schemas;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The models `seed` references, plus everything those transitively reference, sorted by name.
|
|
87
|
+
*
|
|
88
|
+
* `allSchemas` is derived from the registry, so a caller that narrows many seeds against the same registry —
|
|
89
|
+
* MCP builds one per tool schema and one per output schema — passes its own copy rather than rebuilding every
|
|
90
|
+
* registered model each time. Holding it here instead would make this builder stateful, and the one in
|
|
91
|
+
* `openapi.ts` lives at module scope for the life of the process.
|
|
92
|
+
*/
|
|
93
|
+
referencedSchemas(seed: unknown, allSchemas: Record<string, JsonSchema> = this.allModelSchemas()) {
|
|
94
|
+
const referencedNames = this.collectRefNames(seed);
|
|
95
|
+
const pending = [...referencedNames];
|
|
96
|
+
for (let idx = 0; idx < pending.length; idx++) {
|
|
97
|
+
const schemaName = pending[idx];
|
|
98
|
+
if (!schemaName) continue;
|
|
99
|
+
const schema = allSchemas[schemaName];
|
|
100
|
+
if (!schema) continue;
|
|
101
|
+
for (const nestedName of this.collectRefNames(schema)) {
|
|
102
|
+
if (referencedNames.has(nestedName)) continue;
|
|
103
|
+
referencedNames.add(nestedName);
|
|
104
|
+
pending.push(nestedName);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return Object.fromEntries(
|
|
108
|
+
[...referencedNames]
|
|
109
|
+
.sort((a, b) => a.localeCompare(b))
|
|
110
|
+
.flatMap((name) => (allSchemas[name] ? ([[name, allSchemas[name]]] as const) : [])),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
collectRefNames(value: unknown): Set<string> {
|
|
115
|
+
const refs = new Set<string>();
|
|
116
|
+
const visit = (current: unknown) => {
|
|
117
|
+
if (!current || typeof current !== "object") return;
|
|
118
|
+
if (Array.isArray(current)) {
|
|
119
|
+
current.forEach(visit);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const record = current as Record<string, unknown>;
|
|
123
|
+
if (typeof record.$ref === "string") {
|
|
124
|
+
const name = this.#refName(record.$ref);
|
|
125
|
+
if (name) refs.add(name);
|
|
126
|
+
}
|
|
127
|
+
Object.values(record).forEach(visit);
|
|
128
|
+
};
|
|
129
|
+
visit(value);
|
|
130
|
+
return refs;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#refName(ref: string): string | undefined {
|
|
134
|
+
if (!ref.startsWith(this.#refPrefix)) return undefined;
|
|
135
|
+
const name = ref.slice(this.#refPrefix.length);
|
|
136
|
+
return name.length ? name : undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#ref(refName: string, modelType?: ConstantType): JsonSchema {
|
|
140
|
+
if (!modelType) return JsonSchemaBuilder.primitive(refName);
|
|
141
|
+
const modelRef = ConstantRegistry.getModelRef(refName, modelType);
|
|
142
|
+
return { $ref: `${this.#refPrefix}${ConstantRegistry.getModelName(modelRef as Cls)}` };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#modelRef(modelRef: Cls): JsonSchema {
|
|
146
|
+
if (PrimitiveRegistry.has(modelRef))
|
|
147
|
+
return JsonSchemaBuilder.primitive(PrimitiveRegistry.getName(modelRef as typeof PrimitiveScalar));
|
|
148
|
+
return { $ref: `${this.#refPrefix}${ConstantRegistry.getModelName(modelRef)}` };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#field(field: ConstantField): JsonSchema {
|
|
152
|
+
const props = field.getProps();
|
|
153
|
+
const schema = props.enum ? JsonSchemaBuilder.#inlineEnum([...props.enum.values]) : this.#fieldRef(props);
|
|
154
|
+
return JsonSchemaBuilder.#nullable(JsonSchemaBuilder.#arrayed(schema, props.arrDepth), props.nullable);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#fieldRef(props: ReturnType<ConstantField["getProps"]>): JsonSchema {
|
|
158
|
+
if (props.isMap) {
|
|
159
|
+
const [valueRef, valueArrDepth] = getNonArrayModel(props.of as Cls | Cls[]);
|
|
160
|
+
return {
|
|
161
|
+
type: "object",
|
|
162
|
+
additionalProperties: JsonSchemaBuilder.#arrayed(this.#modelRef(valueRef as Cls), valueArrDepth),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return this.#modelRef(props.modelRef as Cls);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#enum(refName: string): JsonSchema {
|
|
169
|
+
const enumRef = ConstantRegistry.enum.get(refName);
|
|
170
|
+
if (!enumRef) return { type: "string", "x-akan-enum": refName };
|
|
171
|
+
return JsonSchemaBuilder.#inlineEnum([...enumRef.values]);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
static primitive(refName: string): JsonSchema {
|
|
175
|
+
switch (refName) {
|
|
176
|
+
case "Boolean":
|
|
177
|
+
return { type: "boolean" };
|
|
178
|
+
case "Date":
|
|
179
|
+
return { type: "string", format: "date-time" };
|
|
180
|
+
case "Float":
|
|
181
|
+
return { type: "number" };
|
|
182
|
+
case "ID":
|
|
183
|
+
return { type: "string", pattern: "^[0-9a-fA-F]{24}$" };
|
|
184
|
+
case "Int":
|
|
185
|
+
return { type: "integer" };
|
|
186
|
+
case "Upload":
|
|
187
|
+
return { type: "string", format: "binary" };
|
|
188
|
+
case "Any":
|
|
189
|
+
return {};
|
|
190
|
+
default:
|
|
191
|
+
return { type: "string" };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
static #inlineEnum(values: unknown[]): JsonSchema {
|
|
196
|
+
return {
|
|
197
|
+
type: values.every((value) => typeof value === "number") ? "number" : "string",
|
|
198
|
+
enum: values,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
static #arrayed(schema: JsonSchema, arrDepth: number): JsonSchema {
|
|
203
|
+
let current = schema;
|
|
204
|
+
for (let idx = 0; idx < arrDepth; idx++) current = { type: "array", items: current };
|
|
205
|
+
return current;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
static #nullable(schema: JsonSchema, nullable: boolean): JsonSchema {
|
|
209
|
+
return nullable ? { anyOf: [schema, { type: "null" }] } : schema;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./JsonSchemaBuilder";
|
package/signal/signalContext.ts
CHANGED
|
@@ -16,18 +16,24 @@ import {
|
|
|
16
16
|
} from "akanjs/constant";
|
|
17
17
|
import type { Adaptor, AdaptorCls, DatabaseService, InjectRegistry, LiveRegistry } from "akanjs/service";
|
|
18
18
|
import type { Internal, InternalCls, InternalInfo, MiddlewareCls } from ".";
|
|
19
|
-
import type { EndpointInfo } from "./endpointInfo";
|
|
19
|
+
import type { EndpointInfo, EndpointType } from "./endpointInfo";
|
|
20
20
|
import { Exception } from "./exception";
|
|
21
|
+
import type { Guard, GuardCls } from "./guard";
|
|
22
|
+
|
|
23
|
+
import { Msg } from "./mcp/Msg";
|
|
21
24
|
import { isTraceEnabled, runWithTrace, SignalTrace, traceSpan } from "./trace";
|
|
22
25
|
|
|
23
26
|
export type SignalTransportType = "http" | "websocket";
|
|
24
27
|
|
|
28
|
+
const httpEndpointTypes = new Set<EndpointType>(["query", "mutation", "prompt"]);
|
|
29
|
+
|
|
25
30
|
interface WebSocketRequest {
|
|
26
31
|
ws: Bun.ServerWebSocket<unknown>;
|
|
27
32
|
data: unknown[];
|
|
28
33
|
eventType: WebSocketEventType;
|
|
29
34
|
}
|
|
30
35
|
type RuntimeRecord = Record<string, unknown>;
|
|
36
|
+
type MiddlewareHandler = (context: SignalContext, next: () => Promise<unknown>) => PromiseOrObject<unknown>;
|
|
31
37
|
|
|
32
38
|
interface ExceptionLike {
|
|
33
39
|
statusCode: number;
|
|
@@ -70,6 +76,7 @@ export class SignalContext<
|
|
|
70
76
|
env,
|
|
71
77
|
live,
|
|
72
78
|
middleware,
|
|
79
|
+
ctx,
|
|
73
80
|
}: {
|
|
74
81
|
endpointInfo: EndpointInfo;
|
|
75
82
|
adaptor: Adaptor;
|
|
@@ -77,12 +84,19 @@ export class SignalContext<
|
|
|
77
84
|
env: Env;
|
|
78
85
|
live: LiveRegistry;
|
|
79
86
|
middleware: Map<string, MiddlewareCls>;
|
|
87
|
+
/**
|
|
88
|
+
* Runs the endpoint against a caller-built context instead of one derived from the request. MCP needs it:
|
|
89
|
+
* its arguments arrive as one named object rather than in a URL, but every guard, middleware and
|
|
90
|
+
* internalArg reads the request through this context, so the transport has to stay the same one.
|
|
91
|
+
*/
|
|
92
|
+
ctx?: Ctx;
|
|
80
93
|
},
|
|
81
94
|
) {
|
|
82
95
|
this.key = key;
|
|
83
|
-
this.transport =
|
|
96
|
+
this.transport = httpEndpointTypes.has(endpointInfo.type) ? "http" : "websocket";
|
|
84
97
|
this.endpointInfo = endpointInfo;
|
|
85
|
-
if (
|
|
98
|
+
if (ctx) this.ctx = ctx;
|
|
99
|
+
else if (this.transport === "http") this.ctx = new HttpExecutionContext(reqOrWsReq as Bun.BunRequest) as Ctx;
|
|
86
100
|
else this.ctx = new WebSocketExecutionContext(reqOrWsReq as WebSocketRequest) as Ctx;
|
|
87
101
|
this.adaptor = adaptor;
|
|
88
102
|
this.#registry = registry;
|
|
@@ -114,13 +128,25 @@ export class SignalContext<
|
|
|
114
128
|
}
|
|
115
129
|
return this;
|
|
116
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Guards read everything from the context they are handed and are already required to be side-effect free and
|
|
133
|
+
* safe to re-run — `SignalResolver.revalidateWsRooms` re-runs them outside of any request — so one instance per
|
|
134
|
+
* class serves every call instead of one per guard per request.
|
|
135
|
+
*/
|
|
136
|
+
static #guards = new WeakMap<GuardCls, Guard>();
|
|
137
|
+
static #getGuard(GuardCls: GuardCls): Guard {
|
|
138
|
+
const cached = SignalContext.#guards.get(GuardCls);
|
|
139
|
+
if (cached) return cached;
|
|
140
|
+
const guard = new GuardCls();
|
|
141
|
+
SignalContext.#guards.set(GuardCls, guard);
|
|
142
|
+
return guard;
|
|
143
|
+
}
|
|
117
144
|
async #checkGuards() {
|
|
118
145
|
const guards = this.endpointInfo.signalOption.guards ?? [];
|
|
119
146
|
if (guards.length === 0) return;
|
|
120
147
|
await Promise.all(
|
|
121
148
|
guards.map(async (GuardCls) => {
|
|
122
|
-
const
|
|
123
|
-
const canPass = await guard.canPass(this);
|
|
149
|
+
const canPass = await SignalContext.#getGuard(GuardCls).canPass(this);
|
|
124
150
|
if (!canPass) throw new Exception.Forbidden(`Access denied by guard: ${GuardCls.name}`);
|
|
125
151
|
}),
|
|
126
152
|
);
|
|
@@ -138,6 +164,32 @@ export class SignalContext<
|
|
|
138
164
|
return false;
|
|
139
165
|
}
|
|
140
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Evaluates only the guards marked `static scope = "account"` — the ones that read the caller and nothing
|
|
169
|
+
* else — so a catalogue can hide entries the caller certainly cannot use.
|
|
170
|
+
*
|
|
171
|
+
* **Never an access gate.** An endpoint whose guards are all resource-scoped passes here and is stopped later
|
|
172
|
+
* by `#checkGuards` with the arguments those guards need. Erring visible is deliberate: a resource guard fails
|
|
173
|
+
* closed with no arguments, so evaluating one here would delete every legitimate entry from the listing.
|
|
174
|
+
*/
|
|
175
|
+
async canListForAccount(): Promise<boolean> {
|
|
176
|
+
const guards = (this.endpointInfo.signalOption.guards ?? []).filter((GuardCls) => GuardCls.scope === "account");
|
|
177
|
+
if (guards.length === 0) return true;
|
|
178
|
+
try {
|
|
179
|
+
await this.#withMiddleware(
|
|
180
|
+
async () => {
|
|
181
|
+
for (const GuardCls of guards) {
|
|
182
|
+
if (!(await SignalContext.#getGuard(GuardCls).canPass(this)))
|
|
183
|
+
throw new Exception.Forbidden(`Access denied by guard: ${GuardCls.name}`);
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
{ endpointMiddlewares: false },
|
|
187
|
+
)();
|
|
188
|
+
return true;
|
|
189
|
+
} catch {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
141
193
|
#withMiddleware(
|
|
142
194
|
coreExec: () => Promise<unknown>,
|
|
143
195
|
{ endpointMiddlewares = true }: { endpointMiddlewares?: boolean } = {},
|
|
@@ -151,12 +203,34 @@ export class SignalContext<
|
|
|
151
203
|
for (let i = middlewares.length - 1; i >= 0; i--) {
|
|
152
204
|
const MiddlewareCls = middlewares[i];
|
|
153
205
|
if (!MiddlewareCls) continue;
|
|
154
|
-
const middleware = new MiddlewareCls();
|
|
155
206
|
const currentNext = next;
|
|
156
|
-
next = async () =>
|
|
207
|
+
next = async () =>
|
|
208
|
+
await (await SignalContext.#getMiddlewareHandler(MiddlewareCls, this.getEnv()))(this, currentNext);
|
|
157
209
|
}
|
|
158
210
|
return next;
|
|
159
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* `use(env)` takes no context, so the instance and the handler it returns are a function of `(class, env)` and
|
|
214
|
+
* hold for the life of the process. Building both per request cost an instance, a handler and a closure on every
|
|
215
|
+
* call for every registered middleware — and `Logging` is registered by default.
|
|
216
|
+
*/
|
|
217
|
+
static #middlewareHandlers = new WeakMap<MiddlewareCls, WeakMap<object, Promise<MiddlewareHandler>>>();
|
|
218
|
+
static #getMiddlewareHandler(MiddlewareCls: MiddlewareCls, env: BaseEnv): Promise<MiddlewareHandler> {
|
|
219
|
+
const byEnv =
|
|
220
|
+
SignalContext.#middlewareHandlers.get(MiddlewareCls) ?? new WeakMap<object, Promise<MiddlewareHandler>>();
|
|
221
|
+
SignalContext.#middlewareHandlers.set(MiddlewareCls, byEnv);
|
|
222
|
+
const cached = byEnv.get(env);
|
|
223
|
+
if (cached) return cached;
|
|
224
|
+
|
|
225
|
+
const handler = Promise.resolve(new MiddlewareCls().use(env) as PromiseOrObject<MiddlewareHandler>).catch(
|
|
226
|
+
(error: unknown) => {
|
|
227
|
+
byEnv.delete(env);
|
|
228
|
+
throw error;
|
|
229
|
+
},
|
|
230
|
+
);
|
|
231
|
+
byEnv.set(env, handler);
|
|
232
|
+
return handler;
|
|
233
|
+
}
|
|
160
234
|
async exec() {
|
|
161
235
|
if (!this.trace) return await this.#exec();
|
|
162
236
|
return await runWithTrace(this.trace, async () => {
|
|
@@ -190,9 +264,11 @@ export class SignalContext<
|
|
|
190
264
|
);
|
|
191
265
|
};
|
|
192
266
|
const next = this.#withMiddleware(coreExec);
|
|
193
|
-
const
|
|
267
|
+
const raw = this.trace ? await traceSpan("execChain", () => next()) : await next();
|
|
194
268
|
if (this.endpointInfo.type === "pubsub") return;
|
|
195
|
-
if (
|
|
269
|
+
if (raw instanceof Response) return raw;
|
|
270
|
+
|
|
271
|
+
const result = this.endpointInfo.type === "prompt" ? Msg.normalize(raw) : raw;
|
|
196
272
|
if (!this.trace) {
|
|
197
273
|
const resolved = await SignalContext.resolveReturn(result, {
|
|
198
274
|
signalContext: this,
|