@truefoundry/trueforge-core 0.0.0 → 0.1.0-rc.0
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/LICENSE +21 -0
- package/dist/agent-session/ITurnResourceResolver.d.ts +69 -0
- package/dist/agent-session/ITurnResourceResolver.d.ts.map +1 -0
- package/dist/agent-session/ITurnResourceResolver.js +19 -0
- package/dist/agent-session/ITurnResourceResolver.js.map +1 -0
- package/dist/agent-session/ITurnResourceResolver.mjs +1 -0
- package/dist/agent-session/ITurnResourceResolver.mjs.map +1 -0
- package/dist/agent-session/SessionHandle.d.ts +81 -0
- package/dist/agent-session/SessionHandle.d.ts.map +1 -0
- package/dist/agent-session/SessionHandle.js +392 -0
- package/dist/agent-session/SessionHandle.js.map +1 -0
- package/dist/agent-session/SessionHandle.mjs +371 -0
- package/dist/agent-session/SessionHandle.mjs.map +1 -0
- package/dist/agent-session/Sessions.d.ts +25 -0
- package/dist/agent-session/Sessions.d.ts.map +1 -0
- package/dist/agent-session/Sessions.js +71 -0
- package/dist/agent-session/Sessions.js.map +1 -0
- package/dist/agent-session/Sessions.mjs +46 -0
- package/dist/agent-session/Sessions.mjs.map +1 -0
- package/dist/agent-session/TurnHandle.d.ts +88 -0
- package/dist/agent-session/TurnHandle.d.ts.map +1 -0
- package/dist/agent-session/TurnHandle.js +490 -0
- package/dist/agent-session/TurnHandle.js.map +1 -0
- package/dist/agent-session/TurnHandle.mjs +469 -0
- package/dist/agent-session/TurnHandle.mjs.map +1 -0
- package/dist/agent-session/TurnResourceResolver.d.ts +154 -0
- package/dist/agent-session/TurnResourceResolver.d.ts.map +1 -0
- package/dist/agent-session/TurnResourceResolver.js +178 -0
- package/dist/agent-session/TurnResourceResolver.js.map +1 -0
- package/dist/agent-session/TurnResourceResolver.mjs +153 -0
- package/dist/agent-session/TurnResourceResolver.mjs.map +1 -0
- package/dist/agent-session/builtinsFromSpec.d.ts +19 -0
- package/dist/agent-session/builtinsFromSpec.d.ts.map +1 -0
- package/dist/agent-session/builtinsFromSpec.js +73 -0
- package/dist/agent-session/builtinsFromSpec.js.map +1 -0
- package/dist/agent-session/builtinsFromSpec.mjs +48 -0
- package/dist/agent-session/builtinsFromSpec.mjs.map +1 -0
- package/dist/agent-session/index.d.ts +29 -0
- package/dist/agent-session/index.d.ts.map +1 -0
- package/dist/agent-session/index.js +117 -0
- package/dist/agent-session/index.js.map +1 -0
- package/dist/agent-session/index.mjs +84 -0
- package/dist/agent-session/index.mjs.map +1 -0
- package/dist/agent-session/models/SessionRecord.d.ts +34 -0
- package/dist/agent-session/models/SessionRecord.d.ts.map +1 -0
- package/dist/agent-session/models/SessionRecord.js +19 -0
- package/dist/agent-session/models/SessionRecord.js.map +1 -0
- package/dist/agent-session/models/SessionRecord.mjs +1 -0
- package/dist/agent-session/models/SessionRecord.mjs.map +1 -0
- package/dist/agent-session/models/TurnRecord.d.ts +37 -0
- package/dist/agent-session/models/TurnRecord.d.ts.map +1 -0
- package/dist/agent-session/models/TurnRecord.js +31 -0
- package/dist/agent-session/models/TurnRecord.js.map +1 -0
- package/dist/agent-session/models/TurnRecord.mjs +6 -0
- package/dist/agent-session/models/TurnRecord.mjs.map +1 -0
- package/dist/agent-session/schemas/agentSpec.d.ts +142 -0
- package/dist/agent-session/schemas/agentSpec.d.ts.map +1 -0
- package/dist/agent-session/schemas/agentSpec.js +170 -0
- package/dist/agent-session/schemas/agentSpec.js.map +1 -0
- package/dist/agent-session/schemas/agentSpec.mjs +154 -0
- package/dist/agent-session/schemas/agentSpec.mjs.map +1 -0
- package/dist/agent-session/schemas/events.d.ts +1037 -0
- package/dist/agent-session/schemas/events.d.ts.map +1 -0
- package/dist/agent-session/schemas/events.js +80 -0
- package/dist/agent-session/schemas/events.js.map +1 -0
- package/dist/agent-session/schemas/events.mjs +69 -0
- package/dist/agent-session/schemas/events.mjs.map +1 -0
- package/dist/agent-session/schemas/pagination.d.ts +9 -0
- package/dist/agent-session/schemas/pagination.d.ts.map +1 -0
- package/dist/agent-session/schemas/pagination.js +36 -0
- package/dist/agent-session/schemas/pagination.js.map +1 -0
- package/dist/agent-session/schemas/pagination.mjs +11 -0
- package/dist/agent-session/schemas/pagination.mjs.map +1 -0
- package/dist/agent-session/schemas/session.d.ts +319 -0
- package/dist/agent-session/schemas/session.d.ts.map +1 -0
- package/dist/agent-session/schemas/session.js +58 -0
- package/dist/agent-session/schemas/session.js.map +1 -0
- package/dist/agent-session/schemas/session.mjs +30 -0
- package/dist/agent-session/schemas/session.mjs.map +1 -0
- package/dist/agent-session/schemas/turn.d.ts +494 -0
- package/dist/agent-session/schemas/turn.d.ts.map +1 -0
- package/dist/agent-session/schemas/turn.js +131 -0
- package/dist/agent-session/schemas/turn.js.map +1 -0
- package/dist/agent-session/schemas/turn.mjs +102 -0
- package/dist/agent-session/schemas/turn.mjs.map +1 -0
- package/dist/agent-session/store/ISessionStore.d.ts +310 -0
- package/dist/agent-session/store/ISessionStore.d.ts.map +1 -0
- package/dist/agent-session/store/ISessionStore.js +19 -0
- package/dist/agent-session/store/ISessionStore.js.map +1 -0
- package/dist/agent-session/store/ISessionStore.mjs +1 -0
- package/dist/agent-session/store/ISessionStore.mjs.map +1 -0
- package/dist/agent-session/store/InMemorySessionStore.d.ts +55 -0
- package/dist/agent-session/store/InMemorySessionStore.d.ts.map +1 -0
- package/dist/agent-session/store/InMemorySessionStore.js +475 -0
- package/dist/agent-session/store/InMemorySessionStore.js.map +1 -0
- package/dist/agent-session/store/InMemorySessionStore.mjs +461 -0
- package/dist/agent-session/store/InMemorySessionStore.mjs.map +1 -0
- package/dist/agent-session/store/OffsetPageToken.d.ts +13 -0
- package/dist/agent-session/store/OffsetPageToken.d.ts.map +1 -0
- package/dist/agent-session/store/OffsetPageToken.js +60 -0
- package/dist/agent-session/store/OffsetPageToken.js.map +1 -0
- package/dist/agent-session/store/OffsetPageToken.mjs +32 -0
- package/dist/agent-session/store/OffsetPageToken.mjs.map +1 -0
- package/dist/agent-session/store/PageToken.d.ts +4 -0
- package/dist/agent-session/store/PageToken.d.ts.map +1 -0
- package/dist/agent-session/store/PageToken.js +45 -0
- package/dist/agent-session/store/PageToken.js.map +1 -0
- package/dist/agent-session/store/PageToken.mjs +19 -0
- package/dist/agent-session/store/PageToken.mjs.map +1 -0
- package/dist/agent-session/store/SessionEventPageToken.d.ts +14 -0
- package/dist/agent-session/store/SessionEventPageToken.d.ts.map +1 -0
- package/dist/agent-session/store/SessionEventPageToken.js +70 -0
- package/dist/agent-session/store/SessionEventPageToken.js.map +1 -0
- package/dist/agent-session/store/SessionEventPageToken.mjs +42 -0
- package/dist/agent-session/store/SessionEventPageToken.mjs.map +1 -0
- package/dist/agent-session/store/SessionListPageToken.d.ts +24 -0
- package/dist/agent-session/store/SessionListPageToken.d.ts.map +1 -0
- package/dist/agent-session/store/SessionListPageToken.js +66 -0
- package/dist/agent-session/store/SessionListPageToken.js.map +1 -0
- package/dist/agent-session/store/SessionListPageToken.mjs +38 -0
- package/dist/agent-session/store/SessionListPageToken.mjs.map +1 -0
- package/dist/agent-session/store/SessionStoreErrors.d.ts +43 -0
- package/dist/agent-session/store/SessionStoreErrors.d.ts.map +1 -0
- package/dist/agent-session/store/SessionStoreErrors.js +126 -0
- package/dist/agent-session/store/SessionStoreErrors.js.map +1 -0
- package/dist/agent-session/store/SessionStoreErrors.mjs +92 -0
- package/dist/agent-session/store/SessionStoreErrors.mjs.map +1 -0
- package/dist/agent-session/store/assertCreateTurnThreadDelta.d.ts +18 -0
- package/dist/agent-session/store/assertCreateTurnThreadDelta.d.ts.map +1 -0
- package/dist/agent-session/store/assertCreateTurnThreadDelta.js +67 -0
- package/dist/agent-session/store/assertCreateTurnThreadDelta.js.map +1 -0
- package/dist/agent-session/store/assertCreateTurnThreadDelta.mjs +42 -0
- package/dist/agent-session/store/assertCreateTurnThreadDelta.mjs.map +1 -0
- package/dist/core/InstructionBuilder.d.ts +71 -0
- package/dist/core/InstructionBuilder.d.ts.map +1 -0
- package/dist/core/InstructionBuilder.js +95 -0
- package/dist/core/InstructionBuilder.js.map +1 -0
- package/dist/core/InstructionBuilder.mjs +69 -0
- package/dist/core/InstructionBuilder.mjs.map +1 -0
- package/dist/core/capabilities/AgentCapability.d.ts +32 -0
- package/dist/core/capabilities/AgentCapability.d.ts.map +1 -0
- package/dist/core/capabilities/AgentCapability.js +19 -0
- package/dist/core/capabilities/AgentCapability.js.map +1 -0
- package/dist/core/capabilities/AgentCapability.mjs +1 -0
- package/dist/core/capabilities/AgentCapability.mjs.map +1 -0
- package/dist/core/capabilities/AgentContextProcessor.d.ts +30 -0
- package/dist/core/capabilities/AgentContextProcessor.d.ts.map +1 -0
- package/dist/core/capabilities/AgentContextProcessor.js +19 -0
- package/dist/core/capabilities/AgentContextProcessor.js.map +1 -0
- package/dist/core/capabilities/AgentContextProcessor.mjs +1 -0
- package/dist/core/capabilities/AgentContextProcessor.mjs.map +1 -0
- package/dist/core/capabilities/ToolResponseProcessor.d.ts +12 -0
- package/dist/core/capabilities/ToolResponseProcessor.d.ts.map +1 -0
- package/dist/core/capabilities/ToolResponseProcessor.js +19 -0
- package/dist/core/capabilities/ToolResponseProcessor.js.map +1 -0
- package/dist/core/capabilities/ToolResponseProcessor.mjs +1 -0
- package/dist/core/capabilities/ToolResponseProcessor.mjs.map +1 -0
- package/dist/core/capabilities/builtins/AskUserQuestion.d.ts +11 -0
- package/dist/core/capabilities/builtins/AskUserQuestion.d.ts.map +1 -0
- package/dist/core/capabilities/builtins/AskUserQuestion.js +90 -0
- package/dist/core/capabilities/builtins/AskUserQuestion.js.map +1 -0
- package/dist/core/capabilities/builtins/AskUserQuestion.mjs +64 -0
- package/dist/core/capabilities/builtins/AskUserQuestion.mjs.map +1 -0
- package/dist/core/capabilities/builtins/ContextCompaction.d.ts +23 -0
- package/dist/core/capabilities/builtins/ContextCompaction.d.ts.map +1 -0
- package/dist/core/capabilities/builtins/ContextCompaction.js +235 -0
- package/dist/core/capabilities/builtins/ContextCompaction.js.map +1 -0
- package/dist/core/capabilities/builtins/ContextCompaction.mjs +208 -0
- package/dist/core/capabilities/builtins/ContextCompaction.mjs.map +1 -0
- package/dist/core/capabilities/builtins/CurrentDateTime.d.ts +17 -0
- package/dist/core/capabilities/builtins/CurrentDateTime.d.ts.map +1 -0
- package/dist/core/capabilities/builtins/CurrentDateTime.js +75 -0
- package/dist/core/capabilities/builtins/CurrentDateTime.js.map +1 -0
- package/dist/core/capabilities/builtins/CurrentDateTime.mjs +47 -0
- package/dist/core/capabilities/builtins/CurrentDateTime.mjs.map +1 -0
- package/dist/core/capabilities/builtins/DynamicSubAgents.d.ts +39 -0
- package/dist/core/capabilities/builtins/DynamicSubAgents.d.ts.map +1 -0
- package/dist/core/capabilities/builtins/DynamicSubAgents.js +176 -0
- package/dist/core/capabilities/builtins/DynamicSubAgents.js.map +1 -0
- package/dist/core/capabilities/builtins/DynamicSubAgents.mjs +134 -0
- package/dist/core/capabilities/builtins/DynamicSubAgents.mjs.map +1 -0
- package/dist/core/capabilities/builtins/LargeToolResponse.d.ts +40 -0
- package/dist/core/capabilities/builtins/LargeToolResponse.d.ts.map +1 -0
- package/dist/core/capabilities/builtins/LargeToolResponse.js +225 -0
- package/dist/core/capabilities/builtins/LargeToolResponse.js.map +1 -0
- package/dist/core/capabilities/builtins/LargeToolResponse.mjs +186 -0
- package/dist/core/capabilities/builtins/LargeToolResponse.mjs.map +1 -0
- package/dist/core/capabilities/builtins/OpenUI.d.ts +31 -0
- package/dist/core/capabilities/builtins/OpenUI.d.ts.map +1 -0
- package/dist/core/capabilities/builtins/OpenUI.js +376 -0
- package/dist/core/capabilities/builtins/OpenUI.js.map +1 -0
- package/dist/core/capabilities/builtins/OpenUI.mjs +326 -0
- package/dist/core/capabilities/builtins/OpenUI.mjs.map +1 -0
- package/dist/core/errors.d.ts +33 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +88 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/errors.mjs +57 -0
- package/dist/core/errors.mjs.map +1 -0
- package/dist/core/events/PassthroughEvents.d.ts +30 -0
- package/dist/core/events/PassthroughEvents.d.ts.map +1 -0
- package/dist/core/events/PassthroughEvents.js +19 -0
- package/dist/core/events/PassthroughEvents.js.map +1 -0
- package/dist/core/events/PassthroughEvents.mjs +1 -0
- package/dist/core/events/PassthroughEvents.mjs.map +1 -0
- package/dist/core/events/schema.d.ts +1103 -0
- package/dist/core/events/schema.d.ts.map +1 -0
- package/dist/core/events/schema.js +337 -0
- package/dist/core/events/schema.js.map +1 -0
- package/dist/core/events/schema.mjs +283 -0
- package/dist/core/events/schema.mjs.map +1 -0
- package/dist/core/index.d.ts +60 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +209 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +144 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/core/llm/ILLM.d.ts +15 -0
- package/dist/core/llm/ILLM.d.ts.map +1 -0
- package/dist/core/llm/ILLM.js +19 -0
- package/dist/core/llm/ILLM.js.map +1 -0
- package/dist/core/llm/ILLM.mjs +1 -0
- package/dist/core/llm/ILLM.mjs.map +1 -0
- package/dist/core/llm/LLMTypes.d.ts +369 -0
- package/dist/core/llm/LLMTypes.d.ts.map +1 -0
- package/dist/core/llm/LLMTypes.js +170 -0
- package/dist/core/llm/LLMTypes.js.map +1 -0
- package/dist/core/llm/LLMTypes.mjs +133 -0
- package/dist/core/llm/LLMTypes.mjs.map +1 -0
- package/dist/core/llm/VercelAILLM.d.ts +228 -0
- package/dist/core/llm/VercelAILLM.d.ts.map +1 -0
- package/dist/core/llm/VercelAILLM.js +1042 -0
- package/dist/core/llm/VercelAILLM.js.map +1 -0
- package/dist/core/llm/VercelAILLM.mjs +999 -0
- package/dist/core/llm/VercelAILLM.mjs.map +1 -0
- package/dist/core/llm/openaiSchemas.d.ts +190 -0
- package/dist/core/llm/openaiSchemas.d.ts.map +1 -0
- package/dist/core/llm/openaiSchemas.js +144 -0
- package/dist/core/llm/openaiSchemas.js.map +1 -0
- package/dist/core/llm/openaiSchemas.mjs +107 -0
- package/dist/core/llm/openaiSchemas.mjs.map +1 -0
- package/dist/core/llm/responseFormat.d.ts +25 -0
- package/dist/core/llm/responseFormat.d.ts.map +1 -0
- package/dist/core/llm/responseFormat.js +58 -0
- package/dist/core/llm/responseFormat.js.map +1 -0
- package/dist/core/llm/responseFormat.mjs +32 -0
- package/dist/core/llm/responseFormat.mjs.map +1 -0
- package/dist/core/llm/toOpenAIChatMessage.d.ts +11 -0
- package/dist/core/llm/toOpenAIChatMessage.d.ts.map +1 -0
- package/dist/core/llm/toOpenAIChatMessage.js +95 -0
- package/dist/core/llm/toOpenAIChatMessage.js.map +1 -0
- package/dist/core/llm/toOpenAIChatMessage.mjs +70 -0
- package/dist/core/llm/toOpenAIChatMessage.mjs.map +1 -0
- package/dist/core/llm/usage.d.ts +4 -0
- package/dist/core/llm/usage.d.ts.map +1 -0
- package/dist/core/llm/usage.js +52 -0
- package/dist/core/llm/usage.js.map +1 -0
- package/dist/core/llm/usage.mjs +26 -0
- package/dist/core/llm/usage.mjs.map +1 -0
- package/dist/core/mcp/ClientSideTool.d.ts +10 -0
- package/dist/core/mcp/ClientSideTool.d.ts.map +1 -0
- package/dist/core/mcp/ClientSideTool.js +56 -0
- package/dist/core/mcp/ClientSideTool.js.map +1 -0
- package/dist/core/mcp/ClientSideTool.mjs +31 -0
- package/dist/core/mcp/ClientSideTool.mjs.map +1 -0
- package/dist/core/mcp/IMCPServer.d.ts +78 -0
- package/dist/core/mcp/IMCPServer.d.ts.map +1 -0
- package/dist/core/mcp/IMCPServer.js +63 -0
- package/dist/core/mcp/IMCPServer.js.map +1 -0
- package/dist/core/mcp/IMCPServer.mjs +33 -0
- package/dist/core/mcp/IMCPServer.mjs.map +1 -0
- package/dist/core/mcp/LocalToolMCP.d.ts +39 -0
- package/dist/core/mcp/LocalToolMCP.d.ts.map +1 -0
- package/dist/core/mcp/LocalToolMCP.js +121 -0
- package/dist/core/mcp/LocalToolMCP.js.map +1 -0
- package/dist/core/mcp/LocalToolMCP.mjs +98 -0
- package/dist/core/mcp/LocalToolMCP.mjs.map +1 -0
- package/dist/core/mcp/RemoteMCP.d.ts +67 -0
- package/dist/core/mcp/RemoteMCP.d.ts.map +1 -0
- package/dist/core/mcp/RemoteMCP.js +244 -0
- package/dist/core/mcp/RemoteMCP.js.map +1 -0
- package/dist/core/mcp/RemoteMCP.mjs +222 -0
- package/dist/core/mcp/RemoteMCP.mjs.map +1 -0
- package/dist/core/mcp/ToolSelectorPolicy.d.ts +52 -0
- package/dist/core/mcp/ToolSelectorPolicy.d.ts.map +1 -0
- package/dist/core/mcp/ToolSelectorPolicy.js +108 -0
- package/dist/core/mcp/ToolSelectorPolicy.js.map +1 -0
- package/dist/core/mcp/ToolSelectorPolicy.mjs +90 -0
- package/dist/core/mcp/ToolSelectorPolicy.mjs.map +1 -0
- package/dist/core/mcp/ToolSet.d.ts +31 -0
- package/dist/core/mcp/ToolSet.d.ts.map +1 -0
- package/dist/core/mcp/ToolSet.js +119 -0
- package/dist/core/mcp/ToolSet.js.map +1 -0
- package/dist/core/mcp/ToolSet.mjs +98 -0
- package/dist/core/mcp/ToolSet.mjs.map +1 -0
- package/dist/core/mcp/convertMCPServers.d.ts +22 -0
- package/dist/core/mcp/convertMCPServers.d.ts.map +1 -0
- package/dist/core/mcp/convertMCPServers.js +89 -0
- package/dist/core/mcp/convertMCPServers.js.map +1 -0
- package/dist/core/mcp/convertMCPServers.mjs +64 -0
- package/dist/core/mcp/convertMCPServers.mjs.map +1 -0
- package/dist/core/mcp/executeToolCalls.d.ts +31 -0
- package/dist/core/mcp/executeToolCalls.d.ts.map +1 -0
- package/dist/core/mcp/executeToolCalls.js +168 -0
- package/dist/core/mcp/executeToolCalls.js.map +1 -0
- package/dist/core/mcp/executeToolCalls.mjs +149 -0
- package/dist/core/mcp/executeToolCalls.mjs.map +1 -0
- package/dist/core/mcp/pagination.d.ts +11 -0
- package/dist/core/mcp/pagination.d.ts.map +1 -0
- package/dist/core/mcp/pagination.js +49 -0
- package/dist/core/mcp/pagination.js.map +1 -0
- package/dist/core/mcp/pagination.mjs +24 -0
- package/dist/core/mcp/pagination.mjs.map +1 -0
- package/dist/core/mcp/remoteMcpClient.d.ts +36 -0
- package/dist/core/mcp/remoteMcpClient.d.ts.map +1 -0
- package/dist/core/mcp/remoteMcpClient.js +138 -0
- package/dist/core/mcp/remoteMcpClient.js.map +1 -0
- package/dist/core/mcp/remoteMcpClient.mjs +112 -0
- package/dist/core/mcp/remoteMcpClient.mjs.map +1 -0
- package/dist/core/mcp/toolNames.d.ts +6 -0
- package/dist/core/mcp/toolNames.d.ts.map +1 -0
- package/dist/core/mcp/toolNames.js +59 -0
- package/dist/core/mcp/toolNames.js.map +1 -0
- package/dist/core/mcp/toolNames.mjs +33 -0
- package/dist/core/mcp/toolNames.mjs.map +1 -0
- package/dist/core/mcp/toolSelectors.d.ts +28 -0
- package/dist/core/mcp/toolSelectors.d.ts.map +1 -0
- package/dist/core/mcp/toolSelectors.js +113 -0
- package/dist/core/mcp/toolSelectors.js.map +1 -0
- package/dist/core/mcp/toolSelectors.mjs +78 -0
- package/dist/core/mcp/toolSelectors.mjs.map +1 -0
- package/dist/core/runtime/AgentDefinition.d.ts +23 -0
- package/dist/core/runtime/AgentDefinition.d.ts.map +1 -0
- package/dist/core/runtime/AgentDefinition.js +19 -0
- package/dist/core/runtime/AgentDefinition.js.map +1 -0
- package/dist/core/runtime/AgentDefinition.mjs +1 -0
- package/dist/core/runtime/AgentDefinition.mjs.map +1 -0
- package/dist/core/runtime/AgentThread.d.ts +64 -0
- package/dist/core/runtime/AgentThread.d.ts.map +1 -0
- package/dist/core/runtime/AgentThread.js +1065 -0
- package/dist/core/runtime/AgentThread.js.map +1 -0
- package/dist/core/runtime/AgentThread.mjs +1062 -0
- package/dist/core/runtime/AgentThread.mjs.map +1 -0
- package/dist/core/runtime/AgentThread.types.d.ts +123 -0
- package/dist/core/runtime/AgentThread.types.d.ts.map +1 -0
- package/dist/core/runtime/AgentThread.types.js +46 -0
- package/dist/core/runtime/AgentThread.types.js.map +1 -0
- package/dist/core/runtime/AgentThread.types.mjs +20 -0
- package/dist/core/runtime/AgentThread.types.mjs.map +1 -0
- package/dist/core/runtime/AgentThreadOrchestrator.d.ts +35 -0
- package/dist/core/runtime/AgentThreadOrchestrator.d.ts.map +1 -0
- package/dist/core/runtime/AgentThreadOrchestrator.js +433 -0
- package/dist/core/runtime/AgentThreadOrchestrator.js.map +1 -0
- package/dist/core/runtime/AgentThreadOrchestrator.mjs +418 -0
- package/dist/core/runtime/AgentThreadOrchestrator.mjs.map +1 -0
- package/dist/core/runtime/CreateDynamicSubAgentThread.d.ts +11 -0
- package/dist/core/runtime/CreateDynamicSubAgentThread.d.ts.map +1 -0
- package/dist/core/runtime/CreateDynamicSubAgentThread.js +19 -0
- package/dist/core/runtime/CreateDynamicSubAgentThread.js.map +1 -0
- package/dist/core/runtime/CreateDynamicSubAgentThread.mjs +1 -0
- package/dist/core/runtime/CreateDynamicSubAgentThread.mjs.map +1 -0
- package/dist/core/runtime/DeferredTool.d.ts +34 -0
- package/dist/core/runtime/DeferredTool.d.ts.map +1 -0
- package/dist/core/runtime/DeferredTool.js +321 -0
- package/dist/core/runtime/DeferredTool.js.map +1 -0
- package/dist/core/runtime/DeferredTool.mjs +287 -0
- package/dist/core/runtime/DeferredTool.mjs.map +1 -0
- package/dist/core/runtime/OpenToolCallCloser.d.ts +21 -0
- package/dist/core/runtime/OpenToolCallCloser.d.ts.map +1 -0
- package/dist/core/runtime/OpenToolCallCloser.js +92 -0
- package/dist/core/runtime/OpenToolCallCloser.js.map +1 -0
- package/dist/core/runtime/OpenToolCallCloser.mjs +66 -0
- package/dist/core/runtime/OpenToolCallCloser.mjs.map +1 -0
- package/dist/core/runtime/UserInputMessage.d.ts +16 -0
- package/dist/core/runtime/UserInputMessage.d.ts.map +1 -0
- package/dist/core/runtime/UserInputMessage.js +183 -0
- package/dist/core/runtime/UserInputMessage.js.map +1 -0
- package/dist/core/runtime/UserInputMessage.mjs +157 -0
- package/dist/core/runtime/UserInputMessage.mjs.map +1 -0
- package/dist/core/runtime/contextUsage.d.ts +17 -0
- package/dist/core/runtime/contextUsage.d.ts.map +1 -0
- package/dist/core/runtime/contextUsage.js +56 -0
- package/dist/core/runtime/contextUsage.js.map +1 -0
- package/dist/core/runtime/contextUsage.mjs +28 -0
- package/dist/core/runtime/contextUsage.mjs.map +1 -0
- package/dist/core/runtime/contextUtils.d.ts +30 -0
- package/dist/core/runtime/contextUtils.d.ts.map +1 -0
- package/dist/core/runtime/contextUtils.js +198 -0
- package/dist/core/runtime/contextUtils.js.map +1 -0
- package/dist/core/runtime/contextUtils.mjs +148 -0
- package/dist/core/runtime/contextUtils.mjs.map +1 -0
- package/dist/core/runtime/metrics.d.ts +22 -0
- package/dist/core/runtime/metrics.d.ts.map +1 -0
- package/dist/core/runtime/metrics.js +89 -0
- package/dist/core/runtime/metrics.js.map +1 -0
- package/dist/core/runtime/metrics.mjs +61 -0
- package/dist/core/runtime/metrics.mjs.map +1 -0
- package/dist/core/sandbox/Sandbox.d.ts +121 -0
- package/dist/core/sandbox/Sandbox.d.ts.map +1 -0
- package/dist/core/sandbox/Sandbox.js +572 -0
- package/dist/core/sandbox/Sandbox.js.map +1 -0
- package/dist/core/sandbox/Sandbox.mjs +536 -0
- package/dist/core/sandbox/Sandbox.mjs.map +1 -0
- package/dist/core/sandbox/SandboxErrors.d.ts +30 -0
- package/dist/core/sandbox/SandboxErrors.d.ts.map +1 -0
- package/dist/core/sandbox/SandboxErrors.js +102 -0
- package/dist/core/sandbox/SandboxErrors.js.map +1 -0
- package/dist/core/sandbox/SandboxErrors.mjs +71 -0
- package/dist/core/sandbox/SandboxErrors.mjs.map +1 -0
- package/dist/core/sandbox/SandboxNatsBridge.d.ts +33 -0
- package/dist/core/sandbox/SandboxNatsBridge.d.ts.map +1 -0
- package/dist/core/sandbox/SandboxNatsBridge.js +222 -0
- package/dist/core/sandbox/SandboxNatsBridge.js.map +1 -0
- package/dist/core/sandbox/SandboxNatsBridge.mjs +201 -0
- package/dist/core/sandbox/SandboxNatsBridge.mjs.map +1 -0
- package/dist/core/sandbox/constants.d.ts +10 -0
- package/dist/core/sandbox/constants.d.ts.map +1 -0
- package/dist/core/sandbox/constants.js +37 -0
- package/dist/core/sandbox/constants.js.map +1 -0
- package/dist/core/sandbox/constants.mjs +10 -0
- package/dist/core/sandbox/constants.mjs.map +1 -0
- package/dist/core/sandbox/provider/DaytonaProvider.d.ts +80 -0
- package/dist/core/sandbox/provider/DaytonaProvider.d.ts.map +1 -0
- package/dist/core/sandbox/provider/DaytonaProvider.js +346 -0
- package/dist/core/sandbox/provider/DaytonaProvider.js.map +1 -0
- package/dist/core/sandbox/provider/DaytonaProvider.mjs +326 -0
- package/dist/core/sandbox/provider/DaytonaProvider.mjs.map +1 -0
- package/dist/core/sandbox/provider/Provider.d.ts +83 -0
- package/dist/core/sandbox/provider/Provider.d.ts.map +1 -0
- package/dist/core/sandbox/provider/Provider.js +43 -0
- package/dist/core/sandbox/provider/Provider.js.map +1 -0
- package/dist/core/sandbox/provider/Provider.mjs +17 -0
- package/dist/core/sandbox/provider/Provider.mjs.map +1 -0
- package/dist/core/sandbox/provider/TFYSandboxProvider.d.ts +41 -0
- package/dist/core/sandbox/provider/TFYSandboxProvider.d.ts.map +1 -0
- package/dist/core/sandbox/provider/TFYSandboxProvider.js +180 -0
- package/dist/core/sandbox/provider/TFYSandboxProvider.js.map +1 -0
- package/dist/core/sandbox/provider/TFYSandboxProvider.mjs +147 -0
- package/dist/core/sandbox/provider/TFYSandboxProvider.mjs.map +1 -0
- package/dist/core/sandbox/sandboxImage.d.ts +6 -0
- package/dist/core/sandbox/sandboxImage.d.ts.map +1 -0
- package/dist/core/sandbox/sandboxImage.js +31 -0
- package/dist/core/sandbox/sandboxImage.js.map +1 -0
- package/dist/core/sandbox/sandboxImage.mjs +6 -0
- package/dist/core/sandbox/sandboxImage.mjs.map +1 -0
- package/dist/core/sandbox/sandboxScripts.gen.d.ts +5 -0
- package/dist/core/sandbox/sandboxScripts.gen.d.ts.map +1 -0
- package/dist/core/sandbox/sandboxScripts.gen.js +343 -0
- package/dist/core/sandbox/sandboxScripts.gen.js.map +1 -0
- package/dist/core/sandbox/sandboxScripts.gen.mjs +318 -0
- package/dist/core/sandbox/sandboxScripts.gen.mjs.map +1 -0
- package/dist/core/sandbox/skills/ISkillMounter.d.ts +7 -0
- package/dist/core/sandbox/skills/ISkillMounter.d.ts.map +1 -0
- package/dist/core/sandbox/skills/ISkillMounter.js +19 -0
- package/dist/core/sandbox/skills/ISkillMounter.js.map +1 -0
- package/dist/core/sandbox/skills/ISkillMounter.mjs +1 -0
- package/dist/core/sandbox/skills/ISkillMounter.mjs.map +1 -0
- package/dist/core/sandbox/skills/SkillMounter.d.ts +17 -0
- package/dist/core/sandbox/skills/SkillMounter.d.ts.map +1 -0
- package/dist/core/sandbox/skills/SkillMounter.js +73 -0
- package/dist/core/sandbox/skills/SkillMounter.js.map +1 -0
- package/dist/core/sandbox/skills/SkillMounter.mjs +48 -0
- package/dist/core/sandbox/skills/SkillMounter.mjs.map +1 -0
- package/dist/core/sandbox/skills/constants.d.ts +10 -0
- package/dist/core/sandbox/skills/constants.d.ts.map +1 -0
- package/dist/core/sandbox/skills/constants.js +56 -0
- package/dist/core/sandbox/skills/constants.js.map +1 -0
- package/dist/core/sandbox/skills/constants.mjs +28 -0
- package/dist/core/sandbox/skills/constants.mjs.map +1 -0
- package/dist/core/sandbox/skills/index.d.ts +5 -0
- package/dist/core/sandbox/skills/index.d.ts.map +1 -0
- package/dist/core/sandbox/skills/index.js +40 -0
- package/dist/core/sandbox/skills/index.js.map +1 -0
- package/dist/core/sandbox/skills/index.mjs +11 -0
- package/dist/core/sandbox/skills/index.mjs.map +1 -0
- package/dist/core/tracing/AgentTracing.d.ts +38 -0
- package/dist/core/tracing/AgentTracing.d.ts.map +1 -0
- package/dist/core/tracing/AgentTracing.js +19 -0
- package/dist/core/tracing/AgentTracing.js.map +1 -0
- package/dist/core/tracing/AgentTracing.mjs +1 -0
- package/dist/core/tracing/AgentTracing.mjs.map +1 -0
- package/dist/core/tracing/NoopAgentTracing.d.ts +3 -0
- package/dist/core/tracing/NoopAgentTracing.d.ts.map +1 -0
- package/dist/core/tracing/NoopAgentTracing.js +79 -0
- package/dist/core/tracing/NoopAgentTracing.js.map +1 -0
- package/dist/core/tracing/NoopAgentTracing.mjs +54 -0
- package/dist/core/tracing/NoopAgentTracing.mjs.map +1 -0
- package/dist/core/util/abort.d.ts +2 -0
- package/dist/core/util/abort.d.ts.map +1 -0
- package/dist/core/util/abort.js +38 -0
- package/dist/core/util/abort.js.map +1 -0
- package/dist/core/util/abort.mjs +13 -0
- package/dist/core/util/abort.mjs.map +1 -0
- package/dist/core/util/errorLogFields.d.ts +6 -0
- package/dist/core/util/errorLogFields.d.ts.map +1 -0
- package/dist/core/util/errorLogFields.js +36 -0
- package/dist/core/util/errorLogFields.js.map +1 -0
- package/dist/core/util/errorLogFields.mjs +11 -0
- package/dist/core/util/errorLogFields.mjs.map +1 -0
- package/dist/core/util/promiseUtils.d.ts +7 -0
- package/dist/core/util/promiseUtils.d.ts.map +1 -0
- package/dist/core/util/promiseUtils.js +82 -0
- package/dist/core/util/promiseUtils.js.map +1 -0
- package/dist/core/util/promiseUtils.mjs +55 -0
- package/dist/core/util/promiseUtils.mjs.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +8 -0
- package/dist/index.mjs.map +1 -0
- package/dist/package.json +3 -0
- package/dist/request-reply/client.d.ts +21 -0
- package/dist/request-reply/client.d.ts.map +1 -0
- package/dist/request-reply/client.js +92 -0
- package/dist/request-reply/client.js.map +1 -0
- package/dist/request-reply/client.mjs +67 -0
- package/dist/request-reply/client.mjs.map +1 -0
- package/dist/request-reply/errors.d.ts +23 -0
- package/dist/request-reply/errors.d.ts.map +1 -0
- package/dist/request-reply/errors.js +66 -0
- package/dist/request-reply/errors.js.map +1 -0
- package/dist/request-reply/errors.mjs +39 -0
- package/dist/request-reply/errors.mjs.map +1 -0
- package/dist/request-reply/executor.d.ts +70 -0
- package/dist/request-reply/executor.d.ts.map +1 -0
- package/dist/request-reply/executor.js +267 -0
- package/dist/request-reply/executor.js.map +1 -0
- package/dist/request-reply/executor.mjs +232 -0
- package/dist/request-reply/executor.mjs.map +1 -0
- package/dist/request-reply/index.d.ts +16 -0
- package/dist/request-reply/index.d.ts.map +1 -0
- package/dist/request-reply/index.js +47 -0
- package/dist/request-reply/index.js.map +1 -0
- package/dist/request-reply/index.mjs +16 -0
- package/dist/request-reply/index.mjs.map +1 -0
- package/dist/request-reply/router.d.ts +20 -0
- package/dist/request-reply/router.d.ts.map +1 -0
- package/dist/request-reply/router.js +55 -0
- package/dist/request-reply/router.js.map +1 -0
- package/dist/request-reply/router.mjs +30 -0
- package/dist/request-reply/router.mjs.map +1 -0
- package/dist/request-reply/types.d.ts +25 -0
- package/dist/request-reply/types.d.ts.map +1 -0
- package/dist/request-reply/types.js +54 -0
- package/dist/request-reply/types.js.map +1 -0
- package/dist/request-reply/types.mjs +28 -0
- package/dist/request-reply/types.mjs.map +1 -0
- package/dist/request-reply/utils.d.ts +9 -0
- package/dist/request-reply/utils.d.ts.map +1 -0
- package/dist/request-reply/utils.js +51 -0
- package/dist/request-reply/utils.js.map +1 -0
- package/dist/request-reply/utils.mjs +22 -0
- package/dist/request-reply/utils.mjs.map +1 -0
- package/package.json +127 -6
- package/README.md +0 -4
- package/index.js +0 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { RedisClientType } from 'redis';
|
|
2
|
+
import type { Logger } from 'winston';
|
|
3
|
+
import type { RequestHandler } from './types';
|
|
4
|
+
export interface RunExecutorOptions {
|
|
5
|
+
/** How often to refresh the heartbeat key (ms). */
|
|
6
|
+
heartbeatIntervalMs: number;
|
|
7
|
+
/** TTL for the reply value so abandoned keys are reclaimed (ms). */
|
|
8
|
+
replyTtlMs: number;
|
|
9
|
+
}
|
|
10
|
+
/** Observability hook for subscriber errors, failed SUBSCRIBEs, and malformed messages. */
|
|
11
|
+
export type RequestReplyErrorHandler = (err: Error, context: {
|
|
12
|
+
executorId: string;
|
|
13
|
+
channel: string;
|
|
14
|
+
}) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Serving side of the request-reply transport: parses and validates each
|
|
17
|
+
* published message, dispatches it to the request handler, writes the
|
|
18
|
+
* JSONReply to the request's reply key, and refreshes the heartbeat key while
|
|
19
|
+
* subscribed.
|
|
20
|
+
*
|
|
21
|
+
* Connection lifecycle stays with the caller: both clients are supplied
|
|
22
|
+
* connected and are never closed here. `subscriberClient` is the connection to
|
|
23
|
+
* SUBSCRIBE on — a `duplicate()` for standalone Redis, or the shared client
|
|
24
|
+
* for Sentinel — and must have a reconnect strategy so a failed connect
|
|
25
|
+
* self-heals (the executor re-subscribes on every `ready`).
|
|
26
|
+
*/
|
|
27
|
+
export declare class RequestReplyExecutor {
|
|
28
|
+
readonly executorId: string;
|
|
29
|
+
/** `rr:req:<executorId>` — the channel this executor subscribes to. */
|
|
30
|
+
readonly channel: string;
|
|
31
|
+
private readonly redis;
|
|
32
|
+
private readonly subscriberClient;
|
|
33
|
+
private readonly logger;
|
|
34
|
+
private readonly heartbeatIntervalMs;
|
|
35
|
+
private readonly heartbeatTtlMs;
|
|
36
|
+
private readonly replyTtlMs;
|
|
37
|
+
private readonly heartbeatKey;
|
|
38
|
+
private readonly requestHandler;
|
|
39
|
+
private readonly onError;
|
|
40
|
+
/** Stable reference so node-redis dedupes the listener across re-subscribes. */
|
|
41
|
+
private readonly subscribeListener;
|
|
42
|
+
private closed;
|
|
43
|
+
private initialized;
|
|
44
|
+
private runningRequests;
|
|
45
|
+
private heartbeatTimer;
|
|
46
|
+
constructor({ executorId, redis, subscriberClient, requestHandler, onError, logger, options, }: {
|
|
47
|
+
executorId: string;
|
|
48
|
+
/** Connected command client, used only for SET (reply + heartbeat). Caller owns its lifecycle. */
|
|
49
|
+
redis: RedisClientType;
|
|
50
|
+
/** Connected client to SUBSCRIBE on (duplicate or Sentinel). Caller owns its lifecycle. */
|
|
51
|
+
subscriberClient: RedisClientType;
|
|
52
|
+
requestHandler: RequestHandler;
|
|
53
|
+
onError?: RequestReplyErrorHandler | undefined;
|
|
54
|
+
logger: Logger;
|
|
55
|
+
options?: Partial<RunExecutorOptions> | undefined;
|
|
56
|
+
});
|
|
57
|
+
init(): Promise<void>;
|
|
58
|
+
private setupSubscriber;
|
|
59
|
+
private beat;
|
|
60
|
+
/** Heartbeat only while subscribed. Idempotent (restarts the timer). */
|
|
61
|
+
private startHeartbeat;
|
|
62
|
+
/** A stopped heartbeat makes callers fail fast with NoResponderError. */
|
|
63
|
+
private stopHeartbeat;
|
|
64
|
+
private processRequestFromMessage;
|
|
65
|
+
/** One raw pub/sub payload. Never throws. */
|
|
66
|
+
private onMessage;
|
|
67
|
+
close(): Promise<void>;
|
|
68
|
+
drain(): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/request-reply/executor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAItC,OAAO,KAAK,EAAa,cAAc,EAAE,MAAM,SAAS,CAAC;AAIzD,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oEAAoE;IACpE,UAAU,EAAE,MAAM,CAAC;CACpB;AAYD,2FAA2F;AAC3F,MAAM,MAAM,wBAAwB,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAC;AAE9G;;;;;;;;;;;GAWG;AACH,qBAAa,oBAAoB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkB;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,gFAAgF;IAChF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAEhC;IAEF,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAoC;IAC3D,OAAO,CAAC,cAAc,CAA+C;IAErE,YAAY,EACV,UAAU,EACV,KAAK,EACL,gBAAgB,EAChB,cAAc,EACd,OAAO,EACP,MAAM,EACN,OAAO,GACR,EAAE;QACD,UAAU,EAAE,MAAM,CAAC;QACnB,kGAAkG;QAClG,KAAK,EAAE,eAAe,CAAC;QACvB,2FAA2F;QAC3F,gBAAgB,EAAE,eAAe,CAAC;QAClC,cAAc,EAAE,cAAc,CAAC;QAC/B,OAAO,CAAC,EAAE,wBAAwB,GAAG,SAAS,CAAC;QAC/C,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,SAAS,CAAC;KACnD,EAaA;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CA6B1B;YAEa,eAAe;YAoBf,IAAI;IAYlB,wEAAwE;IACxE,OAAO,CAAC,cAAc;IAYtB,yEAAyE;IACzE,OAAO,CAAC,aAAa;YAQP,yBAAyB;IA8DvC,6CAA6C;IAC7C,OAAO,CAAC,SAAS;IAsBX,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAY3B;IAIK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3B;CACF"}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/request-reply/executor.ts
|
|
31
|
+
var executor_exports = {};
|
|
32
|
+
__export(executor_exports, {
|
|
33
|
+
RequestReplyExecutor: () => RequestReplyExecutor
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(executor_exports);
|
|
36
|
+
var import_node_crypto = require("crypto");
|
|
37
|
+
var import_zod = __toESM(require("zod"), 1);
|
|
38
|
+
var import_errorLogFields = require("../core/util/errorLogFields.js");
|
|
39
|
+
var import_errors = require("./errors.js");
|
|
40
|
+
var import_types = require("./types.js");
|
|
41
|
+
var import_utils = require("./utils.js");
|
|
42
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
43
|
+
var DEFAULT_REPLY_TTL_MS = 12e4;
|
|
44
|
+
function resolveRunExecutorOptions(options) {
|
|
45
|
+
return {
|
|
46
|
+
heartbeatIntervalMs: options?.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
47
|
+
replyTtlMs: options?.replyTtlMs ?? DEFAULT_REPLY_TTL_MS
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
var RequestReplyExecutor = class {
|
|
51
|
+
executorId;
|
|
52
|
+
/** `rr:req:<executorId>` — the channel this executor subscribes to. */
|
|
53
|
+
channel;
|
|
54
|
+
redis;
|
|
55
|
+
subscriberClient;
|
|
56
|
+
logger;
|
|
57
|
+
heartbeatIntervalMs;
|
|
58
|
+
heartbeatTtlMs;
|
|
59
|
+
replyTtlMs;
|
|
60
|
+
heartbeatKey;
|
|
61
|
+
requestHandler;
|
|
62
|
+
onError;
|
|
63
|
+
/** Stable reference so node-redis dedupes the listener across re-subscribes. */
|
|
64
|
+
subscribeListener = (message) => {
|
|
65
|
+
this.onMessage(message);
|
|
66
|
+
};
|
|
67
|
+
closed = false;
|
|
68
|
+
initialized = false;
|
|
69
|
+
runningRequests = /* @__PURE__ */ new Map();
|
|
70
|
+
heartbeatTimer = null;
|
|
71
|
+
constructor({
|
|
72
|
+
executorId,
|
|
73
|
+
redis,
|
|
74
|
+
subscriberClient,
|
|
75
|
+
requestHandler,
|
|
76
|
+
onError,
|
|
77
|
+
logger,
|
|
78
|
+
options
|
|
79
|
+
}) {
|
|
80
|
+
const { heartbeatIntervalMs, replyTtlMs } = resolveRunExecutorOptions(options);
|
|
81
|
+
this.executorId = executorId;
|
|
82
|
+
this.redis = redis;
|
|
83
|
+
this.subscriberClient = subscriberClient;
|
|
84
|
+
this.logger = logger;
|
|
85
|
+
this.replyTtlMs = replyTtlMs;
|
|
86
|
+
this.requestHandler = requestHandler;
|
|
87
|
+
this.onError = onError;
|
|
88
|
+
this.channel = (0, import_utils.requestChannel)(executorId);
|
|
89
|
+
this.heartbeatKey = (0, import_utils.heartbeatKey)(executorId);
|
|
90
|
+
this.heartbeatIntervalMs = heartbeatIntervalMs;
|
|
91
|
+
this.heartbeatTtlMs = Math.ceil(this.heartbeatIntervalMs * 1.5);
|
|
92
|
+
}
|
|
93
|
+
async init() {
|
|
94
|
+
if (this.closed || this.initialized) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.initialized = true;
|
|
98
|
+
this.subscriberClient.on("error", (err) => {
|
|
99
|
+
this.logger.error("[RequestReplyExecutor] Subscriber error", {
|
|
100
|
+
executorId: this.executorId,
|
|
101
|
+
...(0, import_errorLogFields.extractErrorLogFields)(err)
|
|
102
|
+
});
|
|
103
|
+
this.onError?.(err, { executorId: this.executorId, channel: this.channel });
|
|
104
|
+
this.stopHeartbeat();
|
|
105
|
+
});
|
|
106
|
+
this.subscriberClient.on("end", () => {
|
|
107
|
+
this.logger.warn("[RequestReplyExecutor] Subscriber connection ended", { executorId: this.executorId });
|
|
108
|
+
this.stopHeartbeat();
|
|
109
|
+
});
|
|
110
|
+
this.subscriberClient.on("ready", () => {
|
|
111
|
+
void this.setupSubscriber();
|
|
112
|
+
});
|
|
113
|
+
if (this.subscriberClient.isReady) {
|
|
114
|
+
await this.setupSubscriber();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async setupSubscriber() {
|
|
118
|
+
if (this.closed) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
await this.subscriberClient.subscribe(this.channel, this.subscribeListener);
|
|
123
|
+
this.logger.info("[RequestReplyExecutor] Subscribed to request channel", { executorId: this.executorId });
|
|
124
|
+
this.stopHeartbeat();
|
|
125
|
+
this.startHeartbeat();
|
|
126
|
+
} catch (err) {
|
|
127
|
+
const error = err instanceof Error ? err : new Error(String(err), { cause: err });
|
|
128
|
+
this.logger.error("[RequestReplyExecutor] Error subscribing to request channel", {
|
|
129
|
+
executorId: this.executorId,
|
|
130
|
+
...(0, import_errorLogFields.extractErrorLogFields)(error)
|
|
131
|
+
});
|
|
132
|
+
this.onError?.(error, { executorId: this.executorId, channel: this.channel });
|
|
133
|
+
this.stopHeartbeat();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async beat() {
|
|
137
|
+
this.logger.debug("[RequestReplyExecutor] Beating heartbeat", { executorId: this.executorId });
|
|
138
|
+
try {
|
|
139
|
+
await this.redis.set(this.heartbeatKey, "1", { PX: this.heartbeatTtlMs });
|
|
140
|
+
} catch (err) {
|
|
141
|
+
this.logger.error("[RequestReplyExecutor] Heartbeat error", {
|
|
142
|
+
executorId: this.executorId,
|
|
143
|
+
...(0, import_errorLogFields.extractErrorLogFields)(err)
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/** Heartbeat only while subscribed. Idempotent (restarts the timer). */
|
|
148
|
+
startHeartbeat() {
|
|
149
|
+
if (this.closed) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
this.logger.info("[RequestReplyExecutor] Starting heartbeat", { executorId: this.executorId });
|
|
153
|
+
this.stopHeartbeat();
|
|
154
|
+
void this.beat();
|
|
155
|
+
this.heartbeatTimer = setInterval(() => {
|
|
156
|
+
void this.beat();
|
|
157
|
+
}, this.heartbeatIntervalMs);
|
|
158
|
+
}
|
|
159
|
+
/** A stopped heartbeat makes callers fail fast with NoResponderError. */
|
|
160
|
+
stopHeartbeat() {
|
|
161
|
+
if (this.heartbeatTimer) {
|
|
162
|
+
this.logger.info("[RequestReplyExecutor] Stopping heartbeat", { executorId: this.executorId });
|
|
163
|
+
clearInterval(this.heartbeatTimer);
|
|
164
|
+
this.heartbeatTimer = null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async processRequestFromMessage(message) {
|
|
168
|
+
let raw;
|
|
169
|
+
try {
|
|
170
|
+
raw = JSON.parse(message);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
this.logger.warn("[RequestReplyExecutor] Request message is not valid JSON", { executorId: this.executorId });
|
|
173
|
+
this.onError?.(
|
|
174
|
+
new Error(`Request message is not valid JSON for executorId: ${this.executorId}`, {
|
|
175
|
+
cause: err
|
|
176
|
+
}),
|
|
177
|
+
{
|
|
178
|
+
executorId: this.executorId,
|
|
179
|
+
channel: this.channel
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const parsedRequest = import_types.publishedRequestSchema.safeParse(raw);
|
|
185
|
+
if (!parsedRequest.success) {
|
|
186
|
+
const zodError = import_zod.default.treeifyError(parsedRequest.error);
|
|
187
|
+
this.logger.warn("[RequestReplyExecutor] Invalid request message shape", {
|
|
188
|
+
executorId: this.executorId,
|
|
189
|
+
zod: zodError
|
|
190
|
+
});
|
|
191
|
+
this.onError?.(
|
|
192
|
+
new Error(
|
|
193
|
+
`Invalid request message shape for executorId: ${this.executorId}, error: ${JSON.stringify(zodError)}`,
|
|
194
|
+
{ cause: parsedRequest.error }
|
|
195
|
+
),
|
|
196
|
+
{ executorId: this.executorId, channel: this.channel }
|
|
197
|
+
);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const { replyKey, path, body, headers } = parsedRequest.data;
|
|
201
|
+
let replyPayload;
|
|
202
|
+
try {
|
|
203
|
+
replyPayload = await this.requestHandler(path, { body, headers });
|
|
204
|
+
} catch (err) {
|
|
205
|
+
if (err instanceof import_errors.ReplyError) {
|
|
206
|
+
replyPayload = { status: err.status, headers: {}, body: { message: err.message } };
|
|
207
|
+
} else {
|
|
208
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
209
|
+
replyPayload = { status: 500, headers: {}, body: { message: message2 } };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
await this.redis.set(replyKey, JSON.stringify(replyPayload), { PX: this.replyTtlMs });
|
|
214
|
+
} catch (err) {
|
|
215
|
+
this.logger.error("[RequestReplyExecutor] Request processing failed", {
|
|
216
|
+
executorId: this.executorId,
|
|
217
|
+
replyKey,
|
|
218
|
+
...(0, import_errorLogFields.extractErrorLogFields)(err)
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/** One raw pub/sub payload. Never throws. */
|
|
223
|
+
onMessage(message) {
|
|
224
|
+
this.logger.debug("[RequestReplyExecutor] Received message", { executorId: this.executorId, message });
|
|
225
|
+
if (!message) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (this.closed) {
|
|
229
|
+
this.logger.warn("[RequestReplyExecutor] Received message after executor is closed", {
|
|
230
|
+
executorId: this.executorId,
|
|
231
|
+
message
|
|
232
|
+
});
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const pid = (0, import_node_crypto.randomUUID)();
|
|
236
|
+
const requestPromise = this.processRequestFromMessage(message).finally(() => {
|
|
237
|
+
this.runningRequests.delete(pid);
|
|
238
|
+
});
|
|
239
|
+
this.runningRequests.set(pid, requestPromise);
|
|
240
|
+
}
|
|
241
|
+
// This function should never throw!
|
|
242
|
+
// Stops intake: unsubscribe from the channel and stop the heartbeat. Never
|
|
243
|
+
// closes the clients — the caller owns their lifecycle.
|
|
244
|
+
async close() {
|
|
245
|
+
if (this.closed) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
this.logger.info("[RequestReplyExecutor] Closing", { executorId: this.executorId });
|
|
249
|
+
this.closed = true;
|
|
250
|
+
try {
|
|
251
|
+
await this.subscriberClient.unsubscribe(this.channel);
|
|
252
|
+
} catch {
|
|
253
|
+
}
|
|
254
|
+
this.stopHeartbeat();
|
|
255
|
+
}
|
|
256
|
+
// This function should never throw!
|
|
257
|
+
// This is called during graceful shutdown
|
|
258
|
+
async drain() {
|
|
259
|
+
await this.close();
|
|
260
|
+
await Promise.allSettled(Array.from(this.runningRequests.values()));
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
264
|
+
0 && (module.exports = {
|
|
265
|
+
RequestReplyExecutor
|
|
266
|
+
});
|
|
267
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/request-reply/executor.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport type { RedisClientType } from 'redis';\nimport type { Logger } from 'winston';\nimport z from 'zod';\nimport { extractErrorLogFields } from '../core/util/errorLogFields';\nimport { ReplyError } from './errors';\nimport type { JSONReply, RequestHandler } from './types';\nimport { publishedRequestSchema } from './types';\nimport { heartbeatKey, requestChannel } from './utils';\n\nexport interface RunExecutorOptions {\n /** How often to refresh the heartbeat key (ms). */\n heartbeatIntervalMs: number;\n /** TTL for the reply value so abandoned keys are reclaimed (ms). */\n replyTtlMs: number;\n}\n\nconst DEFAULT_HEARTBEAT_INTERVAL_MS = 5_000;\nconst DEFAULT_REPLY_TTL_MS = 120_000;\n\nfunction resolveRunExecutorOptions(options: Partial<RunExecutorOptions> | undefined): RunExecutorOptions {\n return {\n heartbeatIntervalMs: options?.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS,\n replyTtlMs: options?.replyTtlMs ?? DEFAULT_REPLY_TTL_MS,\n };\n}\n\n/** Observability hook for subscriber errors, failed SUBSCRIBEs, and malformed messages. */\nexport type RequestReplyErrorHandler = (err: Error, context: { executorId: string; channel: string }) => void;\n\n/**\n * Serving side of the request-reply transport: parses and validates each\n * published message, dispatches it to the request handler, writes the\n * JSONReply to the request's reply key, and refreshes the heartbeat key while\n * subscribed.\n *\n * Connection lifecycle stays with the caller: both clients are supplied\n * connected and are never closed here. `subscriberClient` is the connection to\n * SUBSCRIBE on — a `duplicate()` for standalone Redis, or the shared client\n * for Sentinel — and must have a reconnect strategy so a failed connect\n * self-heals (the executor re-subscribes on every `ready`).\n */\nexport class RequestReplyExecutor {\n readonly executorId: string;\n /** `rr:req:<executorId>` — the channel this executor subscribes to. */\n readonly channel: string;\n private readonly redis: RedisClientType;\n private readonly subscriberClient: RedisClientType;\n private readonly logger: Logger;\n private readonly heartbeatIntervalMs: number;\n private readonly heartbeatTtlMs: number;\n private readonly replyTtlMs: number;\n private readonly heartbeatKey: string;\n private readonly requestHandler: RequestHandler;\n private readonly onError: RequestReplyErrorHandler | undefined;\n /** Stable reference so node-redis dedupes the listener across re-subscribes. */\n private readonly subscribeListener = (message: string): void => {\n this.onMessage(message);\n };\n\n private closed = false;\n private initialized = false;\n private runningRequests = new Map<string, Promise<void>>();\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor({\n executorId,\n redis,\n subscriberClient,\n requestHandler,\n onError,\n logger,\n options,\n }: {\n executorId: string;\n /** Connected command client, used only for SET (reply + heartbeat). Caller owns its lifecycle. */\n redis: RedisClientType;\n /** Connected client to SUBSCRIBE on (duplicate or Sentinel). Caller owns its lifecycle. */\n subscriberClient: RedisClientType;\n requestHandler: RequestHandler;\n onError?: RequestReplyErrorHandler | undefined;\n logger: Logger;\n options?: Partial<RunExecutorOptions> | undefined;\n }) {\n const { heartbeatIntervalMs, replyTtlMs } = resolveRunExecutorOptions(options);\n this.executorId = executorId;\n this.redis = redis;\n this.subscriberClient = subscriberClient;\n this.logger = logger;\n this.replyTtlMs = replyTtlMs;\n this.requestHandler = requestHandler;\n this.onError = onError;\n this.channel = requestChannel(executorId);\n this.heartbeatKey = heartbeatKey(executorId);\n this.heartbeatIntervalMs = heartbeatIntervalMs;\n this.heartbeatTtlMs = Math.ceil(this.heartbeatIntervalMs * 1.5);\n }\n\n async init(): Promise<void> {\n if (this.closed || this.initialized) {\n return;\n }\n this.initialized = true;\n\n this.subscriberClient.on('error', (err: Error) => {\n this.logger.error('[RequestReplyExecutor] Subscriber error', {\n executorId: this.executorId,\n ...extractErrorLogFields(err),\n });\n this.onError?.(err, { executorId: this.executorId, channel: this.channel });\n this.stopHeartbeat();\n });\n this.subscriberClient.on('end', () => {\n this.logger.warn('[RequestReplyExecutor] Subscriber connection ended', { executorId: this.executorId });\n this.stopHeartbeat();\n });\n // ready fires on the first connect and every reconnect; re-subscribing is\n // safe because node-redis dedupes the stable listener.\n this.subscriberClient.on('ready', () => {\n void this.setupSubscriber();\n });\n\n // On stable connections we won't get another ready event — if the caller\n // already connected the client, subscribe now.\n if (this.subscriberClient.isReady) {\n await this.setupSubscriber();\n }\n }\n\n private async setupSubscriber(): Promise<void> {\n if (this.closed) {\n return;\n }\n try {\n await this.subscriberClient.subscribe(this.channel, this.subscribeListener);\n this.logger.info('[RequestReplyExecutor] Subscribed to request channel', { executorId: this.executorId });\n this.stopHeartbeat();\n this.startHeartbeat();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err), { cause: err });\n this.logger.error('[RequestReplyExecutor] Error subscribing to request channel', {\n executorId: this.executorId,\n ...extractErrorLogFields(error),\n });\n this.onError?.(error, { executorId: this.executorId, channel: this.channel });\n this.stopHeartbeat();\n }\n }\n\n private async beat(): Promise<void> {\n this.logger.debug('[RequestReplyExecutor] Beating heartbeat', { executorId: this.executorId });\n try {\n await this.redis.set(this.heartbeatKey, '1', { PX: this.heartbeatTtlMs });\n } catch (err) {\n this.logger.error('[RequestReplyExecutor] Heartbeat error', {\n executorId: this.executorId,\n ...extractErrorLogFields(err),\n });\n }\n }\n\n /** Heartbeat only while subscribed. Idempotent (restarts the timer). */\n private startHeartbeat(): void {\n if (this.closed) {\n return;\n }\n this.logger.info('[RequestReplyExecutor] Starting heartbeat', { executorId: this.executorId });\n this.stopHeartbeat();\n void this.beat();\n this.heartbeatTimer = setInterval(() => {\n void this.beat();\n }, this.heartbeatIntervalMs);\n }\n\n /** A stopped heartbeat makes callers fail fast with NoResponderError. */\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n this.logger.info('[RequestReplyExecutor] Stopping heartbeat', { executorId: this.executorId });\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private async processRequestFromMessage(message: string): Promise<void> {\n let raw: unknown;\n try {\n raw = JSON.parse(message);\n } catch (err) {\n // Drop the request, this should never happen\n this.logger.warn('[RequestReplyExecutor] Request message is not valid JSON', { executorId: this.executorId });\n this.onError?.(\n new Error(`Request message is not valid JSON for executorId: ${this.executorId}`, {\n cause: err,\n }),\n {\n executorId: this.executorId,\n channel: this.channel,\n },\n );\n return;\n }\n\n const parsedRequest = publishedRequestSchema.safeParse(raw);\n if (!parsedRequest.success) {\n // Drop the request, this should never happen\n const zodError = z.treeifyError(parsedRequest.error);\n this.logger.warn('[RequestReplyExecutor] Invalid request message shape', {\n executorId: this.executorId,\n zod: zodError,\n });\n this.onError?.(\n new Error(\n `Invalid request message shape for executorId: ${this.executorId}, error: ${JSON.stringify(zodError)}`,\n { cause: parsedRequest.error },\n ),\n { executorId: this.executorId, channel: this.channel },\n );\n return;\n }\n\n const { replyKey, path, body, headers } = parsedRequest.data;\n let replyPayload: JSONReply;\n try {\n replyPayload = await this.requestHandler(path, { body, headers });\n } catch (err) {\n // ReplyError lets handlers choose the reply status; any other throw is an unexpected 500.\n if (err instanceof ReplyError) {\n replyPayload = { status: err.status, headers: {}, body: { message: err.message } };\n } else {\n const message = err instanceof Error ? err.message : String(err);\n replyPayload = { status: 500, headers: {}, body: { message } };\n }\n }\n\n try {\n await this.redis.set(replyKey, JSON.stringify(replyPayload), { PX: this.replyTtlMs });\n } catch (err) {\n this.logger.error('[RequestReplyExecutor] Request processing failed', {\n executorId: this.executorId,\n replyKey,\n ...extractErrorLogFields(err),\n });\n }\n }\n\n /** One raw pub/sub payload. Never throws. */\n private onMessage(message: string): void {\n this.logger.debug('[RequestReplyExecutor] Received message', { executorId: this.executorId, message });\n if (!message) {\n return;\n }\n if (this.closed) {\n this.logger.warn('[RequestReplyExecutor] Received message after executor is closed', {\n executorId: this.executorId,\n message,\n });\n return;\n }\n const pid = randomUUID();\n const requestPromise = this.processRequestFromMessage(message).finally(() => {\n this.runningRequests.delete(pid);\n });\n this.runningRequests.set(pid, requestPromise);\n }\n\n // This function should never throw!\n // Stops intake: unsubscribe from the channel and stop the heartbeat. Never\n // closes the clients — the caller owns their lifecycle.\n async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n this.logger.info('[RequestReplyExecutor] Closing', { executorId: this.executorId });\n this.closed = true;\n try {\n await this.subscriberClient.unsubscribe(this.channel);\n } catch {\n // ignore: connection may already be closed\n }\n this.stopHeartbeat();\n }\n\n // This function should never throw!\n // This is called during graceful shutdown\n async drain(): Promise<void> {\n await this.close();\n await Promise.allSettled(Array.from(this.runningRequests.values()));\n }\n}\n"],"mappings":"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","names":["z","message"]}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// src/request-reply/executor.ts
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import z from "zod";
|
|
4
|
+
import { extractErrorLogFields } from "../core/util/errorLogFields.mjs";
|
|
5
|
+
import { ReplyError } from "./errors.mjs";
|
|
6
|
+
import { publishedRequestSchema } from "./types.mjs";
|
|
7
|
+
import { heartbeatKey, requestChannel } from "./utils.mjs";
|
|
8
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
9
|
+
var DEFAULT_REPLY_TTL_MS = 12e4;
|
|
10
|
+
function resolveRunExecutorOptions(options) {
|
|
11
|
+
return {
|
|
12
|
+
heartbeatIntervalMs: options?.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
13
|
+
replyTtlMs: options?.replyTtlMs ?? DEFAULT_REPLY_TTL_MS
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
var RequestReplyExecutor = class {
|
|
17
|
+
executorId;
|
|
18
|
+
/** `rr:req:<executorId>` — the channel this executor subscribes to. */
|
|
19
|
+
channel;
|
|
20
|
+
redis;
|
|
21
|
+
subscriberClient;
|
|
22
|
+
logger;
|
|
23
|
+
heartbeatIntervalMs;
|
|
24
|
+
heartbeatTtlMs;
|
|
25
|
+
replyTtlMs;
|
|
26
|
+
heartbeatKey;
|
|
27
|
+
requestHandler;
|
|
28
|
+
onError;
|
|
29
|
+
/** Stable reference so node-redis dedupes the listener across re-subscribes. */
|
|
30
|
+
subscribeListener = (message) => {
|
|
31
|
+
this.onMessage(message);
|
|
32
|
+
};
|
|
33
|
+
closed = false;
|
|
34
|
+
initialized = false;
|
|
35
|
+
runningRequests = /* @__PURE__ */ new Map();
|
|
36
|
+
heartbeatTimer = null;
|
|
37
|
+
constructor({
|
|
38
|
+
executorId,
|
|
39
|
+
redis,
|
|
40
|
+
subscriberClient,
|
|
41
|
+
requestHandler,
|
|
42
|
+
onError,
|
|
43
|
+
logger,
|
|
44
|
+
options
|
|
45
|
+
}) {
|
|
46
|
+
const { heartbeatIntervalMs, replyTtlMs } = resolveRunExecutorOptions(options);
|
|
47
|
+
this.executorId = executorId;
|
|
48
|
+
this.redis = redis;
|
|
49
|
+
this.subscriberClient = subscriberClient;
|
|
50
|
+
this.logger = logger;
|
|
51
|
+
this.replyTtlMs = replyTtlMs;
|
|
52
|
+
this.requestHandler = requestHandler;
|
|
53
|
+
this.onError = onError;
|
|
54
|
+
this.channel = requestChannel(executorId);
|
|
55
|
+
this.heartbeatKey = heartbeatKey(executorId);
|
|
56
|
+
this.heartbeatIntervalMs = heartbeatIntervalMs;
|
|
57
|
+
this.heartbeatTtlMs = Math.ceil(this.heartbeatIntervalMs * 1.5);
|
|
58
|
+
}
|
|
59
|
+
async init() {
|
|
60
|
+
if (this.closed || this.initialized) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this.initialized = true;
|
|
64
|
+
this.subscriberClient.on("error", (err) => {
|
|
65
|
+
this.logger.error("[RequestReplyExecutor] Subscriber error", {
|
|
66
|
+
executorId: this.executorId,
|
|
67
|
+
...extractErrorLogFields(err)
|
|
68
|
+
});
|
|
69
|
+
this.onError?.(err, { executorId: this.executorId, channel: this.channel });
|
|
70
|
+
this.stopHeartbeat();
|
|
71
|
+
});
|
|
72
|
+
this.subscriberClient.on("end", () => {
|
|
73
|
+
this.logger.warn("[RequestReplyExecutor] Subscriber connection ended", { executorId: this.executorId });
|
|
74
|
+
this.stopHeartbeat();
|
|
75
|
+
});
|
|
76
|
+
this.subscriberClient.on("ready", () => {
|
|
77
|
+
void this.setupSubscriber();
|
|
78
|
+
});
|
|
79
|
+
if (this.subscriberClient.isReady) {
|
|
80
|
+
await this.setupSubscriber();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async setupSubscriber() {
|
|
84
|
+
if (this.closed) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
await this.subscriberClient.subscribe(this.channel, this.subscribeListener);
|
|
89
|
+
this.logger.info("[RequestReplyExecutor] Subscribed to request channel", { executorId: this.executorId });
|
|
90
|
+
this.stopHeartbeat();
|
|
91
|
+
this.startHeartbeat();
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const error = err instanceof Error ? err : new Error(String(err), { cause: err });
|
|
94
|
+
this.logger.error("[RequestReplyExecutor] Error subscribing to request channel", {
|
|
95
|
+
executorId: this.executorId,
|
|
96
|
+
...extractErrorLogFields(error)
|
|
97
|
+
});
|
|
98
|
+
this.onError?.(error, { executorId: this.executorId, channel: this.channel });
|
|
99
|
+
this.stopHeartbeat();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async beat() {
|
|
103
|
+
this.logger.debug("[RequestReplyExecutor] Beating heartbeat", { executorId: this.executorId });
|
|
104
|
+
try {
|
|
105
|
+
await this.redis.set(this.heartbeatKey, "1", { PX: this.heartbeatTtlMs });
|
|
106
|
+
} catch (err) {
|
|
107
|
+
this.logger.error("[RequestReplyExecutor] Heartbeat error", {
|
|
108
|
+
executorId: this.executorId,
|
|
109
|
+
...extractErrorLogFields(err)
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Heartbeat only while subscribed. Idempotent (restarts the timer). */
|
|
114
|
+
startHeartbeat() {
|
|
115
|
+
if (this.closed) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.logger.info("[RequestReplyExecutor] Starting heartbeat", { executorId: this.executorId });
|
|
119
|
+
this.stopHeartbeat();
|
|
120
|
+
void this.beat();
|
|
121
|
+
this.heartbeatTimer = setInterval(() => {
|
|
122
|
+
void this.beat();
|
|
123
|
+
}, this.heartbeatIntervalMs);
|
|
124
|
+
}
|
|
125
|
+
/** A stopped heartbeat makes callers fail fast with NoResponderError. */
|
|
126
|
+
stopHeartbeat() {
|
|
127
|
+
if (this.heartbeatTimer) {
|
|
128
|
+
this.logger.info("[RequestReplyExecutor] Stopping heartbeat", { executorId: this.executorId });
|
|
129
|
+
clearInterval(this.heartbeatTimer);
|
|
130
|
+
this.heartbeatTimer = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async processRequestFromMessage(message) {
|
|
134
|
+
let raw;
|
|
135
|
+
try {
|
|
136
|
+
raw = JSON.parse(message);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
this.logger.warn("[RequestReplyExecutor] Request message is not valid JSON", { executorId: this.executorId });
|
|
139
|
+
this.onError?.(
|
|
140
|
+
new Error(`Request message is not valid JSON for executorId: ${this.executorId}`, {
|
|
141
|
+
cause: err
|
|
142
|
+
}),
|
|
143
|
+
{
|
|
144
|
+
executorId: this.executorId,
|
|
145
|
+
channel: this.channel
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const parsedRequest = publishedRequestSchema.safeParse(raw);
|
|
151
|
+
if (!parsedRequest.success) {
|
|
152
|
+
const zodError = z.treeifyError(parsedRequest.error);
|
|
153
|
+
this.logger.warn("[RequestReplyExecutor] Invalid request message shape", {
|
|
154
|
+
executorId: this.executorId,
|
|
155
|
+
zod: zodError
|
|
156
|
+
});
|
|
157
|
+
this.onError?.(
|
|
158
|
+
new Error(
|
|
159
|
+
`Invalid request message shape for executorId: ${this.executorId}, error: ${JSON.stringify(zodError)}`,
|
|
160
|
+
{ cause: parsedRequest.error }
|
|
161
|
+
),
|
|
162
|
+
{ executorId: this.executorId, channel: this.channel }
|
|
163
|
+
);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const { replyKey, path, body, headers } = parsedRequest.data;
|
|
167
|
+
let replyPayload;
|
|
168
|
+
try {
|
|
169
|
+
replyPayload = await this.requestHandler(path, { body, headers });
|
|
170
|
+
} catch (err) {
|
|
171
|
+
if (err instanceof ReplyError) {
|
|
172
|
+
replyPayload = { status: err.status, headers: {}, body: { message: err.message } };
|
|
173
|
+
} else {
|
|
174
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
175
|
+
replyPayload = { status: 500, headers: {}, body: { message: message2 } };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
await this.redis.set(replyKey, JSON.stringify(replyPayload), { PX: this.replyTtlMs });
|
|
180
|
+
} catch (err) {
|
|
181
|
+
this.logger.error("[RequestReplyExecutor] Request processing failed", {
|
|
182
|
+
executorId: this.executorId,
|
|
183
|
+
replyKey,
|
|
184
|
+
...extractErrorLogFields(err)
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** One raw pub/sub payload. Never throws. */
|
|
189
|
+
onMessage(message) {
|
|
190
|
+
this.logger.debug("[RequestReplyExecutor] Received message", { executorId: this.executorId, message });
|
|
191
|
+
if (!message) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (this.closed) {
|
|
195
|
+
this.logger.warn("[RequestReplyExecutor] Received message after executor is closed", {
|
|
196
|
+
executorId: this.executorId,
|
|
197
|
+
message
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const pid = randomUUID();
|
|
202
|
+
const requestPromise = this.processRequestFromMessage(message).finally(() => {
|
|
203
|
+
this.runningRequests.delete(pid);
|
|
204
|
+
});
|
|
205
|
+
this.runningRequests.set(pid, requestPromise);
|
|
206
|
+
}
|
|
207
|
+
// This function should never throw!
|
|
208
|
+
// Stops intake: unsubscribe from the channel and stop the heartbeat. Never
|
|
209
|
+
// closes the clients — the caller owns their lifecycle.
|
|
210
|
+
async close() {
|
|
211
|
+
if (this.closed) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
this.logger.info("[RequestReplyExecutor] Closing", { executorId: this.executorId });
|
|
215
|
+
this.closed = true;
|
|
216
|
+
try {
|
|
217
|
+
await this.subscriberClient.unsubscribe(this.channel);
|
|
218
|
+
} catch {
|
|
219
|
+
}
|
|
220
|
+
this.stopHeartbeat();
|
|
221
|
+
}
|
|
222
|
+
// This function should never throw!
|
|
223
|
+
// This is called during graceful shutdown
|
|
224
|
+
async drain() {
|
|
225
|
+
await this.close();
|
|
226
|
+
await Promise.allSettled(Array.from(this.runningRequests.values()));
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
export {
|
|
230
|
+
RequestReplyExecutor
|
|
231
|
+
};
|
|
232
|
+
//# sourceMappingURL=executor.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/request-reply/executor.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport type { RedisClientType } from 'redis';\nimport type { Logger } from 'winston';\nimport z from 'zod';\nimport { extractErrorLogFields } from '../core/util/errorLogFields';\nimport { ReplyError } from './errors';\nimport type { JSONReply, RequestHandler } from './types';\nimport { publishedRequestSchema } from './types';\nimport { heartbeatKey, requestChannel } from './utils';\n\nexport interface RunExecutorOptions {\n /** How often to refresh the heartbeat key (ms). */\n heartbeatIntervalMs: number;\n /** TTL for the reply value so abandoned keys are reclaimed (ms). */\n replyTtlMs: number;\n}\n\nconst DEFAULT_HEARTBEAT_INTERVAL_MS = 5_000;\nconst DEFAULT_REPLY_TTL_MS = 120_000;\n\nfunction resolveRunExecutorOptions(options: Partial<RunExecutorOptions> | undefined): RunExecutorOptions {\n return {\n heartbeatIntervalMs: options?.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS,\n replyTtlMs: options?.replyTtlMs ?? DEFAULT_REPLY_TTL_MS,\n };\n}\n\n/** Observability hook for subscriber errors, failed SUBSCRIBEs, and malformed messages. */\nexport type RequestReplyErrorHandler = (err: Error, context: { executorId: string; channel: string }) => void;\n\n/**\n * Serving side of the request-reply transport: parses and validates each\n * published message, dispatches it to the request handler, writes the\n * JSONReply to the request's reply key, and refreshes the heartbeat key while\n * subscribed.\n *\n * Connection lifecycle stays with the caller: both clients are supplied\n * connected and are never closed here. `subscriberClient` is the connection to\n * SUBSCRIBE on — a `duplicate()` for standalone Redis, or the shared client\n * for Sentinel — and must have a reconnect strategy so a failed connect\n * self-heals (the executor re-subscribes on every `ready`).\n */\nexport class RequestReplyExecutor {\n readonly executorId: string;\n /** `rr:req:<executorId>` — the channel this executor subscribes to. */\n readonly channel: string;\n private readonly redis: RedisClientType;\n private readonly subscriberClient: RedisClientType;\n private readonly logger: Logger;\n private readonly heartbeatIntervalMs: number;\n private readonly heartbeatTtlMs: number;\n private readonly replyTtlMs: number;\n private readonly heartbeatKey: string;\n private readonly requestHandler: RequestHandler;\n private readonly onError: RequestReplyErrorHandler | undefined;\n /** Stable reference so node-redis dedupes the listener across re-subscribes. */\n private readonly subscribeListener = (message: string): void => {\n this.onMessage(message);\n };\n\n private closed = false;\n private initialized = false;\n private runningRequests = new Map<string, Promise<void>>();\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor({\n executorId,\n redis,\n subscriberClient,\n requestHandler,\n onError,\n logger,\n options,\n }: {\n executorId: string;\n /** Connected command client, used only for SET (reply + heartbeat). Caller owns its lifecycle. */\n redis: RedisClientType;\n /** Connected client to SUBSCRIBE on (duplicate or Sentinel). Caller owns its lifecycle. */\n subscriberClient: RedisClientType;\n requestHandler: RequestHandler;\n onError?: RequestReplyErrorHandler | undefined;\n logger: Logger;\n options?: Partial<RunExecutorOptions> | undefined;\n }) {\n const { heartbeatIntervalMs, replyTtlMs } = resolveRunExecutorOptions(options);\n this.executorId = executorId;\n this.redis = redis;\n this.subscriberClient = subscriberClient;\n this.logger = logger;\n this.replyTtlMs = replyTtlMs;\n this.requestHandler = requestHandler;\n this.onError = onError;\n this.channel = requestChannel(executorId);\n this.heartbeatKey = heartbeatKey(executorId);\n this.heartbeatIntervalMs = heartbeatIntervalMs;\n this.heartbeatTtlMs = Math.ceil(this.heartbeatIntervalMs * 1.5);\n }\n\n async init(): Promise<void> {\n if (this.closed || this.initialized) {\n return;\n }\n this.initialized = true;\n\n this.subscriberClient.on('error', (err: Error) => {\n this.logger.error('[RequestReplyExecutor] Subscriber error', {\n executorId: this.executorId,\n ...extractErrorLogFields(err),\n });\n this.onError?.(err, { executorId: this.executorId, channel: this.channel });\n this.stopHeartbeat();\n });\n this.subscriberClient.on('end', () => {\n this.logger.warn('[RequestReplyExecutor] Subscriber connection ended', { executorId: this.executorId });\n this.stopHeartbeat();\n });\n // ready fires on the first connect and every reconnect; re-subscribing is\n // safe because node-redis dedupes the stable listener.\n this.subscriberClient.on('ready', () => {\n void this.setupSubscriber();\n });\n\n // On stable connections we won't get another ready event — if the caller\n // already connected the client, subscribe now.\n if (this.subscriberClient.isReady) {\n await this.setupSubscriber();\n }\n }\n\n private async setupSubscriber(): Promise<void> {\n if (this.closed) {\n return;\n }\n try {\n await this.subscriberClient.subscribe(this.channel, this.subscribeListener);\n this.logger.info('[RequestReplyExecutor] Subscribed to request channel', { executorId: this.executorId });\n this.stopHeartbeat();\n this.startHeartbeat();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err), { cause: err });\n this.logger.error('[RequestReplyExecutor] Error subscribing to request channel', {\n executorId: this.executorId,\n ...extractErrorLogFields(error),\n });\n this.onError?.(error, { executorId: this.executorId, channel: this.channel });\n this.stopHeartbeat();\n }\n }\n\n private async beat(): Promise<void> {\n this.logger.debug('[RequestReplyExecutor] Beating heartbeat', { executorId: this.executorId });\n try {\n await this.redis.set(this.heartbeatKey, '1', { PX: this.heartbeatTtlMs });\n } catch (err) {\n this.logger.error('[RequestReplyExecutor] Heartbeat error', {\n executorId: this.executorId,\n ...extractErrorLogFields(err),\n });\n }\n }\n\n /** Heartbeat only while subscribed. Idempotent (restarts the timer). */\n private startHeartbeat(): void {\n if (this.closed) {\n return;\n }\n this.logger.info('[RequestReplyExecutor] Starting heartbeat', { executorId: this.executorId });\n this.stopHeartbeat();\n void this.beat();\n this.heartbeatTimer = setInterval(() => {\n void this.beat();\n }, this.heartbeatIntervalMs);\n }\n\n /** A stopped heartbeat makes callers fail fast with NoResponderError. */\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n this.logger.info('[RequestReplyExecutor] Stopping heartbeat', { executorId: this.executorId });\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n private async processRequestFromMessage(message: string): Promise<void> {\n let raw: unknown;\n try {\n raw = JSON.parse(message);\n } catch (err) {\n // Drop the request, this should never happen\n this.logger.warn('[RequestReplyExecutor] Request message is not valid JSON', { executorId: this.executorId });\n this.onError?.(\n new Error(`Request message is not valid JSON for executorId: ${this.executorId}`, {\n cause: err,\n }),\n {\n executorId: this.executorId,\n channel: this.channel,\n },\n );\n return;\n }\n\n const parsedRequest = publishedRequestSchema.safeParse(raw);\n if (!parsedRequest.success) {\n // Drop the request, this should never happen\n const zodError = z.treeifyError(parsedRequest.error);\n this.logger.warn('[RequestReplyExecutor] Invalid request message shape', {\n executorId: this.executorId,\n zod: zodError,\n });\n this.onError?.(\n new Error(\n `Invalid request message shape for executorId: ${this.executorId}, error: ${JSON.stringify(zodError)}`,\n { cause: parsedRequest.error },\n ),\n { executorId: this.executorId, channel: this.channel },\n );\n return;\n }\n\n const { replyKey, path, body, headers } = parsedRequest.data;\n let replyPayload: JSONReply;\n try {\n replyPayload = await this.requestHandler(path, { body, headers });\n } catch (err) {\n // ReplyError lets handlers choose the reply status; any other throw is an unexpected 500.\n if (err instanceof ReplyError) {\n replyPayload = { status: err.status, headers: {}, body: { message: err.message } };\n } else {\n const message = err instanceof Error ? err.message : String(err);\n replyPayload = { status: 500, headers: {}, body: { message } };\n }\n }\n\n try {\n await this.redis.set(replyKey, JSON.stringify(replyPayload), { PX: this.replyTtlMs });\n } catch (err) {\n this.logger.error('[RequestReplyExecutor] Request processing failed', {\n executorId: this.executorId,\n replyKey,\n ...extractErrorLogFields(err),\n });\n }\n }\n\n /** One raw pub/sub payload. Never throws. */\n private onMessage(message: string): void {\n this.logger.debug('[RequestReplyExecutor] Received message', { executorId: this.executorId, message });\n if (!message) {\n return;\n }\n if (this.closed) {\n this.logger.warn('[RequestReplyExecutor] Received message after executor is closed', {\n executorId: this.executorId,\n message,\n });\n return;\n }\n const pid = randomUUID();\n const requestPromise = this.processRequestFromMessage(message).finally(() => {\n this.runningRequests.delete(pid);\n });\n this.runningRequests.set(pid, requestPromise);\n }\n\n // This function should never throw!\n // Stops intake: unsubscribe from the channel and stop the heartbeat. Never\n // closes the clients — the caller owns their lifecycle.\n async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n this.logger.info('[RequestReplyExecutor] Closing', { executorId: this.executorId });\n this.closed = true;\n try {\n await this.subscriberClient.unsubscribe(this.channel);\n } catch {\n // ignore: connection may already be closed\n }\n this.stopHeartbeat();\n }\n\n // This function should never throw!\n // This is called during graceful shutdown\n async drain(): Promise<void> {\n await this.close();\n await Promise.allSettled(Array.from(this.runningRequests.values()));\n }\n}\n"],"mappings":"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","names":["message"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis request-reply transport for executor peering: route a command to the
|
|
3
|
+
* replica addressed by a plain `executorId`. Hosts own the Redis connection
|
|
4
|
+
* (inject the node-redis client) and their own id grammar — how an executor
|
|
5
|
+
* id is embedded in / parsed out of resource ids never enters this module.
|
|
6
|
+
*/
|
|
7
|
+
export { redisRequest } from './client';
|
|
8
|
+
export type { SendRequestOptions } from './client';
|
|
9
|
+
export { NoResponderError, ReplyError, RequestTimeoutError } from './errors';
|
|
10
|
+
export { RequestReplyExecutor } from './executor';
|
|
11
|
+
export type { RequestReplyErrorHandler, RunExecutorOptions } from './executor';
|
|
12
|
+
export { RequestReplyRouter } from './router';
|
|
13
|
+
export type { RouteHandler } from './router';
|
|
14
|
+
export { jsonReplySchema } from './types';
|
|
15
|
+
export type { JSONReply, JSONValue, RequestEnvelope, RequestHandler } from './types';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/request-reply/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAClD,YAAY,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC"}
|