diff --git a/engine/base/src/main/java/io/deephaven/engine/util/RemoteFileSourceClassLoader.java b/engine/base/src/main/java/io/deephaven/engine/util/RemoteFileSourceClassLoader.java
new file mode 100644
index 00000000000..fb8784892c5
--- /dev/null
+++ b/engine/base/src/main/java/io/deephaven/engine/util/RemoteFileSourceClassLoader.java
@@ -0,0 +1,254 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.engine.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A custom ClassLoader that fetches source files from remote clients via registered RemoteFileSourceProvider instances.
+ * This is designed to support Groovy script imports where the source files are provided by remote clients.
+ *
+ *
+ * When a resource is requested (e.g., for a Groovy import), this class loader:
+ *
providers = new CopyOnWriteArrayList<>();
+
+
+ /**
+ * Constructs a new RemoteFileSourceClassLoader with the specified parent class loader.
+ *
+ * @param parent the parent class loader for delegation
+ */
+ private RemoteFileSourceClassLoader(ClassLoader parent) {
+ super(parent);
+ }
+
+ /**
+ * Initializes the singleton RemoteFileSourceClassLoader instance with the specified parent class loader.
+ *
+ *
+ * This method must be called exactly once before any calls to {@link #getInstance()}. The method is synchronized to
+ * prevent race conditions when multiple threads attempt initialization.
+ *
+ * @param parent the parent class loader for delegation
+ * @return the newly created singleton instance
+ * @throws IllegalStateException if the instance has already been initialized
+ */
+ public static synchronized RemoteFileSourceClassLoader initialize(ClassLoader parent) {
+ if (instance != null) {
+ throw new IllegalStateException("RemoteFileSourceClassLoader is already initialized");
+ }
+
+ instance = new RemoteFileSourceClassLoader(parent);
+ return instance;
+ }
+
+ /**
+ * Returns the singleton instance of the RemoteFileSourceClassLoader.
+ *
+ *
+ * This method requires that {@link #initialize(ClassLoader)} has been called first.
+ *
+ * @return the singleton instance
+ * @throws IllegalStateException if the instance has not yet been initialized via {@link #initialize(ClassLoader)}
+ */
+ public static RemoteFileSourceClassLoader getInstance() {
+ if (instance == null) {
+ throw new IllegalStateException("RemoteFileSourceClassLoader is not yet initialized");
+ }
+ return instance;
+ }
+
+ /**
+ * Registers a new provider that can source remote resources.
+ *
+ * @param provider the provider to register
+ */
+ public void registerProvider(RemoteFileSourceProvider provider) {
+ providers.add(provider);
+ }
+
+ /**
+ * Unregisters a previously registered provider.
+ *
+ * @param provider the provider to unregister
+ */
+ public void unregisterProvider(RemoteFileSourceProvider provider) {
+ providers.remove(provider);
+ }
+
+ /**
+ * Returns whether there are any active providers with non-empty resource paths configured. This indicates that
+ * remote sources are actually configured, not just that the execution context is set.
+ *
+ * @return true if any provider is active and has resource paths configured, false otherwise
+ */
+ public boolean hasConfiguredRemoteSources() {
+ for (RemoteFileSourceProvider candidate : providers) {
+ if (candidate.isActive() && candidate.hasConfiguredResources()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether the current execution context is dirty, indicating that remote sources have changed and the cache
+ * should be cleared. This method is used by GroovyDeephavenSession to determine when to refresh the class cache.
+ *
+ * @return true if there is a dirty execution context, false otherwise
+ */
+ public boolean isDirty() {
+ for (RemoteFileSourceProvider candidate : providers) {
+ if (candidate.isActive() && candidate.isDirty()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets the resource with the specified name by checking registered providers.
+ *
+ *
+ * This method iterates through all registered providers to see if any can source the requested resource. If a
+ * provider can handle the resource, a custom URL with protocol "remotefile://" is returned. If no provider can
+ * handle the resource, the request is delegated to the parent class loader.
+ *
+ * @param name the resource name
+ * @return a URL for reading the resource, or null if the resource could not be found
+ */
+ @Override
+ public URL getResource(String name) {
+ RemoteFileSourceProvider provider = null;
+ for (RemoteFileSourceProvider candidate : providers) {
+ if (candidate.isActive() && candidate.canSourceResource(name)) {
+ provider = candidate;
+ break;
+ }
+ }
+
+ if (provider != null) {
+ try {
+ return new URL(null, "remotefile://" + name, new RemoteFileURLStreamHandler(provider, name));
+ } catch (MalformedURLException e) {
+ // Fall through to parent if URL creation fails
+ }
+ }
+
+ return super.getResource(name);
+ }
+
+ /**
+ * URLStreamHandler that delegates to a RemoteFileSourceProvider to fetch resource bytes.
+ */
+ private static class RemoteFileURLStreamHandler extends URLStreamHandler {
+ private final RemoteFileSourceProvider provider;
+ private final String resourceName;
+
+ /**
+ * Constructs a new RemoteFileURLStreamHandler for the specified provider and resource.
+ *
+ * @param provider the provider that will source the resource
+ * @param resourceName the name of the resource to fetch
+ */
+ RemoteFileURLStreamHandler(RemoteFileSourceProvider provider, String resourceName) {
+ this.provider = provider;
+ this.resourceName = resourceName;
+ }
+
+ /**
+ * Opens a connection to the resource referenced by this URL.
+ *
+ * @param url the URL to open a connection to
+ * @return a URLConnection to the specified URL
+ */
+ @Override
+ protected URLConnection openConnection(URL url) {
+ return new RemoteFileURLConnection(url, provider, resourceName);
+ }
+ }
+
+ /**
+ * URLConnection that fetches resource bytes from a RemoteFileSourceProvider.
+ */
+ private static class RemoteFileURLConnection extends URLConnection {
+ private final RemoteFileSourceProvider provider;
+ private final String resourceName;
+ private byte[] content;
+
+ /**
+ * Constructs a new RemoteFileURLConnection for the specified URL, provider, and resource.
+ *
+ * @param url the URL to connect to
+ * @param provider the provider that will source the resource
+ * @param resourceName the name of the resource to fetch
+ */
+ RemoteFileURLConnection(URL url, RemoteFileSourceProvider provider, String resourceName) {
+ super(url);
+ this.provider = provider;
+ this.resourceName = resourceName;
+ }
+
+ /**
+ * Opens a connection to the resource by requesting it from the provider.
+ *
+ *
+ * This method fetches the resource bytes from the provider with a timeout of {@value #RESOURCE_TIMEOUT_SECONDS}
+ * seconds. If already connected, this method does nothing.
+ *
+ * @throws IOException if the connection fails or times out
+ */
+ @Override
+ public void connect() throws IOException {
+ if (!connected) {
+ try {
+ content = provider.requestResource(resourceName)
+ .orTimeout(RESOURCE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
+ .get();
+ connected = true;
+ } catch (Exception e) {
+ throw new IOException("Failed to fetch remote resource: " + resourceName, e);
+ }
+ }
+ }
+
+ /**
+ * Returns an input stream that reads from this connection's resource.
+ *
+ *
+ * This method calls {@link #connect()} to ensure the connection is established and resource bytes are fetched
+ * from the provider. The method then verifies that content has been successfully downloaded before creating the
+ * input stream.
+ *
+ * @return an input stream that reads from the fetched resource bytes
+ * @throws IOException if the connection or content download fails or if the resource has no content
+ */
+ @Override
+ public InputStream getInputStream() throws IOException {
+ connect();
+ if (content == null || content.length == 0) {
+ throw new IOException("No content for resource: " + resourceName);
+ }
+ return new ByteArrayInputStream(content);
+ }
+ }
+}
diff --git a/engine/base/src/main/java/io/deephaven/engine/util/RemoteFileSourceProvider.java b/engine/base/src/main/java/io/deephaven/engine/util/RemoteFileSourceProvider.java
new file mode 100644
index 00000000000..dae0ee077f9
--- /dev/null
+++ b/engine/base/src/main/java/io/deephaven/engine/util/RemoteFileSourceProvider.java
@@ -0,0 +1,52 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.engine.util;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Interface for providing remote resources to the ClassLoader. Plugins can implement this interface and register with
+ * RemoteFileSourceClassLoader to provide resources from remote sources.
+ */
+public interface RemoteFileSourceProvider {
+ /**
+ * Check if this provider can source the given resource.
+ *
+ * @param resourceName the name of the resource to check (e.g., "com/example/MyClass.groovy")
+ * @return true if this provider can handle the resource, false otherwise
+ */
+ boolean canSourceResource(String resourceName);
+
+ /**
+ * Check if this provider is currently active and should be used for resource requests.
+ *
+ * @return true if this provider is active, false otherwise
+ */
+ boolean isActive();
+
+ /**
+ * Check if this provider has any resource paths configured. A provider can be active (execution context set) but
+ * have no resource paths configured.
+ *
+ * @return true if this provider has resource paths configured, false otherwise
+ */
+ boolean hasConfiguredResources();
+
+ /**
+ * Check if this provider's execution context is dirty, indicating that remote sources have changed and the cache
+ * should be cleared.
+ *
+ * @return true if this provider is active and dirty, false otherwise
+ */
+ boolean isDirty();
+
+ /**
+ * Request a resource from the remote source.
+ *
+ * @param resourceName the name of the resource to fetch (e.g., "com/example/MyClass.groovy")
+ * @return a CompletableFuture containing the resource bytes, or null if not found
+ */
+ CompletableFuture requestResource(String resourceName);
+}
+
diff --git a/engine/context/src/main/java/io/deephaven/engine/context/ExecutionContext.java b/engine/context/src/main/java/io/deephaven/engine/context/ExecutionContext.java
index 71b55a68cda..d4622acff56 100644
--- a/engine/context/src/main/java/io/deephaven/engine/context/ExecutionContext.java
+++ b/engine/context/src/main/java/io/deephaven/engine/context/ExecutionContext.java
@@ -205,6 +205,21 @@ public ExecutionContext withClassLoader(ClassLoader classLoader) {
operationInitializer, classLoader);
}
+ /**
+ * Returns, or creates, an execution context with the given value for {@code queryCompiler} and existing values for
+ * the other members.
+ *
+ * @param queryCompiler the query compiler to use instead
+ * @return the execution context
+ */
+ public ExecutionContext withQueryCompiler(QueryCompiler queryCompiler) {
+ if (queryCompiler == this.queryCompiler) {
+ return this;
+ }
+ return new ExecutionContext(isSystemic, authContext, queryLibrary, queryScope, queryCompiler, updateGraph,
+ operationInitializer);
+ }
+
/**
* Execute runnable within this execution context.
diff --git a/engine/table/src/main/java/io/deephaven/engine/util/AbstractScriptSession.java b/engine/table/src/main/java/io/deephaven/engine/util/AbstractScriptSession.java
index eee2d650535..9701bbec46f 100644
--- a/engine/table/src/main/java/io/deephaven/engine/util/AbstractScriptSession.java
+++ b/engine/table/src/main/java/io/deephaven/engine/util/AbstractScriptSession.java
@@ -69,7 +69,8 @@ private static void ensureDirectory(final Path directory) {
protected final File classCacheDirectory;
private final ScriptSessionQueryScope queryScope;
- protected final ExecutionContext executionContext;
+ // DH-20578: Not final so it can be updated with fresh QueryCompiler when remote sources are detected
+ protected ExecutionContext executionContext;
private S lastSnapshot;
@@ -95,9 +96,26 @@ protected AbstractScriptSession(
this.classCacheDirectory = classCacheDirectory;
queryScope = new ScriptSessionQueryScope();
+
+ executionContext = createExecutionContext(updateGraph, operationInitializer, parentClassLoader);
+ }
+
+ /**
+ * DH-20578: Creates an ExecutionContext with a QueryCompiler based on the provided ClassLoader. This allows
+ * updating the ExecutionContext when fresh ClassLoaders are needed (e.g., for remote file sources).
+ *
+ * @param updateGraph the update graph for the context
+ * @param operationInitializer the operation initializer for the context
+ * @param parentClassLoader the ClassLoader to use for creating the QueryCompiler
+ * @return a new ExecutionContext with a QueryCompiler based on the provided ClassLoader
+ */
+ protected ExecutionContext createExecutionContext(
+ final UpdateGraph updateGraph,
+ final OperationInitializer operationInitializer,
+ final ClassLoader parentClassLoader) {
final QueryCompiler compilerContext = QueryCompilerImpl.create(classCacheDirectory, parentClassLoader);
- executionContext = ExecutionContext.newBuilder()
+ return ExecutionContext.newBuilder()
.markSystemic()
.newQueryLibrary()
.setQueryScope(queryScope)
diff --git a/engine/table/src/main/java/io/deephaven/engine/util/GroovyDeephavenSession.java b/engine/table/src/main/java/io/deephaven/engine/util/GroovyDeephavenSession.java
index 6bb8c5d262d..89f5efe59b5 100644
--- a/engine/table/src/main/java/io/deephaven/engine/util/GroovyDeephavenSession.java
+++ b/engine/table/src/main/java/io/deephaven/engine/util/GroovyDeephavenSession.java
@@ -96,7 +96,8 @@ public class GroovyDeephavenSession extends AbstractScriptSession mapping = new ConcurrentHashMap<>();
@Override
@@ -131,6 +132,9 @@ protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundE
/** Contains imports to be applied to .groovy files loaded from the classpath */
private final ImportCustomizer loadedGroovyScriptImports;
+ private final CompilerConfiguration scriptConfig;
+ private final CompilerConfiguration consoleConfig;
+
private final Set dynamicClasses;
private final Map bindingBackingMap;
@@ -162,7 +166,9 @@ public SafeCloseable setScriptPrefix(final String newPrefix) {
}
}
- private final DeephavenGroovyShell groovyShell;
+ private DeephavenGroovyShell groovyShell;
+
+ private boolean previousEvalHadRemoteSources = false;
public static GroovyDeephavenSession of(
final UpdateGraph updateGraph,
@@ -206,8 +212,8 @@ public static GroovyDeephavenSession of(
return new GroovyDeephavenSession(updateGraph, operationInitializer, objectTypeLookup, changeListener,
- runScripts, classCacheDirectory, consoleImports, loadedGroovyScriptImports, bindingBackingMap,
- groovyShell);
+ runScripts, classCacheDirectory, consoleImports, loadedGroovyScriptImports, scriptConfig,
+ consoleConfig, bindingBackingMap, groovyShell);
}
private GroovyDeephavenSession(
@@ -219,6 +225,8 @@ private GroovyDeephavenSession(
final File classCacheDirectory,
final ImportCustomizer consoleImports,
final ImportCustomizer loadedGroovyScriptImports,
+ final CompilerConfiguration scriptConfig,
+ final CompilerConfiguration consoleConfig,
final Map bindingBackingMap,
final DeephavenGroovyShell groovyShell)
throws IOException {
@@ -229,6 +237,8 @@ private GroovyDeephavenSession(
this.consoleImports = consoleImports;
this.loadedGroovyScriptImports = loadedGroovyScriptImports;
+ this.scriptConfig = scriptConfig;
+ this.consoleConfig = consoleConfig;
this.dynamicClasses = new HashSet<>();
this.bindingBackingMap = bindingBackingMap;
@@ -332,6 +342,31 @@ protected T getVariable(String name) {
}
}
+ /**
+ * Clears cached .class files and refreshes the GroovyClassLoader and associated shell. This ensures that classes
+ * will be recompiled from source on the next execution.
+ *
+ * @throws IllegalStateException if the cache directory does not exist
+ */
+ private void refreshClassLoader() {
+ // If no cache directory exists, this is an error - we shouldn't be trying to refresh without a cache
+ if (classCacheDirectory == null || !classCacheDirectory.exists()) {
+ throw new IllegalStateException("Cannot refresh classloader: cache directory does not exist");
+ }
+
+ deleteCachedClassFiles(classCacheDirectory);
+
+ // Create fresh classloader - reusing the existing configurations
+ GroovyClassLoader scriptClassLoader = new GroovyClassLoader(STATIC_LOADER, scriptConfig);
+
+ // Update the execution context with a fresh QueryCompiler
+ final QueryCompiler freshCompiler = QueryCompilerImpl.create(classCacheDirectory, scriptClassLoader);
+ executionContext = executionContext.withQueryCompiler(freshCompiler);
+
+ // Permanently replace groovyShell with fresh shell
+ groovyShell = new DeephavenGroovyShell(scriptClassLoader, groovyShell.getContext(), consoleConfig);
+ }
+
@Override
protected void evaluate(String command, String scriptName) {
grepScriptImports(removeComments(command));
@@ -343,8 +378,26 @@ protected void evaluate(String command, String scriptName) {
final String currentScriptName = scriptName == null
? DEFAULT_SCRIPT_PREFIX
: scriptName.replaceAll("[^0-9A-Za-z_]", "_").replaceAll("(^[0-9])", "_$1");
- try (final SafeCloseable ignored = groovyShell.setScriptPrefix(currentScriptName)) {
+ final RemoteFileSourceClassLoader remoteLoader = RemoteFileSourceClassLoader.getInstance();
+ final boolean hasRemoteSources = remoteLoader.hasConfiguredRemoteSources();
+ final boolean isDirty = remoteLoader.isDirty();
+
+ // Clear the cache in two cases:
+ // 1. is_dirty flag is set - remote sources have changed
+ // 2. Previous eval had remote sources but current does not - catches edge case where script is run
+ // without providing execution context at all, and we need to clear from previous remote source scenario
+ if (isDirty || (previousEvalHadRemoteSources && !hasRemoteSources)) {
+ log.debug("Clearing class cache. isDirty: " + isDirty + ", previousEvalHadRemoteSources: "
+ + previousEvalHadRemoteSources + ", hasRemoteSources: " + hasRemoteSources);
+ refreshClassLoader();
+ }
+
+ // Update state tracker for next execution
+ previousEvalHadRemoteSources = hasRemoteSources;
+
+ // Execute the script
+ try (final SafeCloseable ignored = groovyShell.setScriptPrefix(currentScriptName)) {
updateClassloader(lastCommand);
try {
ExecutionContext.getContext().getUpdateGraph().exclusiveLock()
@@ -358,6 +411,7 @@ protected void evaluate(String command, String scriptName) {
}
}
+
private RuntimeException wrapAndRewriteStackTrace(String scriptName, String currentScriptName, Exception e,
String lastCommand, String commandPrefix) {
final Exception en = maybeRewriteStackTrace(scriptName, currentScriptName, e, lastCommand, commandPrefix);
@@ -688,7 +742,7 @@ && isAnInteger(aClass.getName().substring(SCRIPT_PREFIX.length()))) {
// only increment QueryLibrary version if some dynamic class overrides an existing class
if (!dynamicClasses.add(entry.getKey()) && !notifiedQueryLibrary) {
notifiedQueryLibrary = true;
- executionContext.getQueryLibrary().updateVersionString();
+ getExecutionContext().getQueryLibrary().updateVersionString();
}
try {
@@ -716,6 +770,36 @@ private static boolean isAnInteger(final String s) {
}
}
+ /**
+ * Recursively delete all .class files from the cache directory to force recompilation. This is necessary because
+ * GroovyClassLoader can reload .class files from disk even after clearCache().
+ *
+ * @param directory the directory to recursively search for .class files
+ */
+ private void deleteCachedClassFiles(@NotNull File directory) {
+ if (!directory.exists() || !directory.isDirectory()) {
+ return;
+ }
+
+ File[] files = directory.listFiles();
+ if (files == null) {
+ return;
+ }
+
+ log.debug().append("Deleting ").append(files.length).append(" files from class cache directory: ")
+ .append(directory.getAbsolutePath());
+
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteCachedClassFiles(file);
+ } else if (file.getName().endsWith(".class")) {
+ if (!file.delete()) {
+ log.warn("Failed to delete cached class file: " + file.getAbsolutePath());
+ }
+ }
+ }
+ }
+
@Override
protected GroovySnapshot emptySnapshot() {
return new GroovySnapshot(Collections.emptyMap());
diff --git a/go/internal/proto/remotefilesource/remotefilesource.pb.go b/go/internal/proto/remotefilesource/remotefilesource.pb.go
new file mode 100644
index 00000000000..619507922bf
--- /dev/null
+++ b/go/internal/proto/remotefilesource/remotefilesource.pb.go
@@ -0,0 +1,731 @@
+//
+// Copyright (c) 2016-2025 Deephaven Data Labs and Patent Pending
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.28.1
+// protoc v6.33.2
+// source: deephaven_core/proto/remotefilesource.proto
+
+package remotefilesource
+
+import (
+ ticket "github.com/deephaven/deephaven-core/go/internal/proto/ticket"
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// Server → Client: Requests sent from server to client via MessageStream
+type RemoteFileSourceServerMessage struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // Unique identifier for this request, used to correlate the response
+ RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
+ // Types that are assignable to Request:
+ // *RemoteFileSourceServerMessage_MetaRequest
+ // *RemoteFileSourceServerMessage_SetExecutionContextResponse
+ Request isRemoteFileSourceServerMessage_Request `protobuf_oneof:"request"`
+}
+
+func (x *RemoteFileSourceServerMessage) Reset() {
+ *x = RemoteFileSourceServerMessage{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *RemoteFileSourceServerMessage) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RemoteFileSourceServerMessage) ProtoMessage() {}
+
+func (x *RemoteFileSourceServerMessage) ProtoReflect() protoreflect.Message {
+ mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RemoteFileSourceServerMessage.ProtoReflect.Descriptor instead.
+func (*RemoteFileSourceServerMessage) Descriptor() ([]byte, []int) {
+ return file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *RemoteFileSourceServerMessage) GetRequestId() string {
+ if x != nil {
+ return x.RequestId
+ }
+ return ""
+}
+
+func (m *RemoteFileSourceServerMessage) GetRequest() isRemoteFileSourceServerMessage_Request {
+ if m != nil {
+ return m.Request
+ }
+ return nil
+}
+
+func (x *RemoteFileSourceServerMessage) GetMetaRequest() *RemoteFileSourceMetaRequest {
+ if x, ok := x.GetRequest().(*RemoteFileSourceServerMessage_MetaRequest); ok {
+ return x.MetaRequest
+ }
+ return nil
+}
+
+func (x *RemoteFileSourceServerMessage) GetSetExecutionContextResponse() *SetExecutionContextResponse {
+ if x, ok := x.GetRequest().(*RemoteFileSourceServerMessage_SetExecutionContextResponse); ok {
+ return x.SetExecutionContextResponse
+ }
+ return nil
+}
+
+type isRemoteFileSourceServerMessage_Request interface {
+ isRemoteFileSourceServerMessage_Request()
+}
+
+type RemoteFileSourceServerMessage_MetaRequest struct {
+ // Request source data/resource from the client
+ MetaRequest *RemoteFileSourceMetaRequest `protobuf:"bytes,2,opt,name=meta_request,json=metaRequest,proto3,oneof"`
+}
+
+type RemoteFileSourceServerMessage_SetExecutionContextResponse struct {
+ // Acknowledgment that execution context was set
+ SetExecutionContextResponse *SetExecutionContextResponse `protobuf:"bytes,3,opt,name=set_execution_context_response,json=setExecutionContextResponse,proto3,oneof"`
+}
+
+func (*RemoteFileSourceServerMessage_MetaRequest) isRemoteFileSourceServerMessage_Request() {}
+
+func (*RemoteFileSourceServerMessage_SetExecutionContextResponse) isRemoteFileSourceServerMessage_Request() {
+}
+
+// Client → Server: Requests/responses sent from client to server via MessageStream
+type RemoteFileSourceClientMessage struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // The request_id from the ServerRequest this is responding to (if applicable)
+ RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
+ // Types that are assignable to Request:
+ // *RemoteFileSourceClientMessage_MetaResponse
+ // *RemoteFileSourceClientMessage_SetExecutionContext
+ Request isRemoteFileSourceClientMessage_Request `protobuf_oneof:"request"`
+}
+
+func (x *RemoteFileSourceClientMessage) Reset() {
+ *x = RemoteFileSourceClientMessage{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *RemoteFileSourceClientMessage) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RemoteFileSourceClientMessage) ProtoMessage() {}
+
+func (x *RemoteFileSourceClientMessage) ProtoReflect() protoreflect.Message {
+ mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RemoteFileSourceClientMessage.ProtoReflect.Descriptor instead.
+func (*RemoteFileSourceClientMessage) Descriptor() ([]byte, []int) {
+ return file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *RemoteFileSourceClientMessage) GetRequestId() string {
+ if x != nil {
+ return x.RequestId
+ }
+ return ""
+}
+
+func (m *RemoteFileSourceClientMessage) GetRequest() isRemoteFileSourceClientMessage_Request {
+ if m != nil {
+ return m.Request
+ }
+ return nil
+}
+
+func (x *RemoteFileSourceClientMessage) GetMetaResponse() *RemoteFileSourceMetaResponse {
+ if x, ok := x.GetRequest().(*RemoteFileSourceClientMessage_MetaResponse); ok {
+ return x.MetaResponse
+ }
+ return nil
+}
+
+func (x *RemoteFileSourceClientMessage) GetSetExecutionContext() *SetExecutionContextRequest {
+ if x, ok := x.GetRequest().(*RemoteFileSourceClientMessage_SetExecutionContext); ok {
+ return x.SetExecutionContext
+ }
+ return nil
+}
+
+type isRemoteFileSourceClientMessage_Request interface {
+ isRemoteFileSourceClientMessage_Request()
+}
+
+type RemoteFileSourceClientMessage_MetaResponse struct {
+ // Response to a resource request
+ MetaResponse *RemoteFileSourceMetaResponse `protobuf:"bytes,2,opt,name=meta_response,json=metaResponse,proto3,oneof"`
+}
+
+type RemoteFileSourceClientMessage_SetExecutionContext struct {
+ // Set the execution context for script execution
+ SetExecutionContext *SetExecutionContextRequest `protobuf:"bytes,3,opt,name=set_execution_context,json=setExecutionContext,proto3,oneof"`
+}
+
+func (*RemoteFileSourceClientMessage_MetaResponse) isRemoteFileSourceClientMessage_Request() {}
+
+func (*RemoteFileSourceClientMessage_SetExecutionContext) isRemoteFileSourceClientMessage_Request() {}
+
+// Request source data/resource from the client
+type RemoteFileSourceMetaRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // The name/path of the resource being requested (e.g., "com/example/MyClass.java")
+ ResourceName string `protobuf:"bytes,1,opt,name=resource_name,json=resourceName,proto3" json:"resource_name,omitempty"`
+}
+
+func (x *RemoteFileSourceMetaRequest) Reset() {
+ *x = RemoteFileSourceMetaRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *RemoteFileSourceMetaRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RemoteFileSourceMetaRequest) ProtoMessage() {}
+
+func (x *RemoteFileSourceMetaRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RemoteFileSourceMetaRequest.ProtoReflect.Descriptor instead.
+func (*RemoteFileSourceMetaRequest) Descriptor() ([]byte, []int) {
+ return file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *RemoteFileSourceMetaRequest) GetResourceName() string {
+ if x != nil {
+ return x.ResourceName
+ }
+ return ""
+}
+
+// Response to a resource request
+type RemoteFileSourceMetaResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // The content of the resource, or empty if not found
+ Content []byte `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"`
+ // Indicates whether the resource was found
+ Found bool `protobuf:"varint,2,opt,name=found,proto3" json:"found,omitempty"`
+ // Error message if the resource could not be retrieved
+ Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
+}
+
+func (x *RemoteFileSourceMetaResponse) Reset() {
+ *x = RemoteFileSourceMetaResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *RemoteFileSourceMetaResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RemoteFileSourceMetaResponse) ProtoMessage() {}
+
+func (x *RemoteFileSourceMetaResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RemoteFileSourceMetaResponse.ProtoReflect.Descriptor instead.
+func (*RemoteFileSourceMetaResponse) Descriptor() ([]byte, []int) {
+ return file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *RemoteFileSourceMetaResponse) GetContent() []byte {
+ if x != nil {
+ return x.Content
+ }
+ return nil
+}
+
+func (x *RemoteFileSourceMetaResponse) GetFound() bool {
+ if x != nil {
+ return x.Found
+ }
+ return false
+}
+
+func (x *RemoteFileSourceMetaResponse) GetError() string {
+ if x != nil {
+ return x.Error
+ }
+ return ""
+}
+
+// Request to set the execution context for script execution
+type SetExecutionContextRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // Resource paths that should be resolved from the remote source
+ // (e.g., ["com/example/Test.groovy", "org/mycompany/Utils.groovy"])
+ ResourcePaths []string `protobuf:"bytes,1,rep,name=resource_paths,json=resourcePaths,proto3" json:"resource_paths,omitempty"`
+ // Indicates whether remote sources have changed and cache should be cleared
+ IsDirty bool `protobuf:"varint,2,opt,name=is_dirty,json=isDirty,proto3" json:"is_dirty,omitempty"`
+}
+
+func (x *SetExecutionContextRequest) Reset() {
+ *x = SetExecutionContextRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *SetExecutionContextRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SetExecutionContextRequest) ProtoMessage() {}
+
+func (x *SetExecutionContextRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[4]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SetExecutionContextRequest.ProtoReflect.Descriptor instead.
+func (*SetExecutionContextRequest) Descriptor() ([]byte, []int) {
+ return file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *SetExecutionContextRequest) GetResourcePaths() []string {
+ if x != nil {
+ return x.ResourcePaths
+ }
+ return nil
+}
+
+func (x *SetExecutionContextRequest) GetIsDirty() bool {
+ if x != nil {
+ return x.IsDirty
+ }
+ return false
+}
+
+// Response acknowledging execution context was set
+type SetExecutionContextResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // Whether the operation was successful
+ Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
+}
+
+func (x *SetExecutionContextResponse) Reset() {
+ *x = SetExecutionContextResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *SetExecutionContextResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SetExecutionContextResponse) ProtoMessage() {}
+
+func (x *SetExecutionContextResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[5]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SetExecutionContextResponse.ProtoReflect.Descriptor instead.
+func (*SetExecutionContextResponse) Descriptor() ([]byte, []int) {
+ return file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *SetExecutionContextResponse) GetSuccess() bool {
+ if x != nil {
+ return x.Success
+ }
+ return false
+}
+
+// Fetch the remote file source plugin into the specified ticket (Flight command, not MessageStream)
+type RemoteFileSourcePluginFetchRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ ResultId *ticket.Ticket `protobuf:"bytes,1,opt,name=result_id,json=resultId,proto3" json:"result_id,omitempty"`
+ // The plugin name to create the PluginMarker for.
+ PluginName string `protobuf:"bytes,2,opt,name=plugin_name,json=pluginName,proto3" json:"plugin_name,omitempty"`
+}
+
+func (x *RemoteFileSourcePluginFetchRequest) Reset() {
+ *x = RemoteFileSourcePluginFetchRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *RemoteFileSourcePluginFetchRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RemoteFileSourcePluginFetchRequest) ProtoMessage() {}
+
+func (x *RemoteFileSourcePluginFetchRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_deephaven_core_proto_remotefilesource_proto_msgTypes[6]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RemoteFileSourcePluginFetchRequest.ProtoReflect.Descriptor instead.
+func (*RemoteFileSourcePluginFetchRequest) Descriptor() ([]byte, []int) {
+ return file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *RemoteFileSourcePluginFetchRequest) GetResultId() *ticket.Ticket {
+ if x != nil {
+ return x.ResultId
+ }
+ return nil
+}
+
+func (x *RemoteFileSourcePluginFetchRequest) GetPluginName() string {
+ if x != nil {
+ return x.PluginName
+ }
+ return ""
+}
+
+var File_deephaven_core_proto_remotefilesource_proto protoreflect.FileDescriptor
+
+var file_deephaven_core_proto_remotefilesource_proto_rawDesc = []byte{
+ 0x0a, 0x2b, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, 0x76, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x72, 0x65,
+ 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x66, 0x69, 0x6c,
+ 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x21, 0x69,
+ 0x6f, 0x2e, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, 0x76, 0x65, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x67, 0x72, 0x70, 0x63,
+ 0x1a, 0x21, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, 0x76, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x72, 0x65,
+ 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x22, 0xb6, 0x02, 0x0a, 0x1d, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46, 0x69,
+ 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x65,
+ 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+ 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65,
+ 0x73, 0x74, 0x49, 0x64, 0x12, 0x63, 0x0a, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x5f, 0x72, 0x65, 0x71,
+ 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x69, 0x6f, 0x2e,
+ 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, 0x76, 0x65, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
+ 0x62, 0x61, 0x63, 0x6b, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52,
+ 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d,
+ 0x65, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6d, 0x65,
+ 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x85, 0x01, 0x0a, 0x1e, 0x73, 0x65,
+ 0x74, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x74,
+ 0x65, 0x78, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01,
+ 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, 0x76, 0x65,
+ 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x70, 0x6c, 0x61, 0x6e,
+ 0x65, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74,
+ 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+ 0x73, 0x65, 0x48, 0x00, 0x52, 0x1b, 0x73, 0x65, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69,
+ 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+ 0x65, 0x42, 0x09, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa6, 0x02, 0x0a,
+ 0x1d, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63,
+ 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1d,
+ 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x66, 0x0a,
+ 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02,
+ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61,
+ 0x76, 0x65, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x70, 0x6c,
+ 0x61, 0x6e, 0x65, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46,
+ 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x73, 0x0a, 0x15, 0x73, 0x65, 0x74, 0x5f, 0x65, 0x78, 0x65,
+ 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x03,
+ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61,
+ 0x76, 0x65, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x70, 0x6c,
+ 0x61, 0x6e, 0x65, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x78, 0x65, 0x63,
+ 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75,
+ 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x13, 0x73, 0x65, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74,
+ 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x72, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x42, 0x0a, 0x1b, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46,
+ 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x71,
+ 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
+ 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73,
+ 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x64, 0x0a, 0x1c, 0x52, 0x65, 0x6d,
+ 0x6f, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74,
+ 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e,
+ 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74,
+ 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72,
+ 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22,
+ 0x5e, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x43,
+ 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a,
+ 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18,
+ 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50,
+ 0x61, 0x74, 0x68, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x64, 0x69, 0x72, 0x74, 0x79,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x44, 0x69, 0x72, 0x74, 0x79, 0x22,
+ 0x37, 0x0a, 0x1b, 0x53, 0x65, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x43,
+ 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18,
+ 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
+ 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x8d, 0x01, 0x0a, 0x22, 0x52, 0x65, 0x6d,
+ 0x6f, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6c, 0x75,
+ 0x67, 0x69, 0x6e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+ 0x46, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
+ 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x69, 0x6f, 0x2e, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, 0x76, 0x65,
+ 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x70, 0x6c, 0x61, 0x6e,
+ 0x65, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x08, 0x72,
+ 0x65, 0x73, 0x75, 0x6c, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x6c, 0x75, 0x67, 0x69,
+ 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x6c,
+ 0x75, 0x67, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x4c, 0x48, 0x01, 0x50, 0x01, 0x5a, 0x46,
+ 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x65, 0x65, 0x70, 0x68,
+ 0x61, 0x76, 0x65, 0x6e, 0x2f, 0x64, 0x65, 0x65, 0x70, 0x68, 0x61, 0x76, 0x65, 0x6e, 0x2d, 0x63,
+ 0x6f, 0x72, 0x65, 0x2f, 0x67, 0x6f, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x66, 0x69, 0x6c, 0x65,
+ 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_deephaven_core_proto_remotefilesource_proto_rawDescOnce sync.Once
+ file_deephaven_core_proto_remotefilesource_proto_rawDescData = file_deephaven_core_proto_remotefilesource_proto_rawDesc
+)
+
+func file_deephaven_core_proto_remotefilesource_proto_rawDescGZIP() []byte {
+ file_deephaven_core_proto_remotefilesource_proto_rawDescOnce.Do(func() {
+ file_deephaven_core_proto_remotefilesource_proto_rawDescData = protoimpl.X.CompressGZIP(file_deephaven_core_proto_remotefilesource_proto_rawDescData)
+ })
+ return file_deephaven_core_proto_remotefilesource_proto_rawDescData
+}
+
+var file_deephaven_core_proto_remotefilesource_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
+var file_deephaven_core_proto_remotefilesource_proto_goTypes = []interface{}{
+ (*RemoteFileSourceServerMessage)(nil), // 0: io.deephaven.proto.backplane.grpc.RemoteFileSourceServerMessage
+ (*RemoteFileSourceClientMessage)(nil), // 1: io.deephaven.proto.backplane.grpc.RemoteFileSourceClientMessage
+ (*RemoteFileSourceMetaRequest)(nil), // 2: io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaRequest
+ (*RemoteFileSourceMetaResponse)(nil), // 3: io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaResponse
+ (*SetExecutionContextRequest)(nil), // 4: io.deephaven.proto.backplane.grpc.SetExecutionContextRequest
+ (*SetExecutionContextResponse)(nil), // 5: io.deephaven.proto.backplane.grpc.SetExecutionContextResponse
+ (*RemoteFileSourcePluginFetchRequest)(nil), // 6: io.deephaven.proto.backplane.grpc.RemoteFileSourcePluginFetchRequest
+ (*ticket.Ticket)(nil), // 7: io.deephaven.proto.backplane.grpc.Ticket
+}
+var file_deephaven_core_proto_remotefilesource_proto_depIdxs = []int32{
+ 2, // 0: io.deephaven.proto.backplane.grpc.RemoteFileSourceServerMessage.meta_request:type_name -> io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaRequest
+ 5, // 1: io.deephaven.proto.backplane.grpc.RemoteFileSourceServerMessage.set_execution_context_response:type_name -> io.deephaven.proto.backplane.grpc.SetExecutionContextResponse
+ 3, // 2: io.deephaven.proto.backplane.grpc.RemoteFileSourceClientMessage.meta_response:type_name -> io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaResponse
+ 4, // 3: io.deephaven.proto.backplane.grpc.RemoteFileSourceClientMessage.set_execution_context:type_name -> io.deephaven.proto.backplane.grpc.SetExecutionContextRequest
+ 7, // 4: io.deephaven.proto.backplane.grpc.RemoteFileSourcePluginFetchRequest.result_id:type_name -> io.deephaven.proto.backplane.grpc.Ticket
+ 5, // [5:5] is the sub-list for method output_type
+ 5, // [5:5] is the sub-list for method input_type
+ 5, // [5:5] is the sub-list for extension type_name
+ 5, // [5:5] is the sub-list for extension extendee
+ 0, // [0:5] is the sub-list for field type_name
+}
+
+func init() { file_deephaven_core_proto_remotefilesource_proto_init() }
+func file_deephaven_core_proto_remotefilesource_proto_init() {
+ if File_deephaven_core_proto_remotefilesource_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_deephaven_core_proto_remotefilesource_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*RemoteFileSourceServerMessage); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_deephaven_core_proto_remotefilesource_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*RemoteFileSourceClientMessage); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_deephaven_core_proto_remotefilesource_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*RemoteFileSourceMetaRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_deephaven_core_proto_remotefilesource_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*RemoteFileSourceMetaResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_deephaven_core_proto_remotefilesource_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*SetExecutionContextRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_deephaven_core_proto_remotefilesource_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*SetExecutionContextResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_deephaven_core_proto_remotefilesource_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*RemoteFileSourcePluginFetchRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ file_deephaven_core_proto_remotefilesource_proto_msgTypes[0].OneofWrappers = []interface{}{
+ (*RemoteFileSourceServerMessage_MetaRequest)(nil),
+ (*RemoteFileSourceServerMessage_SetExecutionContextResponse)(nil),
+ }
+ file_deephaven_core_proto_remotefilesource_proto_msgTypes[1].OneofWrappers = []interface{}{
+ (*RemoteFileSourceClientMessage_MetaResponse)(nil),
+ (*RemoteFileSourceClientMessage_SetExecutionContext)(nil),
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_deephaven_core_proto_remotefilesource_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 7,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_deephaven_core_proto_remotefilesource_proto_goTypes,
+ DependencyIndexes: file_deephaven_core_proto_remotefilesource_proto_depIdxs,
+ MessageInfos: file_deephaven_core_proto_remotefilesource_proto_msgTypes,
+ }.Build()
+ File_deephaven_core_proto_remotefilesource_proto = out.File
+ file_deephaven_core_proto_remotefilesource_proto_rawDesc = nil
+ file_deephaven_core_proto_remotefilesource_proto_goTypes = nil
+ file_deephaven_core_proto_remotefilesource_proto_depIdxs = nil
+}
diff --git a/plugin/remotefilesource/build.gradle b/plugin/remotefilesource/build.gradle
new file mode 100644
index 00000000000..61e914c8f88
--- /dev/null
+++ b/plugin/remotefilesource/build.gradle
@@ -0,0 +1,14 @@
+plugins {
+ id 'io.deephaven.project.register'
+}
+
+dependencies {
+ implementation project(':plugin')
+ implementation project(':server')
+ implementation project(':proto:proto-backplane-grpc')
+ implementation project(':proto:proto-backplane-grpc-flight')
+
+ compileOnly libs.autoservice
+ compileOnly libs.jetbrains.annotations
+ annotationProcessor libs.autoservice.compiler
+}
\ No newline at end of file
diff --git a/plugin/remotefilesource/gradle.properties b/plugin/remotefilesource/gradle.properties
new file mode 100644
index 00000000000..c186bbfdde1
--- /dev/null
+++ b/plugin/remotefilesource/gradle.properties
@@ -0,0 +1 @@
+io.deephaven.project.ProjectType=JAVA_PUBLIC
diff --git a/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceCommandResolver.java b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceCommandResolver.java
new file mode 100644
index 00000000000..5c8bef481ff
--- /dev/null
+++ b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceCommandResolver.java
@@ -0,0 +1,266 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.remotefilesource;
+
+import com.google.protobuf.Any;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.rpc.Code;
+import io.deephaven.UncheckedDeephavenException;
+import io.deephaven.base.verify.Assert;
+import io.deephaven.internal.log.LoggerFactory;
+import io.deephaven.io.logger.Logger;
+import io.deephaven.plugin.type.PluginMarker;
+import io.deephaven.proto.backplane.grpc.RemoteFileSourcePluginFetchRequest;
+import io.deephaven.proto.backplane.grpc.Ticket;
+import io.deephaven.proto.util.Exceptions;
+import io.deephaven.server.session.CommandResolver;
+import io.deephaven.server.session.SessionState;
+import io.deephaven.server.session.TicketRouter;
+import io.deephaven.server.session.WantsTicketRouter;
+
+import io.grpc.StatusRuntimeException;
+import org.apache.arrow.flight.impl.Flight;
+import org.jetbrains.annotations.Nullable;
+
+import java.nio.ByteBuffer;
+import java.util.function.Consumer;
+
+public class RemoteFileSourceCommandResolver implements CommandResolver, WantsTicketRouter {
+ private static final Logger log = LoggerFactory.getLogger(RemoteFileSourceCommandResolver.class);
+
+ private static final String FETCH_PLUGIN_TYPE_URL =
+ "type.googleapis.com/" + RemoteFileSourcePluginFetchRequest.getDescriptor().getFullName();
+
+ /**
+ * Parses a RemoteFileSourcePluginFetchRequest from the given Any command.
+ *
+ * @param command the Any command containing the fetch request
+ * @return the parsed RemoteFileSourcePluginFetchRequest
+ * @throws IllegalArgumentException if the command type URL doesn't match the expected fetch plugin type
+ * @throws UncheckedDeephavenException if the command cannot be parsed as a RemoteFileSourcePluginFetchRequest
+ */
+ private static RemoteFileSourcePluginFetchRequest parseFetchRequest(final Any command) {
+ if (!FETCH_PLUGIN_TYPE_URL.equals(command.getTypeUrl())) {
+ throw new IllegalArgumentException("Not a valid remotefilesource command: " + command.getTypeUrl());
+ }
+
+ try {
+ return RemoteFileSourcePluginFetchRequest.parseFrom(command.getValue());
+ } catch (InvalidProtocolBufferException e) {
+ throw new UncheckedDeephavenException("Could not parse RemoteFileSourcePluginFetchRequest", e);
+ }
+ }
+
+ /**
+ * Attempts to parse ByteString data as a protobuf Any message. Returns null if parsing fails rather than throwing
+ * an exception, allowing callers to handle invalid data gracefully.
+ *
+ * @param data the ByteString data to parse
+ * @return the parsed Any message, or null if parsing fails
+ */
+ private static Any parseAsAnyOrNull(final ByteString data) {
+ try {
+ return Any.parseFrom(data);
+ } catch (final InvalidProtocolBufferException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Exports the PluginMarker singleton based on the fetch request. The marker object is exported to the session using
+ * the result ticket specified in the request, and flight info is returned containing the endpoint for accessing it.
+ *
+ *
+ * Note: This exports a PluginMarker for the specified plugin name. Plugin-specific routing is handled by
+ * TypedTicket.type in the ConnectRequest phase, which is validated against the plugin's name() method.
+ *
+ * @param session the session state for the current request
+ * @param descriptor the flight descriptor containing the command
+ * @param request the parsed RemoteFileSourcePluginFetchRequest containing the result ticket
+ * @return a FlightInfo export object containing the plugin endpoint information
+ * @throws StatusRuntimeException if the request doesn't contain a valid result ID ticket or plugin name
+ */
+ private static SessionState.ExportObject fetchPlugin(@Nullable final SessionState session,
+ final Flight.FlightDescriptor descriptor,
+ final RemoteFileSourcePluginFetchRequest request) {
+ final Ticket resultTicket = request.getResultId();
+ final boolean hasResultId = !resultTicket.getTicket().isEmpty();
+ if (!hasResultId) {
+ throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT,
+ "RemoteFileSourcePluginFetchRequest must contain a valid result_id");
+ }
+
+ final String pluginName = request.getPluginName();
+ if (pluginName.isEmpty()) {
+ throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT,
+ "RemoteFileSourcePluginFetchRequest must contain a valid plugin_name");
+ }
+
+ // Export a plugin-specific PluginMarker. Plugins using PluginMarker should check
+ // marker.getPluginName() in isType() to prevent conflicts with markers for other plugins.
+ session.newExport(resultTicket, "RemoteFileSourcePluginFetchRequest.resultTicket")
+ .submit(() -> PluginMarker.forPluginName(pluginName));
+
+ final Flight.FlightInfo flightInfo = Flight.FlightInfo.newBuilder()
+ .setFlightDescriptor(descriptor)
+ .addEndpoint(Flight.FlightEndpoint.newBuilder()
+ .setTicket(Flight.Ticket.newBuilder()
+ .setTicket(
+ resultTicket.getTicket()))
+ .build())
+ .setTotalRecords(-1)
+ .setTotalBytes(-1)
+ .build();
+
+ return SessionState.wrapAsExport(flightInfo);
+ }
+
+ /**
+ * Resolves a flight descriptor to flight info for remote file source commands. Handles
+ * RemoteFileSourcePluginFetchRequest commands by parsing the descriptor and delegating to the appropriate handler
+ * method.
+ *
+ * @param session the session state for the current request
+ * @param descriptor the flight descriptor containing the command
+ * @param logId the log identifier for tracking
+ * @return a FlightInfo export object for the requested command
+ * @throws StatusRuntimeException if session is null (UNAUTHENTICATED), the command cannot be parsed, or the command
+ * type URL is not recognized
+ */
+ @Override
+ public SessionState.ExportObject flightInfoFor(@Nullable final SessionState session,
+ final Flight.FlightDescriptor descriptor,
+ final String logId) {
+ if (session == null) {
+ throw Exceptions.statusRuntimeException(Code.UNAUTHENTICATED,
+ "Could not resolve '" + logId + "': no session available");
+ }
+
+ final Any request = parseAsAnyOrNull(descriptor.getCmd());
+ if (request == null) {
+ log.error().append("Could not parse remotefilesource command.").endl();
+ throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT,
+ "Could not parse remotefilesource command Any.");
+ }
+
+ if (!FETCH_PLUGIN_TYPE_URL.equals(request.getTypeUrl())) {
+ log.error().append("Invalid remotefilesource command typeUrl: " + request.getTypeUrl()).endl();
+ throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT, "Invalid typeUrl: " + request.getTypeUrl());
+ }
+
+ return fetchPlugin(session, descriptor, parseFetchRequest(request));
+ }
+
+ /**
+ * Visits all flight info that this resolver exposes.
+ *
+ *
+ * Not implemented: This resolver does not expose any flight info via list flights.
+ */
+ @Override
+ public void forAllFlightInfo(@Nullable final SessionState session, final Consumer visitor) {
+ // nothing to do
+ }
+
+ /**
+ * Returns a log-friendly name for the given ticket.
+ *
+ * @throws UnsupportedOperationException always, as this resolver does not support ticket-based routing
+ */
+ @Override
+ public String getLogNameFor(final ByteBuffer ticket, final String logId) {
+ // no ticket-based routing
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Determines if this resolver is responsible for handling the given command descriptor. This resolver handles
+ * commands with type URL matching RemoteFileSourcePluginFetchRequest.
+ *
+ * @param descriptor the flight descriptor containing the command
+ * @return true if this resolver handles the command, false otherwise
+ */
+ @Override
+ public boolean handlesCommand(final Flight.FlightDescriptor descriptor) {
+ Assert.eq(descriptor.getType(), "descriptor.getType()", Flight.FlightDescriptor.DescriptorType.CMD, "CMD");
+
+ final Any command = parseAsAnyOrNull(descriptor.getCmd());
+ if (command == null) {
+ return false;
+ }
+
+ return FETCH_PLUGIN_TYPE_URL.equals(command.getTypeUrl());
+ }
+
+ /**
+ * Publishes an export to the session using a ticket.
+ *
+ * @throws UnsupportedOperationException always, as this resolver does not support publishing
+ */
+ @Override
+ public SessionState.ExportBuilder publish(final SessionState session,
+ final ByteBuffer ticket,
+ final String logId,
+ @Nullable final Runnable onPublish) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Publishes an export to the session using a flight descriptor.
+ *
+ * @throws UnsupportedOperationException always, as this resolver does not support publishing
+ */
+ @Override
+ public SessionState.ExportBuilder publish(final SessionState session,
+ final Flight.FlightDescriptor descriptor, final String logId,
+ @Nullable final Runnable onPublish) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Resolves a flight descriptor to an export object.
+ *
+ * @throws UnsupportedOperationException always, use flightInfoFor() instead
+ */
+ @Override
+ public SessionState.ExportObject resolve(@Nullable final SessionState session,
+ final Flight.FlightDescriptor descriptor,
+ final String logId) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Resolves a ticket to an export object.
+ *
+ * @throws UnsupportedOperationException always, as this resolver does not support ticket-based routing
+ */
+ @Override
+ public SessionState.ExportObject resolve(@Nullable final SessionState session,
+ final ByteBuffer ticket,
+ final String logId) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Sets the ticket router for this resolver.
+ *
+ *
+ * Not implemented: This resolver does not need access to the ticket router.
+ */
+ @Override
+ public void setTicketRouter(TicketRouter ticketRouter) {
+ // not needed
+ }
+
+ /**
+ * Returns the ticket route byte for this resolver. This resolver does not use ticket-based routing, so returns 0.
+ *
+ * @return 0, indicating no ticket routing
+ */
+ @Override
+ public byte ticketRoute() {
+ return 0;
+ }
+}
diff --git a/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceMessageStream.java b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceMessageStream.java
new file mode 100644
index 00000000000..a61b89834d2
--- /dev/null
+++ b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceMessageStream.java
@@ -0,0 +1,404 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.remotefilesource;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import io.deephaven.engine.util.RemoteFileSourceClassLoader;
+import io.deephaven.engine.util.RemoteFileSourceProvider;
+import io.deephaven.internal.log.LoggerFactory;
+import io.deephaven.io.logger.Logger;
+import io.deephaven.plugin.type.ObjectCommunicationException;
+import io.deephaven.plugin.type.ObjectType;
+import io.deephaven.proto.backplane.grpc.RemoteFileSourceClientMessage;
+import io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaRequest;
+import io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaResponse;
+import io.deephaven.proto.backplane.grpc.RemoteFileSourceServerMessage;
+import io.deephaven.proto.backplane.grpc.SetExecutionContextRequest;
+import io.deephaven.proto.backplane.grpc.SetExecutionContextResponse;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Message stream implementation for RemoteFileSource bidirectional communication. Each instance represents a file
+ * source provider for one client connection and implements RemoteFileSourceProvider so it can be registered with the
+ * RemoteFileSourceClassLoader. Only one MessageStream can be "active" at a time (determined by the execution context).
+ * The RemoteFileSourceClassLoader checks isActive() on each registered provider to find the active one.
+ */
+public class RemoteFileSourceMessageStream implements ObjectType.MessageStream, RemoteFileSourceProvider {
+ private static final Logger log = LoggerFactory.getLogger(RemoteFileSourceMessageStream.class);
+
+ /**
+ * The current execution context containing the active message stream and configuration. Null when no execution
+ * context is active. Used by this class's isActive() and canSourceResource() methods to determine if this provider
+ * should handle resource requests from RemoteFileSourceClassLoader.
+ */
+ private static volatile RemoteFileSourceExecutionContext executionContext;
+
+
+ private final ObjectType.MessageStream connection;
+ private final Map> pendingRequests = new ConcurrentHashMap<>();
+
+ /**
+ * Creates a new RemoteFileSourceMessageStream for the given connection. Automatically registers this instance as a
+ * provider with the RemoteFileSourceClassLoader.
+ *
+ * @param connection the message stream connection to the client
+ * @throws ObjectCommunicationException if the initial message cannot be sent to the client
+ */
+ public RemoteFileSourceMessageStream(final ObjectType.MessageStream connection)
+ throws ObjectCommunicationException {
+ this.connection = connection;
+ // Send initial empty message to client as required by the ObjectType contract
+ connection.onData(ByteBuffer.allocate(0));
+ // Register this instance as a provider with the RemoteFileSourceClassLoader
+ registerWithClassLoader();
+ }
+
+ /**
+ * Determines if this provider can source the specified resource. Only returns true if this message stream is
+ * active, the resource is a .groovy file, and the resource path matches one of the configured resource paths.
+ *
+ * @param resourceName the name of the resource to check
+ * @return true if this provider can source the resource, false otherwise
+ */
+ @Override
+ public boolean canSourceResource(String resourceName) {
+ if (!isActive()) {
+ return false;
+ }
+
+ // Only handle .groovy source files, not compiled .class files
+ if (!resourceName.endsWith(".groovy")) {
+ return false;
+ }
+
+ for (String contextResourcePath : executionContext.getResourcePaths()) {
+ if (resourceName.equals(contextResourcePath)) {
+ log.debug().append("Can source: ").append(resourceName).endl();
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Requests a resource from the remote client. Sends a request to the client and returns a future that will be
+ * completed when the client responds. Only services requests if this message stream is active.
+ *
+ * @param resourceName the name of the resource to request
+ * @return a CompletableFuture that will contain the resource bytes when available, or null if inactive
+ */
+ @Override
+ public CompletableFuture requestResource(String resourceName) {
+ // Only service requests if this instance is active
+ if (!isActive()) {
+ log.warn().append("Request for resource ").append(resourceName)
+ .append(" on inactive message stream").endl();
+ return CompletableFuture.failedFuture(new IllegalStateException("Inactive message stream"));
+ }
+
+ log.info().append("Requesting resource: ").append(resourceName).endl();
+
+ String requestId = UUID.randomUUID().toString();
+ CompletableFuture future = new CompletableFuture<>();
+ pendingRequests.put(requestId, future);
+
+ try {
+ // Build RemoteFileSourceMetaRequest proto
+ RemoteFileSourceMetaRequest metaRequest =
+ RemoteFileSourceMetaRequest.newBuilder()
+ .setResourceName(resourceName)
+ .build();
+
+ // Wrap in RemoteFileSourceServerMessage (server→client)
+ RemoteFileSourceServerMessage message =
+ RemoteFileSourceServerMessage.newBuilder()
+ .setRequestId(requestId)
+ .setMetaRequest(metaRequest)
+ .build();
+
+ ByteBuffer buffer = ByteBuffer.wrap(message.toByteArray());
+
+ log.info().append("Sending resource request for: ").append(resourceName)
+ .append(" with requestId: ").append(requestId).endl();
+
+ connection.onData(buffer);
+ } catch (ObjectCommunicationException e) {
+ future.completeExceptionally(e);
+ pendingRequests.remove(requestId);
+ }
+
+ return future;
+ }
+
+ /**
+ * Checks if this message stream is currently active. A message stream is active when the execution context is set
+ * and this instance is the active stream.
+ *
+ * @return true if this message stream is active, false otherwise
+ */
+ @Override
+ public boolean isActive() {
+ RemoteFileSourceExecutionContext context = executionContext;
+ return context != null && context.getActiveMessageStream() == this;
+ }
+
+ /**
+ * Checks if this provider has any resource paths configured.
+ *
+ * @return true if this provider is active and has non-empty resource paths, false otherwise
+ */
+ @Override
+ public boolean hasConfiguredResources() {
+ return isActive() && !executionContext.getResourcePaths().isEmpty();
+ }
+
+ /**
+ * Checks if this provider's execution context is dirty.
+ *
+ * @return true if this provider is active and the execution context is dirty, false otherwise
+ */
+ @Override
+ public boolean isDirty() {
+ return isActive() && executionContext.isDirty();
+ }
+
+ /**
+ * Sets the execution context with the active message stream and resource paths.
+ *
+ *
+ * This static method establishes which message stream instance should be considered "active" for resource requests,
+ * and which resource paths should be resolved from that remote source. Only one execution context can be active at
+ * a time across all instances.
+ *
+ *
+ * In multi-client scenarios (Community Core), this ensures that only the message stream for the currently executing
+ * script is active, preventing resource requests from being serviced by the wrong client connection.
+ *
+ *
+ * Typical Usage: Called at the beginning of script execution to establish which .groovy files should be
+ * sourced from the remote client rather than the local classpath.
+ *
+ * @param messageStream the message stream to set as active (must not be null)
+ * @param resourcePaths list of resource paths (e.g., "package/MyScript.groovy") to resolve from remote source
+ * @param isDirty whether remote sources have changed and cache should be cleared
+ * @throws IllegalArgumentException if messageStream is null
+ */
+ public static void setExecutionContext(RemoteFileSourceMessageStream messageStream, List resourcePaths,
+ boolean isDirty) {
+ if (messageStream == null) {
+ throw new IllegalArgumentException("messageStream must not be null");
+ }
+
+ executionContext = new RemoteFileSourceExecutionContext(messageStream, resourcePaths, isDirty);
+ log.info().append("Set execution context with ")
+ .append(executionContext.getResourcePaths().size()).append(" resource paths")
+ .append(", isDirty: ").append(isDirty).endl();
+ }
+
+ /**
+ * Clears the execution context.
+ */
+ public static void clearExecutionContext() {
+ if (executionContext != null) {
+ executionContext = null;
+ log.info().append("Cleared execution context").endl();
+ }
+ }
+
+
+ /**
+ * Handles incoming data from the client. Parses RemoteFileSourceClientMessage messages and processes meta responses
+ * or execution context updates from the client.
+ *
+ * @param payload the message payload containing the protobuf data
+ * @param references optional references (not used)
+ * @throws ObjectCommunicationException if the message cannot be parsed
+ */
+ @Override
+ public void onData(ByteBuffer payload, Object... references) throws ObjectCommunicationException {
+ try {
+ byte[] bytes = new byte[payload.remaining()];
+ payload.get(bytes);
+ RemoteFileSourceClientMessage message = RemoteFileSourceClientMessage.parseFrom(bytes);
+
+ if (message.hasMetaResponse()) {
+ handleMetaResponse(message.getRequestId(), message.getMetaResponse());
+ } else if (message.hasSetExecutionContext()) {
+ handleSetExecutionContext(message.getRequestId(), message.getSetExecutionContext());
+ } else {
+ log.error().append("Received unknown message type from client").endl();
+ throw new ObjectCommunicationException("Received unknown message type from client");
+ }
+ } catch (InvalidProtocolBufferException e) {
+ log.error().append("Failed to parse RemoteFileSourceClientMessage: ").append(e).endl();
+ throw new ObjectCommunicationException("Failed to parse message", e);
+ }
+ }
+
+ /**
+ * Handles a meta response from the client containing requested resource content.
+ *
+ * @param requestId the request ID
+ * @param response the meta response from the client
+ */
+ private void handleMetaResponse(String requestId, RemoteFileSourceMetaResponse response) {
+ CompletableFuture future = pendingRequests.remove(requestId);
+ if (future == null) {
+ log.warn().append("Received response for unknown requestId: ").append(requestId).endl();
+ return;
+ }
+
+ byte[] content = response.getContent().toByteArray();
+
+ log.info().append("Received resource response for requestId: ").append(requestId)
+ .append(", found: ").append(response.getFound())
+ .append(", content length: ").append(content.length).endl();
+
+ if (!response.getError().isEmpty()) {
+ log.warn().append("Error in response: ").append(response.getError()).endl();
+ }
+
+ future.complete(content);
+ }
+
+ /**
+ * Handles a request from the client to set the execution context.
+ *
+ * @param requestId the request ID
+ * @param setExecutionContext the SetExecutionContextRequest containing resource paths and isDirty flag
+ */
+ private void handleSetExecutionContext(String requestId, SetExecutionContextRequest setExecutionContext) {
+ boolean isDirty = setExecutionContext.getIsDirty();
+ List resourcePaths = setExecutionContext.getResourcePathsList();
+
+ setExecutionContext(this, resourcePaths, isDirty);
+ log.info().append("Client set execution context for this message stream with ")
+ .append(resourcePaths.size()).append(" resource paths")
+ .append(", isDirty: ").append(isDirty).endl();
+
+ sendExecutionContextAcknowledgment(requestId);
+ }
+
+ /**
+ * Sends an acknowledgment to the client that the execution context was successfully set.
+ *
+ * @param requestId the request ID to acknowledge
+ */
+ private void sendExecutionContextAcknowledgment(String requestId) {
+ SetExecutionContextResponse response = SetExecutionContextResponse.newBuilder()
+ .setSuccess(true)
+ .build();
+
+ RemoteFileSourceServerMessage serverRequest = RemoteFileSourceServerMessage.newBuilder()
+ .setRequestId(requestId)
+ .setSetExecutionContextResponse(response)
+ .build();
+
+ try {
+ connection.onData(ByteBuffer.wrap(serverRequest.toByteArray()));
+ } catch (ObjectCommunicationException e) {
+ log.error().append("Failed to send execution context acknowledgment: ").append(e).endl();
+ }
+ }
+
+ /**
+ * Handles cleanup when the message stream is closed. Unregisters this provider from the
+ * RemoteFileSourceClassLoader, clears the execution context if this was active, and cancels all pending resource
+ * requests.
+ */
+ @Override
+ public void onClose() {
+ // Unregister this provider from the RemoteFileSourceClassLoader
+ unregisterFromClassLoader();
+
+ // Clear execution context if this was the active stream
+ if (isActive()) {
+ clearExecutionContext();
+ }
+
+ // Cancel all pending requests
+ pendingRequests.values().forEach(future -> future.cancel(true));
+ pendingRequests.clear();
+ }
+
+ /**
+ * Register this message stream instance as a provider with the RemoteFileSourceClassLoader.
+ */
+ private void registerWithClassLoader() {
+ RemoteFileSourceClassLoader classLoader = RemoteFileSourceClassLoader.getInstance();
+ classLoader.registerProvider(this);
+ log.info().append("Registered RemoteFileSourceMessageStream provider with RemoteFileSourceClassLoader").endl();
+ }
+
+ /**
+ * Unregister this message stream instance from the RemoteFileSourceClassLoader.
+ */
+ private void unregisterFromClassLoader() {
+ RemoteFileSourceClassLoader classLoader = RemoteFileSourceClassLoader.getInstance();
+ classLoader.unregisterProvider(this);
+ log.info().append("Unregistered RemoteFileSourceMessageStream provider from RemoteFileSourceClassLoader")
+ .endl();
+ }
+
+
+ /**
+ * Encapsulates the execution context for remote file source operations. This includes the currently active message
+ * stream and the resource paths that should be resolved from the remote source. This class is immutable - a new
+ * instance is created each time the context changes.
+ */
+ public static class RemoteFileSourceExecutionContext {
+ private final RemoteFileSourceMessageStream activeMessageStream;
+ private final List resourcePaths;
+ private final boolean isDirty;
+
+ /**
+ * Creates a new execution context.
+ *
+ * @param activeMessageStream the active message stream
+ * @param resourcePaths list of resource paths to resolve from remote source
+ * @param isDirty whether remote sources have changed and cache should be cleared
+ */
+ public RemoteFileSourceExecutionContext(RemoteFileSourceMessageStream activeMessageStream,
+ List resourcePaths, boolean isDirty) {
+ this.activeMessageStream = activeMessageStream;
+ this.resourcePaths = resourcePaths;
+ this.isDirty = isDirty;
+ }
+
+ /**
+ * Gets the currently active message stream.
+ *
+ * @return the active message stream
+ */
+ public RemoteFileSourceMessageStream getActiveMessageStream() {
+ return activeMessageStream;
+ }
+
+ /**
+ * Gets the resource paths that should be resolved from the remote source.
+ *
+ * @return the list of resource paths
+ */
+ public List getResourcePaths() {
+ return resourcePaths;
+ }
+
+ /**
+ * Gets whether remote sources have changed and cache should be cleared.
+ *
+ * @return true if dirty, false otherwise
+ */
+ public boolean isDirty() {
+ return isDirty;
+ }
+ }
+}
+
diff --git a/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourcePlugin.java b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourcePlugin.java
new file mode 100644
index 00000000000..a106d0c1daf
--- /dev/null
+++ b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourcePlugin.java
@@ -0,0 +1,54 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.remotefilesource;
+
+import com.google.auto.service.AutoService;
+import io.deephaven.plugin.type.ObjectType;
+import io.deephaven.plugin.type.ObjectTypeBase;
+import io.deephaven.plugin.type.ObjectCommunicationException;
+import io.deephaven.plugin.type.PluginMarker;
+
+import java.nio.ByteBuffer;
+
+/**
+ * ObjectType plugin for remote file sources. This plugin is registered via @AutoService and handles creation of
+ * RemoteFileSourceMessageStream connections for bidirectional communication with clients.
+ *
+ * This plugin recognizes PluginMarker objects whose pluginName matches this plugin's name. When a connection is
+ * established, a RemoteFileSourceMessageStream is created to handle bidirectional message passing between client and
+ * server.
+ *
+ * Each RemoteFileSourceMessageStream instance registers itself as a provider with the RemoteFileSourceClassLoader when
+ * created and unregisters when closed. The RemoteFileSourceClassLoader uses isActive() to determine which registered
+ * provider should handle resource requests.
+ */
+@AutoService(ObjectType.class)
+public class RemoteFileSourcePlugin extends ObjectTypeBase {
+
+ @Override
+ public String name() {
+ return "DeephavenRemoteFileSourcePlugin";
+ }
+
+ @Override
+ public boolean isType(Object object) {
+ if (object instanceof PluginMarker) {
+ PluginMarker marker = (PluginMarker) object;
+ return name().equals(marker.getPluginName());
+ }
+ return false;
+ }
+
+ @Override
+ public MessageStream compatibleClientConnection(Object object, MessageStream connection)
+ throws ObjectCommunicationException {
+ if (!isType(object)) {
+ throw new ObjectCommunicationException("Expected RemoteFileSource marker object, got " + object.getClass());
+ }
+
+ // Return a new bidirectional message stream for this connection
+ return new RemoteFileSourceMessageStream(connection);
+ }
+}
+
diff --git a/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceTicketResolverFactoryService.java b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceTicketResolverFactoryService.java
new file mode 100644
index 00000000000..adbc1ab34ae
--- /dev/null
+++ b/plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceTicketResolverFactoryService.java
@@ -0,0 +1,21 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.remotefilesource;
+
+import com.google.auto.service.AutoService;
+import io.deephaven.server.runner.TicketResolversFromServiceLoader;
+import io.deephaven.server.session.TicketResolver;
+
+/**
+ * Factory service for creating RemoteFileSourceCommandResolver instances. This service is registered via @AutoService
+ * and provides ticket resolver functionality for handling remote file source plugin commands through the Deephaven
+ * server infrastructure.
+ */
+@AutoService(TicketResolversFromServiceLoader.Factory.class)
+public class RemoteFileSourceTicketResolverFactoryService implements TicketResolversFromServiceLoader.Factory {
+ @Override
+ public TicketResolver create(final TicketResolversFromServiceLoader.TicketResolverOptions options) {
+ return new RemoteFileSourceCommandResolver();
+ }
+}
diff --git a/plugin/src/main/java/io/deephaven/plugin/type/PluginMarker.java b/plugin/src/main/java/io/deephaven/plugin/type/PluginMarker.java
new file mode 100644
index 00000000000..02ebd4f3589
--- /dev/null
+++ b/plugin/src/main/java/io/deephaven/plugin/type/PluginMarker.java
@@ -0,0 +1,61 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.plugin.type;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A generic marker object for plugin exports that can be used by multiple plugin types.
+ *
+ * IMPORTANT: The pluginName field is required because ObjectTypeLookup.findObjectType() returns the FIRST plugin where
+ * isType() returns true. Without plugin-specific identification in isType(), multiple plugins using PluginMarker would
+ * conflict, and whichever is registered first would intercept all PluginMarker instances.
+ *
+ * This class maintains a single instance per pluginName - multiple calls to {@link #forPluginName(String)} with the
+ * same name will return the same instance.
+ */
+public class PluginMarker {
+ private static final Map INSTANCES = new ConcurrentHashMap<>();
+
+ private final String pluginName;
+
+ /**
+ * Private constructor - use forPluginName() to get instances.
+ *
+ * @param pluginName the plugin name identifier (should match the plugin's name() method)
+ */
+ private PluginMarker(String pluginName) {
+ this.pluginName = pluginName;
+ }
+
+ /**
+ * Gets the PluginMarker instance for the specified plugin name, creating it if necessary.
+ *
+ * @param pluginName the plugin name identifier (should match the plugin's name() method)
+ * @return the PluginMarker instance for this plugin name
+ * @throws IllegalArgumentException if pluginName is null or empty
+ */
+ public static PluginMarker forPluginName(String pluginName) {
+ if (pluginName == null || pluginName.isEmpty()) {
+ throw new IllegalArgumentException("pluginName cannot be null or empty");
+ }
+ return INSTANCES.computeIfAbsent(pluginName, PluginMarker::new);
+ }
+
+ /**
+ * Gets the plugin name this marker is intended for. This should match the ObjectType.name() of the target plugin.
+ *
+ * @return the plugin name identifier
+ */
+ public String getPluginName() {
+ return pluginName;
+ }
+
+ @Override
+ public String toString() {
+ return "PluginMarker{pluginName='" + pluginName + "'}";
+ }
+}
+
diff --git a/proto/proto-backplane-grpc/Dockerfile b/proto/proto-backplane-grpc/Dockerfile
index a95d0465655..0b142c717a0 100644
--- a/proto/proto-backplane-grpc/Dockerfile
+++ b/proto/proto-backplane-grpc/Dockerfile
@@ -29,6 +29,7 @@ RUN set -eux; \
/includes/deephaven_core/proto/application.proto \
/includes/deephaven_core/proto/inputtable.proto \
/includes/deephaven_core/proto/partitionedtable.proto \
+ /includes/deephaven_core/proto/remotefilesource.proto \
/includes/deephaven_core/proto/config.proto \
/includes/deephaven_core/proto/hierarchicaltable.proto \
/includes/deephaven_core/proto/storage.proto; \
@@ -48,6 +49,7 @@ RUN set -eux; \
/includes/deephaven_core/proto/application.proto \
/includes/deephaven_core/proto/inputtable.proto \
/includes/deephaven_core/proto/partitionedtable.proto \
+ /includes/deephaven_core/proto/remotefilesource.proto \
/includes/deephaven_core/proto/config.proto \
/includes/deephaven_core/proto/hierarchicaltable.proto \
/includes/deephaven_core/proto/storage.proto; \
@@ -64,6 +66,7 @@ RUN set -eux; \
/includes/deephaven_core/proto/application.proto \
/includes/deephaven_core/proto/inputtable.proto \
/includes/deephaven_core/proto/partitionedtable.proto \
+ /includes/deephaven_core/proto/remotefilesource.proto \
/includes/deephaven_core/proto/config.proto \
/includes/deephaven_core/proto/hierarchicaltable.proto \
/includes/deephaven_core/proto/storage.proto; \
@@ -83,6 +86,7 @@ RUN set -eux; \
/includes/deephaven_core/proto/application.proto \
/includes/deephaven_core/proto/inputtable.proto \
/includes/deephaven_core/proto/partitionedtable.proto \
+ /includes/deephaven_core/proto/remotefilesource.proto \
/includes/deephaven_core/proto/config.proto \
/includes/deephaven_core/proto/hierarchicaltable.proto \
/includes/deephaven_core/proto/storage.proto; \
@@ -151,6 +155,12 @@ RUN set -eux; \
--doc_opt=html,partitionedtable.html \
-I/includes \
/includes/deephaven_core/proto/partitionedtable.proto; \
+ /opt/protoc/bin/protoc \
+ --plugin=protoc-gen-doc=/usr/local/bin/protoc-gen-doc \
+ --doc_out=generated/proto-doc/multi-html \
+ --doc_opt=html,remotefilesource.html \
+ -I/includes \
+ /includes/deephaven_core/proto/remotefilesource.proto; \
/opt/protoc/bin/protoc \
--plugin=protoc-gen-doc=/usr/local/bin/protoc-gen-doc \
--doc_out=generated/proto-doc/multi-html \
@@ -218,6 +228,12 @@ RUN set -eux; \
--doc_opt=markdown,partitionedtable.md \
-I/includes \
/includes/deephaven_core/proto/partitionedtable.proto; \
+ /opt/protoc/bin/protoc \
+ --plugin=protoc-gen-doc=/usr/local/bin/protoc-gen-doc \
+ --doc_out=generated/proto-doc/multi-md \
+ --doc_opt=markdown,remotefilesource.md \
+ -I/includes \
+ /includes/deephaven_core/proto/remotefilesource.proto; \
/opt/protoc/bin/protoc \
--plugin=protoc-gen-doc=/usr/local/bin/protoc-gen-doc \
--doc_out=generated/proto-doc/multi-md \
diff --git a/proto/proto-backplane-grpc/src/main/proto/deephaven_core/proto/remotefilesource.proto b/proto/proto-backplane-grpc/src/main/proto/deephaven_core/proto/remotefilesource.proto
new file mode 100644
index 00000000000..7560ba56648
--- /dev/null
+++ b/proto/proto-backplane-grpc/src/main/proto/deephaven_core/proto/remotefilesource.proto
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2016-2025 Deephaven Data Labs and Patent Pending
+ */
+syntax = "proto3";
+
+package io.deephaven.proto.backplane.grpc;
+
+option java_multiple_files = true;
+option optimize_for = SPEED;
+option go_package = "github.com/deephaven/deephaven-core/go/internal/proto/remotefilesource";
+
+import "deephaven_core/proto/ticket.proto";
+
+// Server → Client: Requests sent from server to client via MessageStream
+message RemoteFileSourceServerMessage {
+ // Unique identifier for this request, used to correlate the response
+ string request_id = 1;
+
+ oneof request {
+ // Request source data/resource from the client
+ RemoteFileSourceMetaRequest meta_request = 2;
+
+ // Acknowledgment that execution context was set
+ SetExecutionContextResponse set_execution_context_response = 3;
+ }
+}
+
+// Client → Server: Requests/responses sent from client to server via MessageStream
+message RemoteFileSourceClientMessage {
+ // The request_id from the ServerRequest this is responding to (if applicable)
+ string request_id = 1;
+
+ oneof request {
+ // Response to a resource request
+ RemoteFileSourceMetaResponse meta_response = 2;
+
+ // Set the execution context for script execution
+ SetExecutionContextRequest set_execution_context = 3;
+ }
+}
+
+// Request source data/resource from the client
+message RemoteFileSourceMetaRequest {
+ // The name/path of the resource being requested (e.g., "com/example/MyClass.java")
+ string resource_name = 1;
+}
+
+// Response to a resource request
+message RemoteFileSourceMetaResponse {
+ // The content of the resource, or empty if not found
+ bytes content = 1;
+
+ // Indicates whether the resource was found
+ bool found = 2;
+
+ // Error message if the resource could not be retrieved
+ string error = 3;
+}
+
+// Request to set the execution context for script execution
+message SetExecutionContextRequest {
+ // Resource paths that should be resolved from the remote source
+ // (e.g., ["com/example/Test.groovy", "org/mycompany/Utils.groovy"])
+ repeated string resource_paths = 1;
+
+ // Indicates whether remote sources have changed and cache should be cleared
+ bool is_dirty = 2;
+}
+
+// Response acknowledging execution context was set
+message SetExecutionContextResponse {
+ // Whether the operation was successful
+ bool success = 1;
+}
+
+// Fetch the remote file source plugin into the specified ticket (Flight command, not MessageStream)
+message RemoteFileSourcePluginFetchRequest {
+ io.deephaven.proto.backplane.grpc.Ticket result_id = 1;
+
+ // The plugin name to create the PluginMarker for.
+ string plugin_name = 2;
+}
\ No newline at end of file
diff --git a/proto/raw-js-openapi/src/index.js b/proto/raw-js-openapi/src/index.js
index 6d7b2e69ff5..9176663e2b6 100644
--- a/proto/raw-js-openapi/src/index.js
+++ b/proto/raw-js-openapi/src/index.js
@@ -6,6 +6,7 @@ var application_pb = require("deephaven_core/proto/application_pb");
var inputtable_pb = require("deephaven_core/proto/inputtable_pb");
var object_pb = require("deephaven_core/proto/object_pb");
var partitionedtable_pb = require("deephaven_core/proto/partitionedtable_pb");
+var remotefilesource_pb = require("deephaven_core/proto/remotefilesource_pb");
var storage_pb = require("deephaven_core/proto/storage_pb");
var config_pb = require("deephaven_core/proto/config_pb");
var hierarchicaltable_pb = require("deephaven_core/proto/hierarchicaltable_pb");
@@ -46,6 +47,7 @@ var io = { deephaven_core: {
object_pb_service,
partitionedtable_pb,
partitionedtable_pb_service,
+ remotefilesource_pb,
storage_pb,
storage_pb_service,
config_pb,
diff --git a/proto/raw-js-openapi/src/shim/remotefilesource_pb.js b/proto/raw-js-openapi/src/shim/remotefilesource_pb.js
new file mode 100644
index 00000000000..01f90ee66e7
--- /dev/null
+++ b/proto/raw-js-openapi/src/shim/remotefilesource_pb.js
@@ -0,0 +1,2 @@
+Object.assign(exports, require('real/remotefilesource_pb').io.deephaven.proto.backplane.grpc)
+
diff --git a/proto/raw-js-openapi/webpack.config.js b/proto/raw-js-openapi/webpack.config.js
index f0e47f9cbee..8d64a5ac4bd 100644
--- a/proto/raw-js-openapi/webpack.config.js
+++ b/proto/raw-js-openapi/webpack.config.js
@@ -3,7 +3,7 @@ const path = require('path');
// Workaround for broken codegen from protoc-gen-js using import_style=commonjs_strict, both in
// the grpc-web protoc-gen-ts plugin, and in protoc-gen-js itself:
const aliases = {};
-for (const proto of ['application', 'config', 'console', 'hierarchicaltable', 'inputtable', 'object', 'partitionedtable', 'session', 'storage', 'table', 'ticket']) {
+for (const proto of ['application', 'config', 'console', 'hierarchicaltable', 'inputtable', 'object', 'partitionedtable', 'remotefilesource', 'session', 'storage', 'table', 'ticket']) {
// Allows a reference to the real proto files, to be made from the shim
aliases[`real/${proto}_pb`] = `${__dirname}/build/js-src/deephaven_core/proto/${proto}_pb`;
diff --git a/py/client/deephaven_core/proto/remotefilesource_pb2.py b/py/client/deephaven_core/proto/remotefilesource_pb2.py
new file mode 100644
index 00000000000..3aa205c62e3
--- /dev/null
+++ b/py/client/deephaven_core/proto/remotefilesource_pb2.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: deephaven_core/proto/remotefilesource.proto
+# Protobuf Python Version: 6.31.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+ _runtime_version.Domain.PUBLIC,
+ 6,
+ 31,
+ 1,
+ '',
+ 'deephaven_core/proto/remotefilesource.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from deephaven_core.proto import ticket_pb2 as deephaven__core_dot_proto_dot_ticket__pb2
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n+deephaven_core/proto/remotefilesource.proto\x12!io.deephaven.proto.backplane.grpc\x1a!deephaven_core/proto/ticket.proto\"\x80\x02\n\x1dRemoteFileSourceServerMessage\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12V\n\x0cmeta_request\x18\x02 \x01(\x0b\x32>.io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaRequestH\x00\x12h\n\x1eset_execution_context_response\x18\x03 \x01(\x0b\x32>.io.deephaven.proto.backplane.grpc.SetExecutionContextResponseH\x00\x42\t\n\x07request\"\xf8\x01\n\x1dRemoteFileSourceClientMessage\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12X\n\rmeta_response\x18\x02 \x01(\x0b\x32?.io.deephaven.proto.backplane.grpc.RemoteFileSourceMetaResponseH\x00\x12^\n\x15set_execution_context\x18\x03 \x01(\x0b\x32=.io.deephaven.proto.backplane.grpc.SetExecutionContextRequestH\x00\x42\t\n\x07request\"4\n\x1bRemoteFileSourceMetaRequest\x12\x15\n\rresource_name\x18\x01 \x01(\t\"M\n\x1cRemoteFileSourceMetaResponse\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\x0c\x12\r\n\x05\x66ound\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"F\n\x1aSetExecutionContextRequest\x12\x16\n\x0eresource_paths\x18\x01 \x03(\t\x12\x10\n\x08is_dirty\x18\x02 \x01(\x08\".\n\x1bSetExecutionContextResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"w\n\"RemoteFileSourcePluginFetchRequest\x12<\n\tresult_id\x18\x01 \x01(\x0b\x32).io.deephaven.proto.backplane.grpc.Ticket\x12\x13\n\x0bplugin_name\x18\x02 \x01(\tBLH\x01P\x01ZFgithub.com/deephaven/deephaven-core/go/internal/proto/remotefilesourceb\x06proto3')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'deephaven_core.proto.remotefilesource_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+ _globals['DESCRIPTOR']._loaded_options = None
+ _globals['DESCRIPTOR']._serialized_options = b'H\001P\001ZFgithub.com/deephaven/deephaven-core/go/internal/proto/remotefilesource'
+ _globals['_REMOTEFILESOURCESERVERMESSAGE']._serialized_start=118
+ _globals['_REMOTEFILESOURCESERVERMESSAGE']._serialized_end=374
+ _globals['_REMOTEFILESOURCECLIENTMESSAGE']._serialized_start=377
+ _globals['_REMOTEFILESOURCECLIENTMESSAGE']._serialized_end=625
+ _globals['_REMOTEFILESOURCEMETAREQUEST']._serialized_start=627
+ _globals['_REMOTEFILESOURCEMETAREQUEST']._serialized_end=679
+ _globals['_REMOTEFILESOURCEMETARESPONSE']._serialized_start=681
+ _globals['_REMOTEFILESOURCEMETARESPONSE']._serialized_end=758
+ _globals['_SETEXECUTIONCONTEXTREQUEST']._serialized_start=760
+ _globals['_SETEXECUTIONCONTEXTREQUEST']._serialized_end=830
+ _globals['_SETEXECUTIONCONTEXTRESPONSE']._serialized_start=832
+ _globals['_SETEXECUTIONCONTEXTRESPONSE']._serialized_end=878
+ _globals['_REMOTEFILESOURCEPLUGINFETCHREQUEST']._serialized_start=880
+ _globals['_REMOTEFILESOURCEPLUGINFETCHREQUEST']._serialized_end=999
+# @@protoc_insertion_point(module_scope)
diff --git a/py/client/deephaven_core/proto/remotefilesource_pb2.pyi b/py/client/deephaven_core/proto/remotefilesource_pb2.pyi
new file mode 100644
index 00000000000..08689044308
--- /dev/null
+++ b/py/client/deephaven_core/proto/remotefilesource_pb2.pyi
@@ -0,0 +1,211 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+
+Copyright (c) 2016-2025 Deephaven Data Labs and Patent Pending
+"""
+
+import builtins
+import collections.abc
+import deephaven_core.proto.ticket_pb2
+import google.protobuf.descriptor
+import google.protobuf.internal.containers
+import google.protobuf.message
+import sys
+import typing
+
+if sys.version_info >= (3, 10):
+ import typing as typing_extensions
+else:
+ import typing_extensions
+
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+@typing.final
+class RemoteFileSourceServerMessage(google.protobuf.message.Message):
+ """Server → Client: Requests sent from server to client via MessageStream"""
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ REQUEST_ID_FIELD_NUMBER: builtins.int
+ META_REQUEST_FIELD_NUMBER: builtins.int
+ SET_EXECUTION_CONTEXT_RESPONSE_FIELD_NUMBER: builtins.int
+ request_id: builtins.str
+ """Unique identifier for this request, used to correlate the response"""
+ @property
+ def meta_request(self) -> Global___RemoteFileSourceMetaRequest:
+ """Request source data/resource from the client"""
+
+ @property
+ def set_execution_context_response(self) -> Global___SetExecutionContextResponse:
+ """Acknowledgment that execution context was set"""
+
+ def __init__(
+ self,
+ *,
+ request_id: builtins.str = ...,
+ meta_request: Global___RemoteFileSourceMetaRequest | None = ...,
+ set_execution_context_response: Global___SetExecutionContextResponse | None = ...,
+ ) -> None: ...
+ _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["meta_request", b"meta_request", "request", b"request", "set_execution_context_response", b"set_execution_context_response"]
+ def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ...
+ _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["meta_request", b"meta_request", "request", b"request", "request_id", b"request_id", "set_execution_context_response", b"set_execution_context_response"]
+ def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
+ _WhichOneofReturnType_request: typing_extensions.TypeAlias = typing.Literal["meta_request", "set_execution_context_response"]
+ _WhichOneofArgType_request: typing_extensions.TypeAlias = typing.Literal["request", b"request"]
+ def WhichOneof(self, oneof_group: _WhichOneofArgType_request) -> _WhichOneofReturnType_request | None: ...
+
+Global___RemoteFileSourceServerMessage: typing_extensions.TypeAlias = RemoteFileSourceServerMessage
+
+@typing.final
+class RemoteFileSourceClientMessage(google.protobuf.message.Message):
+ """Client → Server: Requests/responses sent from client to server via MessageStream"""
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ REQUEST_ID_FIELD_NUMBER: builtins.int
+ META_RESPONSE_FIELD_NUMBER: builtins.int
+ SET_EXECUTION_CONTEXT_FIELD_NUMBER: builtins.int
+ request_id: builtins.str
+ """The request_id from the ServerRequest this is responding to (if applicable)"""
+ @property
+ def meta_response(self) -> Global___RemoteFileSourceMetaResponse:
+ """Response to a resource request"""
+
+ @property
+ def set_execution_context(self) -> Global___SetExecutionContextRequest:
+ """Set the execution context for script execution"""
+
+ def __init__(
+ self,
+ *,
+ request_id: builtins.str = ...,
+ meta_response: Global___RemoteFileSourceMetaResponse | None = ...,
+ set_execution_context: Global___SetExecutionContextRequest | None = ...,
+ ) -> None: ...
+ _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["meta_response", b"meta_response", "request", b"request", "set_execution_context", b"set_execution_context"]
+ def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ...
+ _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["meta_response", b"meta_response", "request", b"request", "request_id", b"request_id", "set_execution_context", b"set_execution_context"]
+ def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
+ _WhichOneofReturnType_request: typing_extensions.TypeAlias = typing.Literal["meta_response", "set_execution_context"]
+ _WhichOneofArgType_request: typing_extensions.TypeAlias = typing.Literal["request", b"request"]
+ def WhichOneof(self, oneof_group: _WhichOneofArgType_request) -> _WhichOneofReturnType_request | None: ...
+
+Global___RemoteFileSourceClientMessage: typing_extensions.TypeAlias = RemoteFileSourceClientMessage
+
+@typing.final
+class RemoteFileSourceMetaRequest(google.protobuf.message.Message):
+ """Request source data/resource from the client"""
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ RESOURCE_NAME_FIELD_NUMBER: builtins.int
+ resource_name: builtins.str
+ """The name/path of the resource being requested (e.g., "com/example/MyClass.java")"""
+ def __init__(
+ self,
+ *,
+ resource_name: builtins.str = ...,
+ ) -> None: ...
+ _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["resource_name", b"resource_name"]
+ def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
+
+Global___RemoteFileSourceMetaRequest: typing_extensions.TypeAlias = RemoteFileSourceMetaRequest
+
+@typing.final
+class RemoteFileSourceMetaResponse(google.protobuf.message.Message):
+ """Response to a resource request"""
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ CONTENT_FIELD_NUMBER: builtins.int
+ FOUND_FIELD_NUMBER: builtins.int
+ ERROR_FIELD_NUMBER: builtins.int
+ content: builtins.bytes
+ """The content of the resource, or empty if not found"""
+ found: builtins.bool
+ """Indicates whether the resource was found"""
+ error: builtins.str
+ """Error message if the resource could not be retrieved"""
+ def __init__(
+ self,
+ *,
+ content: builtins.bytes = ...,
+ found: builtins.bool = ...,
+ error: builtins.str = ...,
+ ) -> None: ...
+ _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["content", b"content", "error", b"error", "found", b"found"]
+ def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
+
+Global___RemoteFileSourceMetaResponse: typing_extensions.TypeAlias = RemoteFileSourceMetaResponse
+
+@typing.final
+class SetExecutionContextRequest(google.protobuf.message.Message):
+ """Request to set the execution context for script execution"""
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ RESOURCE_PATHS_FIELD_NUMBER: builtins.int
+ IS_DIRTY_FIELD_NUMBER: builtins.int
+ is_dirty: builtins.bool
+ """Indicates whether remote sources have changed and cache should be cleared"""
+ @property
+ def resource_paths(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]:
+ """Resource paths that should be resolved from the remote source
+ (e.g., ["com/example/Test.groovy", "org/mycompany/Utils.groovy"])
+ """
+
+ def __init__(
+ self,
+ *,
+ resource_paths: collections.abc.Iterable[builtins.str] | None = ...,
+ is_dirty: builtins.bool = ...,
+ ) -> None: ...
+ _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["is_dirty", b"is_dirty", "resource_paths", b"resource_paths"]
+ def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
+
+Global___SetExecutionContextRequest: typing_extensions.TypeAlias = SetExecutionContextRequest
+
+@typing.final
+class SetExecutionContextResponse(google.protobuf.message.Message):
+ """Response acknowledging execution context was set"""
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ SUCCESS_FIELD_NUMBER: builtins.int
+ success: builtins.bool
+ """Whether the operation was successful"""
+ def __init__(
+ self,
+ *,
+ success: builtins.bool = ...,
+ ) -> None: ...
+ _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["success", b"success"]
+ def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
+
+Global___SetExecutionContextResponse: typing_extensions.TypeAlias = SetExecutionContextResponse
+
+@typing.final
+class RemoteFileSourcePluginFetchRequest(google.protobuf.message.Message):
+ """Fetch the remote file source plugin into the specified ticket (Flight command, not MessageStream)"""
+
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ RESULT_ID_FIELD_NUMBER: builtins.int
+ PLUGIN_NAME_FIELD_NUMBER: builtins.int
+ plugin_name: builtins.str
+ """The plugin name to create the PluginMarker for."""
+ @property
+ def result_id(self) -> deephaven_core.proto.ticket_pb2.Ticket: ...
+ def __init__(
+ self,
+ *,
+ result_id: deephaven_core.proto.ticket_pb2.Ticket | None = ...,
+ plugin_name: builtins.str = ...,
+ ) -> None: ...
+ _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["result_id", b"result_id"]
+ def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ...
+ _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["plugin_name", b"plugin_name", "result_id", b"result_id"]
+ def ClearField(self, field_name: _ClearFieldArgType) -> None: ...
+
+Global___RemoteFileSourcePluginFetchRequest: typing_extensions.TypeAlias = RemoteFileSourcePluginFetchRequest
diff --git a/py/client/deephaven_core/proto/remotefilesource_pb2_grpc.py b/py/client/deephaven_core/proto/remotefilesource_pb2_grpc.py
new file mode 100644
index 00000000000..a66295078b8
--- /dev/null
+++ b/py/client/deephaven_core/proto/remotefilesource_pb2_grpc.py
@@ -0,0 +1,24 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import grpc
+import warnings
+
+
+GRPC_GENERATED_VERSION = '1.76.0'
+GRPC_VERSION = grpc.__version__
+_version_not_supported = False
+
+try:
+ from grpc._utilities import first_version_is_lower
+ _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
+except ImportError:
+ _version_not_supported = True
+
+if _version_not_supported:
+ raise RuntimeError(
+ f'The grpc package installed is at version {GRPC_VERSION},'
+ + ' but the generated code in deephaven_core/proto/remotefilesource_pb2_grpc.py depends on'
+ + f' grpcio>={GRPC_GENERATED_VERSION}.'
+ + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
+ )
diff --git a/server/build.gradle b/server/build.gradle
index f59344a3cdf..564093a1f3b 100644
--- a/server/build.gradle
+++ b/server/build.gradle
@@ -93,6 +93,7 @@ dependencies {
runtimeOnly project(':plugin-figure')
runtimeOnly project(':plugin-partitionedtable')
runtimeOnly project(':plugin-hierarchicaltable')
+ runtimeOnly project(':plugin-remotefilesource')
implementation project(':plugin-gc-app')
api platform(libs.grpc.bom)
diff --git a/server/jetty-app-11/build.gradle b/server/jetty-app-11/build.gradle
index e075c08a8fa..09baafd32a8 100644
--- a/server/jetty-app-11/build.gradle
+++ b/server/jetty-app-11/build.gradle
@@ -16,6 +16,7 @@ dependencies {
implementation libs.dagger
annotationProcessor libs.dagger.compiler
+ runtimeOnly project(':plugin-remotefilesource')
runtimeOnly project(':log-to-slf4j')
runtimeOnly project(':logback-print-stream-globals')
runtimeOnly project(':logback-logbuffer')
diff --git a/server/jetty-app/build.gradle b/server/jetty-app/build.gradle
index 50c7406be2b..ec03184a602 100644
--- a/server/jetty-app/build.gradle
+++ b/server/jetty-app/build.gradle
@@ -16,6 +16,7 @@ dependencies {
implementation libs.dagger
annotationProcessor libs.dagger.compiler
+ runtimeOnly project(':plugin-remotefilesource')
runtimeOnly project(':log-to-slf4j')
runtimeOnly project(':logback-print-stream-globals')
runtimeOnly project(':logback-logbuffer')
diff --git a/settings.gradle b/settings.gradle
index ea57fc5cf51..c99ad5cdec4 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -318,6 +318,9 @@ project(':plugin-figure').projectDir = file('plugin/figure')
include(':plugin-partitionedtable')
project(':plugin-partitionedtable').projectDir = file('plugin/partitionedtable')
+include(':plugin-remotefilesource')
+project(':plugin-remotefilesource').projectDir = file('plugin/remotefilesource')
+
include(':plugin-hierarchicaltable')
project(':plugin-hierarchicaltable').projectDir = file('plugin/hierarchicaltable')
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/CoreClient.java b/web/client-api/src/main/java/io/deephaven/web/client/api/CoreClient.java
index 0abbdd30efe..1eb0247b1f8 100644
--- a/web/client-api/src/main/java/io/deephaven/web/client/api/CoreClient.java
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/CoreClient.java
@@ -15,6 +15,7 @@
import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.config_pb_service.ConfigServiceClient;
import io.deephaven.javascript.proto.dhinternal.jspb.Map;
import io.deephaven.web.client.api.event.HasEventHandling;
+import io.deephaven.web.client.api.remotefilesource.JsRemoteFileSourceService;
import io.deephaven.web.client.api.storage.JsStorageService;
import io.deephaven.web.client.fu.JsLog;
import io.deephaven.web.client.fu.LazyPromise;
@@ -46,6 +47,7 @@ public class CoreClient extends HasEventHandling {
LOGIN_TYPE_ANONYMOUS = "anonymous";
private final IdeConnection ideConnection;
+ private Promise remoteFileSourceServicePromise;
public CoreClient(String serverUrl, @TsTypeRef(ConnectOptions.class) @JsOptional Object connectOptions) {
ideConnection = new IdeConnection(serverUrl, connectOptions);
@@ -141,6 +143,14 @@ public Promise onConnected(@JsOptional Double timeoutInMillis) {
return ideConnection.onConnected();
}
+ public Promise getRemoteFileSourceService() {
+ if (remoteFileSourceServicePromise == null) {
+ remoteFileSourceServicePromise = JsRemoteFileSourceService.fetchPlugin(ideConnection.connection.get());
+ }
+
+ return remoteFileSourceServicePromise;
+ }
+
public Promise getServerConfigValues() {
return getConfigs(
c -> ideConnection.connection.get().configServiceClient().getConfigurationConstants(
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/JsProtobufUtils.java b/web/client-api/src/main/java/io/deephaven/web/client/api/JsProtobufUtils.java
new file mode 100644
index 00000000000..6ee6ad92c75
--- /dev/null
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/JsProtobufUtils.java
@@ -0,0 +1,183 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.web.client.api;
+
+import elemental2.core.Uint8Array;
+import elemental2.dom.TextEncoder;
+
+/**
+ * Utility methods for working with protobuf messages in the client-side JavaScript environment.
+ */
+public class JsProtobufUtils {
+
+ // Varint encoding constants
+ private static final int VARINT_CONTINUATION_BIT = 0x80; // 10000000 - indicates more bytes follow
+ private static final int VARINT_DATA_MASK = 0x7F; // 01111111 - extracts 7 bits of data
+ private static final int VARINT_BYTE_THRESHOLD = 128; // Values >= 128 require multiple bytes
+
+ private JsProtobufUtils() {
+ // Utility class, no instantiation
+ }
+
+ /**
+ * Wraps a protobuf message in a google.protobuf.Any message.
+ *
+ * The google.protobuf.Any message has two fields:
+ *
+ * - Field 1: type_url (string) - identifies the type of message contained
+ * - Field 2: value (bytes) - the actual serialized message
+ *
+ *
+ * This method manually encodes the Any message in protobuf binary format since the client-side JavaScript protobuf
+ * library doesn't provide Any.pack() like the server-side Java library does.
+ *
+ * @param typeUrl the type URL for the message (e.g., "type.googleapis.com/package.MessageName")
+ * @param messageBytes the serialized protobuf message bytes
+ * @return the serialized Any message containing the wrapped message
+ */
+ public static Uint8Array wrapInAny(String typeUrl, Uint8Array messageBytes) {
+ // Protobuf tag constants for google.protobuf.Any message fields
+ // Tag format: (field_number << 3) | wire_type
+ // wire_type=2 means length-delimited (for strings/bytes)
+ final int TYPE_URL_TAG = 10; // (1 << 3) | 2 = field 1, wire type 2
+ final int VALUE_TAG = 18; // (2 << 3) | 2 = field 2, wire type 2
+
+ // Encode the type_url string to UTF-8 bytes
+ TextEncoder textEncoder = new TextEncoder();
+ Uint8Array typeUrlBytes = textEncoder.encode(typeUrl);
+
+ // Calculate sizes for protobuf binary encoding
+ int typeUrlFieldSize = calculateFieldSize(TYPE_URL_TAG, typeUrlBytes.length);
+ int valueFieldSize = calculateFieldSize(VALUE_TAG, messageBytes.length);
+
+ // Allocate buffer for the complete Any message
+ int totalSize = typeUrlFieldSize + valueFieldSize;
+ Uint8Array result = new Uint8Array(totalSize);
+ int pos = 0;
+
+ // Write field 1 (type_url) in protobuf binary format
+ pos = writeField(result, pos, TYPE_URL_TAG, typeUrlBytes);
+
+ // Write field 2 (value) in protobuf binary format
+ writeField(result, pos, VALUE_TAG, messageBytes);
+
+ return result;
+ }
+
+ /**
+ * Calculates the total size needed for a protobuf length-delimited field.
+ *
+ * A length-delimited field consists of:
+ *
+ * - Tag (field number + wire type) encoded as a varint
+ * - Length of the data encoded as a varint
+ * - The actual data bytes
+ *
+ *
+ * @param tag the protobuf field tag (field number << 3 | wire type)
+ * @param dataLength the length of the data in bytes
+ * @return the total number of bytes needed for this field
+ */
+ private static int calculateFieldSize(int tag, int dataLength) {
+ return sizeOfVarint(tag) + sizeOfVarint(dataLength) + dataLength;
+ }
+
+ /**
+ * Calculates how many bytes a varint encoding will require for the given value.
+ *
+ * Protobuf uses varint encoding where each byte stores 7 bits of data (the 8th bit is a continuation flag). This
+ * means:
+ *
+ * - 1 byte: 0 to 127 (2^7 - 1)
+ * - 2 bytes: 128 to 16,383 (2^14 - 1)
+ * - 3 bytes: 16,384 to 2,097,151 (2^21 - 1)
+ * - 4 bytes: 2,097,152 to 268,435,455 (2^28 - 1)
+ * - 5 bytes: 268,435,456 to 4,294,967,295 (max unsigned 32-bit int)
+ * - 10 bytes: negative numbers (sign-extended to 64 bits in varint encoding)
+ *
+ *
+ * @param value the integer value to encode
+ * @return the number of bytes required to encode the value as a varint
+ */
+ private static int sizeOfVarint(int value) {
+ if (value < 0)
+ return 10; // Negative numbers use sign extension, always 10 bytes
+ if (value < VARINT_BYTE_THRESHOLD) // 2^7 = 128
+ return 1;
+ if (value < 16384) // 2^14
+ return 2;
+ if (value < 2097152) // 2^21
+ return 3;
+ if (value < 268435456) // 2^28
+ return 4;
+ return 5; // Max unsigned 32-bit int requires 5 bytes
+ }
+
+ /**
+ * Writes a complete protobuf length-delimited field to the buffer.
+ *
+ * A length-delimited field consists of:
+ *
+ * - Tag (field number + wire type) encoded as a varint
+ * - Length of the data encoded as a varint
+ * - The actual data bytes
+ *
+ *
+ * @param buffer the buffer to write to
+ * @param pos the starting position in the buffer
+ * @param tag the protobuf field tag
+ * @param data the data bytes to write
+ * @return the new position after writing the complete field
+ */
+ private static int writeField(Uint8Array buffer, int pos, int tag, Uint8Array data) {
+ // Write tag and length
+ pos = writeVarint(buffer, pos, tag);
+ pos = writeVarint(buffer, pos, data.length);
+ // Write data bytes
+ for (int i = 0; i < data.length; i++) {
+ buffer.setAt(pos++, data.getAt(i));
+ }
+ return pos;
+ }
+
+ /**
+ * Writes a value to the buffer as a protobuf varint (variable-length integer).
+ *
+ * Varint encoding works by:
+ *
+ * - Taking the lowest 7 bits of the value
+ * - Setting the 8th bit to 1 if more bytes follow (continuation flag)
+ * - Writing the byte to the buffer
+ * - Shifting the value right by 7 bits
+ * - Repeating until the value is less than 128
+ * - Writing the final byte without the continuation flag (8th bit = 0)
+ *
+ *
+ * Example: encoding 300
+ *
+ * - 300 in binary: 100101100
+ * - First byte: (300 & 0x7F) | 0x80 = 0b00101100 | 0b10000000 = 172 (0xAC)
+ * - Shift: 300 >>> 7 = 2
+ * - Second byte: 2 (no continuation flag)
+ * - Result: [172, 2]
+ *
+ *
+ * @param buffer the buffer to write to
+ * @param pos the starting position in the buffer
+ * @param value the value to encode
+ * @return the new position after writing
+ */
+ private static int writeVarint(Uint8Array buffer, int pos, int value) {
+ while (value >= VARINT_BYTE_THRESHOLD) {
+ // Extract lowest 7 bits and set continuation flag (8th bit = 1)
+ buffer.setAt(pos++, (double) ((value & VARINT_DATA_MASK) | VARINT_CONTINUATION_BIT));
+ // Shift right by 7 to process next chunk
+ value >>>= 7; // Unsigned right shift to handle large positive values
+ }
+ // Write final byte (no continuation flag, 8th bit = 0)
+ buffer.setAt(pos++, (double) value);
+ return pos;
+ }
+}
+
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/remotefilesource/JsRemoteFileSourceService.java b/web/client-api/src/main/java/io/deephaven/web/client/api/remotefilesource/JsRemoteFileSourceService.java
new file mode 100644
index 00000000000..ec51fb748ed
--- /dev/null
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/remotefilesource/JsRemoteFileSourceService.java
@@ -0,0 +1,366 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.web.client.api.remotefilesource;
+
+import com.vertispan.tsdefs.annotations.TsInterface;
+import com.vertispan.tsdefs.annotations.TsName;
+import elemental2.core.Uint8Array;
+import elemental2.dom.TextEncoder;
+import elemental2.promise.Promise;
+import io.deephaven.javascript.proto.dhinternal.arrow.flight.protocol.flight_pb.FlightDescriptor;
+import io.deephaven.javascript.proto.dhinternal.arrow.flight.protocol.flight_pb.FlightInfo;
+import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceClientMessage;
+import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceMetaRequest;
+import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceMetaResponse;
+import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourcePluginFetchRequest;
+import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceServerMessage;
+import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.SetExecutionContextRequest;
+import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.SetExecutionContextResponse;
+import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.ticket_pb.Ticket;
+import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.ticket_pb.TypedTicket;
+import io.deephaven.web.client.api.Callbacks;
+import io.deephaven.web.client.api.JsProtobufUtils;
+import io.deephaven.web.client.api.event.Event;
+import io.deephaven.web.client.api.event.EventFn;
+import io.deephaven.web.client.api.WorkerConnection;
+import io.deephaven.web.client.api.event.HasEventHandling;
+import io.deephaven.web.client.api.widget.JsWidget;
+import io.deephaven.web.shared.fu.RemoverFn;
+import io.deephaven.web.client.api.widget.WidgetMessageDetails;
+import io.deephaven.web.client.fu.LazyPromise;
+import jsinterop.annotations.JsIgnore;
+import jsinterop.annotations.JsMethod;
+import jsinterop.annotations.JsNullable;
+import jsinterop.annotations.JsOptional;
+import jsinterop.annotations.JsProperty;
+import jsinterop.annotations.JsType;
+import jsinterop.base.Js;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * JavaScript client for the RemoteFileSource service. Provides bidirectional communication with the server-side
+ * RemoteFileSourcePlugin via a message stream.
+ *
+ * Events:
+ *
+ * - {@link #EVENT_REQUEST_SOURCE}: Fired when the server requests a resource from the client. This event MUST have
+ * exactly one listener registered. Attempting to register more than one listener will throw an IllegalStateException.
+ * Receiving a resource request without a registered listener will also throw an IllegalStateException.
+ *
+ */
+@JsType(namespace = "dh.remotefilesource", name = "RemoteFileSourceService")
+public class JsRemoteFileSourceService extends HasEventHandling {
+ /** Event name for resource request events from the server */
+ @JsProperty(namespace = "dh.remotefilesource.RemoteFileSourceService")
+ public static final String EVENT_REQUEST_SOURCE = "requestsource";
+
+ // Plugin name must match RemoteFileSourcePlugin.name() on the server
+ private static final String PLUGIN_NAME = "DeephavenRemoteFileSourcePlugin";
+
+ // Timeout for setExecutionContext requests (in milliseconds)
+ private static final int SET_EXECUTION_CONTEXT_TIMEOUT_MS = 30000; // 30 seconds
+
+ private final JsWidget widget;
+
+ // Track pending setExecutionContext requests
+ private final Map> pendingSetExecutionContextRequests = new HashMap<>();
+ private int requestIdCounter = 0;
+
+ private JsRemoteFileSourceService(JsWidget widget) {
+ this.widget = widget;
+ }
+
+ /**
+ * Overrides addEventListener to enforce that EVENT_REQUEST_SOURCE can only have one listener.
+ *
+ * @param name the name of the event to listen for
+ * @param callback a function to call when the event occurs
+ * @return Returns a cleanup function.
+ * @param the type of the data that the event will provide
+ */
+ @Override
+ public RemoverFn addEventListener(String name, EventFn callback) {
+ if (EVENT_REQUEST_SOURCE.equals(name) && hasListeners(EVENT_REQUEST_SOURCE)) {
+ throw new IllegalStateException(
+ "EVENT_REQUEST_SOURCE already has a listener. Only one listener is allowed for this event.");
+ }
+ return super.addEventListener(name, callback);
+ }
+
+ /**
+ * Fetches the FlightInfo for the plugin fetch command.
+ *
+ * @param connection the worker connection to use
+ * @return a promise that resolves to the FlightInfo for the plugin fetch
+ */
+ private static Promise fetchPluginFlightInfo(WorkerConnection connection) {
+ // Create a new export ticket for the result
+ Ticket resultTicket = connection.getTickets().newExportTicket();
+
+ // Create the fetch request
+ RemoteFileSourcePluginFetchRequest fetchRequest = new RemoteFileSourcePluginFetchRequest();
+ fetchRequest.setResultId(resultTicket);
+ fetchRequest.setPluginName(PLUGIN_NAME);
+
+ // Serialize the request to bytes
+ Uint8Array innerRequestBytes = fetchRequest.serializeBinary();
+
+ // Wrap in google.protobuf.Any with the proper typeUrl
+ Uint8Array anyWrappedBytes = JsProtobufUtils.wrapInAny(
+ "type.googleapis.com/io.deephaven.proto.backplane.grpc.RemoteFileSourcePluginFetchRequest",
+ innerRequestBytes);
+
+ // Create a FlightDescriptor with the command
+ FlightDescriptor descriptor = new FlightDescriptor();
+ descriptor.setType(FlightDescriptor.DescriptorType.getCMD());
+ descriptor.setCmd(anyWrappedBytes);
+
+ // Send the getFlightInfo request
+ return Callbacks.grpcUnaryPromise(
+ c -> connection.flightServiceClient().getFlightInfo(descriptor, connection.metadata(), c::apply));
+ }
+
+ /**
+ * Fetches a RemoteFileSource plugin instance from the server and establishes a message stream connection.
+ *
+ * @param connection the worker connection to use for communication
+ * @return a promise that resolves to a RemoteFileSourceService instance with an active message stream
+ */
+ @JsIgnore
+ public static Promise fetchPlugin(WorkerConnection connection) {
+ return fetchPluginFlightInfo(connection)
+ .then(flightInfo -> {
+ // The first endpoint contains the ticket for the plugin instance.
+ // This is the standard Flight pattern: we passed resultTicket in the request,
+ // the server exported the service to that ticket, and returned a FlightInfo
+ // with an endpoint containing that same ticket for us to use.
+ if (flightInfo.getEndpointList().length > 0) {
+ // Get the Arrow Flight ticket from the endpoint
+ io.deephaven.javascript.proto.dhinternal.arrow.flight.protocol.flight_pb.Ticket flightTicket =
+ flightInfo.getEndpointList().getAt(0).getTicket();
+
+ // Convert the Arrow Flight ticket to a Deephaven ticket
+ Ticket dhTicket = new Ticket();
+ dhTicket.setTicket(flightTicket.getTicket_asU8());
+
+ // Create a TypedTicket for the plugin instance
+ // The type must match RemoteFileSourcePlugin.name()
+ TypedTicket typedTicket = new TypedTicket();
+ typedTicket.setTicket(dhTicket);
+ typedTicket.setType(PLUGIN_NAME);
+
+ JsWidget widget = new JsWidget(connection, typedTicket);
+
+ JsRemoteFileSourceService service = new JsRemoteFileSourceService(widget);
+ return service.connect();
+ } else {
+ return Promise.reject("No endpoints returned from " + PLUGIN_NAME + " plugin fetch");
+ }
+ });
+ }
+
+ /**
+ * Establishes the message stream connection to the server-side plugin instance.
+ *
+ * @return a promise that resolves to this service instance when the connection is established
+ */
+ private Promise connect() {
+ widget.addEventListener(JsWidget.EVENT_MESSAGE, this::handleMessage);
+ return widget.refetch().then(w -> Promise.resolve(this));
+ }
+
+ /**
+ * Handles incoming messages from the server.
+ *
+ * @param event the message event from the server
+ */
+ private void handleMessage(Event event) {
+ Uint8Array payload = event.getDetail().getDataAsU8();
+
+ RemoteFileSourceServerMessage message;
+ try {
+ message = RemoteFileSourceServerMessage.deserializeBinary(payload);
+ } catch (Exception e) {
+ // Failed to parse as proto
+ throw new IllegalStateException("Received unparseable message from server", e);
+ }
+
+ // Route the parsed message to the appropriate handler
+ if (message.hasMetaRequest()) {
+ handleMetaRequest(message);
+ } else if (message.hasSetExecutionContextResponse()) {
+ handleSetExecutionContextResponse(message);
+ } else {
+ throw new IllegalStateException("Received unknown message type from server");
+ }
+ }
+
+ /**
+ * Handles a meta request (resource request) from the server.
+ *
+ * @param message the server request message
+ */
+ private void handleMetaRequest(RemoteFileSourceServerMessage message) {
+ if (!hasListeners(EVENT_REQUEST_SOURCE)) {
+ throw new IllegalStateException(
+ "Received resource request from server but no listener is registered for EVENT_REQUEST_SOURCE. "
+ + "A listener must be registered to handle resource requests.");
+ }
+ RemoteFileSourceMetaRequest request = message.getMetaRequest();
+ fireEvent(EVENT_REQUEST_SOURCE, new ResourceRequestEvent(message.getRequestId(), request));
+ }
+
+ /**
+ * Handles a set execution context response from the server.
+ *
+ * @param message the server request message
+ */
+ private void handleSetExecutionContextResponse(RemoteFileSourceServerMessage message) {
+ String requestId = message.getRequestId();
+ LazyPromise promise = pendingSetExecutionContextRequests.remove(requestId);
+ if (promise != null) {
+ SetExecutionContextResponse response = message.getSetExecutionContextResponse();
+ promise.succeed(response.getSuccess());
+ }
+ }
+
+
+ /**
+ * Sets the execution context on the server to identify this message stream as active for script execution.
+ *
+ * @param isDirty whether the execution context is dirty (has pending changes)
+ * @param resourcePaths array of resource paths to resolve from remote source (e.g., ["com/example/Test.groovy",
+ * "org/mycompany/Utils.groovy"]), or null/empty for no specific resources
+ * @return a promise that resolves to true if the server successfully set the execution context, false otherwise
+ */
+ @JsMethod
+ public Promise setExecutionContext(boolean isDirty, @JsOptional String[] resourcePaths) {
+ // Generate a unique request ID
+ String requestId = "setExecutionContext-" + (requestIdCounter++);
+
+ // Create a lazy promise that will be resolved when we get the response
+ LazyPromise promise = new LazyPromise<>();
+ pendingSetExecutionContextRequests.put(requestId, promise);
+
+ // Send the request
+ RemoteFileSourceClientMessage clientRequest = getSetExecutionContextRequest(isDirty, resourcePaths, requestId);
+ sendClientRequest(clientRequest);
+
+ // Return a promise with built-in timeout
+ return promise.asPromise(SET_EXECUTION_CONTEXT_TIMEOUT_MS);
+ }
+
+ /**
+ * Helper method to build a RemoteFileSourceClientMessage for setting execution context.
+ *
+ * @param isDirty whether the execution context is dirty (has pending changes)
+ * @param resourcePaths array of resource paths to resolve
+ * @param requestId unique request ID
+ * @return the constructed RemoteFileSourceClientMessage
+ */
+ private static @NotNull RemoteFileSourceClientMessage getSetExecutionContextRequest(boolean isDirty,
+ String[] resourcePaths, String requestId) {
+ SetExecutionContextRequest setContextRequest = new SetExecutionContextRequest();
+ setContextRequest.setIsDirty(isDirty);
+
+ if (resourcePaths != null) {
+ setContextRequest.setResourcePathsList(resourcePaths);
+ }
+
+ RemoteFileSourceClientMessage clientRequest = new RemoteFileSourceClientMessage();
+ clientRequest.setRequestId(requestId);
+ clientRequest.setSetExecutionContext(setContextRequest);
+ return clientRequest;
+ }
+
+ /**
+ * Helper method to send a RemoteFileSourceClientMessage to the server.
+ *
+ * @param clientRequest the client request to send
+ */
+ private void sendClientRequest(RemoteFileSourceClientMessage clientRequest) {
+ // Serialize the protobuf message to bytes
+ Uint8Array messageBytes = clientRequest.serializeBinary();
+
+ // Uint8Array is an ArrayBufferView, which is one of the MessageUnion types
+ // The unchecked cast is safe because MessageUnion accepts String | ArrayBuffer | ArrayBufferView
+ widget.sendMessage(Js.uncheckedCast(messageBytes), null);
+ }
+
+ /**
+ * Closes the message stream connection to the server.
+ */
+ public void close() {
+ widget.close();
+ }
+
+ /**
+ * Event details for a resource request from the server. Wraps the proto RemoteFileSourceMetaRequest and provides a
+ * respond() method.
+ */
+ @TsInterface
+ @TsName(namespace = "dh.remotefilesource")
+ public class ResourceRequestEvent {
+ private final String requestId;
+ private final RemoteFileSourceMetaRequest protoRequest;
+
+ public ResourceRequestEvent(String requestId, RemoteFileSourceMetaRequest protoRequest) {
+ this.requestId = requestId;
+ this.protoRequest = protoRequest;
+ }
+
+ /**
+ * @return the name/path of the requested resource
+ */
+ @JsProperty
+ public String getResourceName() {
+ return protoRequest.getResourceName();
+ }
+
+ /**
+ * Responds to this resource request with the given content.
+ *
+ * @param content the resource content (String | Uint8Array | null):
+ *
+ * - String - will be UTF-8 encoded before sending to server
+ * - Uint8Array - sent as-is to server
+ * - null - indicates resource was not found
+ *
+ */
+ @JsMethod
+ public void respond(@JsNullable ResourceContentUnion content) {
+ // Build RemoteFileSourceMetaResponse proto
+ RemoteFileSourceMetaResponse response = new RemoteFileSourceMetaResponse();
+
+ if (content == null) {
+ // Resource not found
+ response.setFound(false);
+ response.setContent(new Uint8Array(0));
+ } else {
+ response.setFound(true);
+
+ // Convert content to bytes using union type methods
+ if (content.isString()) {
+ TextEncoder textEncoder = new TextEncoder();
+ response.setContent(textEncoder.encode(content.asString()));
+ } else if (content.isUint8Array()) {
+ response.setContent(content.asUint8Array());
+ } else {
+ throw new IllegalArgumentException("Content must be a String, Uint8Array, or null");
+ }
+ }
+
+ // Wrap in RemoteFileSourceClientMessage (client→server)
+ RemoteFileSourceClientMessage clientRequest = new RemoteFileSourceClientMessage();
+ clientRequest.setRequestId(requestId);
+ clientRequest.setMetaResponse(response);
+
+ sendClientRequest(clientRequest);
+ }
+ }
+}
diff --git a/web/client-api/src/main/java/io/deephaven/web/client/api/remotefilesource/ResourceContentUnion.java b/web/client-api/src/main/java/io/deephaven/web/client/api/remotefilesource/ResourceContentUnion.java
new file mode 100644
index 00000000000..8728839edd8
--- /dev/null
+++ b/web/client-api/src/main/java/io/deephaven/web/client/api/remotefilesource/ResourceContentUnion.java
@@ -0,0 +1,49 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.web.client.api.remotefilesource;
+
+import com.vertispan.tsdefs.annotations.TsUnion;
+import com.vertispan.tsdefs.annotations.TsUnionMember;
+import elemental2.core.Uint8Array;
+import jsinterop.annotations.JsOverlay;
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsType;
+import jsinterop.base.Js;
+
+/**
+ * Union type for resource content that can be either a String or Uint8Array.
+ */
+@TsUnion
+@JsType(name = "?", namespace = JsPackage.GLOBAL, isNative = true)
+public interface ResourceContentUnion {
+ @JsOverlay
+ static ResourceContentUnion of(Object o) {
+ return Js.cast(o);
+ }
+
+ @JsOverlay
+ default boolean isString() {
+ // Cast to (Object) since Java only "knows" that `this` is `ResourceContentUnion` type which cannot have a
+ // subclass that is also a String.
+ return (Object) this instanceof String;
+ }
+
+ @JsOverlay
+ default boolean isUint8Array() {
+ return this instanceof Uint8Array;
+ }
+
+ @TsUnionMember
+ @JsOverlay
+ default String asString() {
+ return Js.cast(this);
+ }
+
+ @TsUnionMember
+ @JsOverlay
+ default Uint8Array asUint8Array() {
+ return (Uint8Array) this;
+ }
+}
+
diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceClientMessage.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceClientMessage.java
new file mode 100644
index 00000000000..fa1377115b8
--- /dev/null
+++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceClientMessage.java
@@ -0,0 +1,308 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb;
+
+import elemental2.core.JsArray;
+import elemental2.core.Uint8Array;
+import jsinterop.annotations.JsOverlay;
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsProperty;
+import jsinterop.annotations.JsType;
+import jsinterop.base.Js;
+import jsinterop.base.JsPropertyMap;
+
+@JsType(
+ isNative = true,
+ name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceClientMessage",
+ namespace = JsPackage.GLOBAL)
+public class RemoteFileSourceClientMessage {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface MetaResponseFieldType {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface GetContentUnionType {
+ @JsOverlay
+ static RemoteFileSourceClientMessage.ToObjectReturnType.MetaResponseFieldType.GetContentUnionType of(
+ Object o) {
+ return Js.cast(o);
+ }
+
+ @JsOverlay
+ default String asString() {
+ return Js.asString(this);
+ }
+
+ @JsOverlay
+ default Uint8Array asUint8Array() {
+ return Js.cast(this);
+ }
+
+ @JsOverlay
+ default boolean isString() {
+ return (Object) this instanceof String;
+ }
+
+ @JsOverlay
+ default boolean isUint8Array() {
+ return (Object) this instanceof Uint8Array;
+ }
+ }
+
+ @JsOverlay
+ static RemoteFileSourceClientMessage.ToObjectReturnType.MetaResponseFieldType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ RemoteFileSourceClientMessage.ToObjectReturnType.MetaResponseFieldType.GetContentUnionType getContent();
+
+ @JsProperty
+ String getError();
+
+ @JsProperty
+ boolean isFound();
+
+ @JsProperty
+ void setContent(
+ RemoteFileSourceClientMessage.ToObjectReturnType.MetaResponseFieldType.GetContentUnionType content);
+
+ @JsOverlay
+ default void setContent(String content) {
+ setContent(
+ Js.uncheckedCast(
+ content));
+ }
+
+ @JsOverlay
+ default void setContent(Uint8Array content) {
+ setContent(
+ Js.uncheckedCast(
+ content));
+ }
+
+ @JsProperty
+ void setError(String error);
+
+ @JsProperty
+ void setFound(boolean found);
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface SetExecutionContextFieldType {
+ @JsOverlay
+ static RemoteFileSourceClientMessage.ToObjectReturnType.SetExecutionContextFieldType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ JsArray getResourcePathsList();
+
+ @JsProperty
+ boolean isIsDirty();
+
+ @JsProperty
+ void setIsDirty(boolean isDirty);
+
+ @JsProperty
+ void setResourcePathsList(JsArray resourcePathsList);
+
+ @JsOverlay
+ default void setResourcePathsList(String[] resourcePathsList) {
+ setResourcePathsList(Js.>uncheckedCast(resourcePathsList));
+ }
+ }
+
+ @JsOverlay
+ static RemoteFileSourceClientMessage.ToObjectReturnType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ RemoteFileSourceClientMessage.ToObjectReturnType.MetaResponseFieldType getMetaResponse();
+
+ @JsProperty
+ String getRequestId();
+
+ @JsProperty
+ RemoteFileSourceClientMessage.ToObjectReturnType.SetExecutionContextFieldType getSetExecutionContext();
+
+ @JsProperty
+ void setMetaResponse(
+ RemoteFileSourceClientMessage.ToObjectReturnType.MetaResponseFieldType metaResponse);
+
+ @JsProperty
+ void setRequestId(String requestId);
+
+ @JsProperty
+ void setSetExecutionContext(
+ RemoteFileSourceClientMessage.ToObjectReturnType.SetExecutionContextFieldType setExecutionContext);
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType0 {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface MetaResponseFieldType {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface GetContentUnionType {
+ @JsOverlay
+ static RemoteFileSourceClientMessage.ToObjectReturnType0.MetaResponseFieldType.GetContentUnionType of(
+ Object o) {
+ return Js.cast(o);
+ }
+
+ @JsOverlay
+ default String asString() {
+ return Js.asString(this);
+ }
+
+ @JsOverlay
+ default Uint8Array asUint8Array() {
+ return Js.cast(this);
+ }
+
+ @JsOverlay
+ default boolean isString() {
+ return (Object) this instanceof String;
+ }
+
+ @JsOverlay
+ default boolean isUint8Array() {
+ return (Object) this instanceof Uint8Array;
+ }
+ }
+
+ @JsOverlay
+ static RemoteFileSourceClientMessage.ToObjectReturnType0.MetaResponseFieldType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ RemoteFileSourceClientMessage.ToObjectReturnType0.MetaResponseFieldType.GetContentUnionType getContent();
+
+ @JsProperty
+ String getError();
+
+ @JsProperty
+ boolean isFound();
+
+ @JsProperty
+ void setContent(
+ RemoteFileSourceClientMessage.ToObjectReturnType0.MetaResponseFieldType.GetContentUnionType content);
+
+ @JsOverlay
+ default void setContent(String content) {
+ setContent(
+ Js.uncheckedCast(
+ content));
+ }
+
+ @JsOverlay
+ default void setContent(Uint8Array content) {
+ setContent(
+ Js.uncheckedCast(
+ content));
+ }
+
+ @JsProperty
+ void setError(String error);
+
+ @JsProperty
+ void setFound(boolean found);
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface SetExecutionContextFieldType {
+ @JsOverlay
+ static RemoteFileSourceClientMessage.ToObjectReturnType0.SetExecutionContextFieldType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ JsArray getResourcePathsList();
+
+ @JsProperty
+ boolean isIsDirty();
+
+ @JsProperty
+ void setIsDirty(boolean isDirty);
+
+ @JsProperty
+ void setResourcePathsList(JsArray resourcePathsList);
+
+ @JsOverlay
+ default void setResourcePathsList(String[] resourcePathsList) {
+ setResourcePathsList(Js.>uncheckedCast(resourcePathsList));
+ }
+ }
+
+ @JsOverlay
+ static RemoteFileSourceClientMessage.ToObjectReturnType0 create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ RemoteFileSourceClientMessage.ToObjectReturnType0.MetaResponseFieldType getMetaResponse();
+
+ @JsProperty
+ String getRequestId();
+
+ @JsProperty
+ RemoteFileSourceClientMessage.ToObjectReturnType0.SetExecutionContextFieldType getSetExecutionContext();
+
+ @JsProperty
+ void setMetaResponse(
+ RemoteFileSourceClientMessage.ToObjectReturnType0.MetaResponseFieldType metaResponse);
+
+ @JsProperty
+ void setRequestId(String requestId);
+
+ @JsProperty
+ void setSetExecutionContext(
+ RemoteFileSourceClientMessage.ToObjectReturnType0.SetExecutionContextFieldType setExecutionContext);
+ }
+
+ public static native RemoteFileSourceClientMessage deserializeBinary(Uint8Array bytes);
+
+ public static native RemoteFileSourceClientMessage deserializeBinaryFromReader(
+ RemoteFileSourceClientMessage message, Object reader);
+
+ public static native void serializeBinaryToWriter(
+ RemoteFileSourceClientMessage message, Object writer);
+
+ public static native RemoteFileSourceClientMessage.ToObjectReturnType toObject(
+ boolean includeInstance, RemoteFileSourceClientMessage msg);
+
+ public native void clearMetaResponse();
+
+ public native void clearSetExecutionContext();
+
+ public native RemoteFileSourceMetaResponse getMetaResponse();
+
+ public native int getRequestCase();
+
+ public native String getRequestId();
+
+ public native SetExecutionContextRequest getSetExecutionContext();
+
+ public native boolean hasMetaResponse();
+
+ public native boolean hasSetExecutionContext();
+
+ public native Uint8Array serializeBinary();
+
+ public native void setMetaResponse();
+
+ public native void setMetaResponse(RemoteFileSourceMetaResponse value);
+
+ public native void setRequestId(String value);
+
+ public native void setSetExecutionContext();
+
+ public native void setSetExecutionContext(SetExecutionContextRequest value);
+
+ public native RemoteFileSourceClientMessage.ToObjectReturnType0 toObject();
+
+ public native RemoteFileSourceClientMessage.ToObjectReturnType0 toObject(boolean includeInstance);
+}
diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceMetaRequest.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceMetaRequest.java
new file mode 100644
index 00000000000..88b401232a6
--- /dev/null
+++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceMetaRequest.java
@@ -0,0 +1,67 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb;
+
+import elemental2.core.Uint8Array;
+import jsinterop.annotations.JsOverlay;
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsProperty;
+import jsinterop.annotations.JsType;
+import jsinterop.base.Js;
+import jsinterop.base.JsPropertyMap;
+
+@JsType(
+ isNative = true,
+ name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceMetaRequest",
+ namespace = JsPackage.GLOBAL)
+public class RemoteFileSourceMetaRequest {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType {
+ @JsOverlay
+ static RemoteFileSourceMetaRequest.ToObjectReturnType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ String getResourceName();
+
+ @JsProperty
+ void setResourceName(String resourceName);
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType0 {
+ @JsOverlay
+ static RemoteFileSourceMetaRequest.ToObjectReturnType0 create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ String getResourceName();
+
+ @JsProperty
+ void setResourceName(String resourceName);
+ }
+
+ public static native RemoteFileSourceMetaRequest deserializeBinary(Uint8Array bytes);
+
+ public static native RemoteFileSourceMetaRequest deserializeBinaryFromReader(
+ RemoteFileSourceMetaRequest message, Object reader);
+
+ public static native void serializeBinaryToWriter(
+ RemoteFileSourceMetaRequest message, Object writer);
+
+ public static native RemoteFileSourceMetaRequest.ToObjectReturnType toObject(
+ boolean includeInstance, RemoteFileSourceMetaRequest msg);
+
+ public native String getResourceName();
+
+ public native Uint8Array serializeBinary();
+
+ public native void setResourceName(String value);
+
+ public native RemoteFileSourceMetaRequest.ToObjectReturnType0 toObject();
+
+ public native RemoteFileSourceMetaRequest.ToObjectReturnType0 toObject(boolean includeInstance);
+}
diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceMetaResponse.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceMetaResponse.java
new file mode 100644
index 00000000000..057fd695300
--- /dev/null
+++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceMetaResponse.java
@@ -0,0 +1,253 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb;
+
+import elemental2.core.Uint8Array;
+import jsinterop.annotations.JsOverlay;
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsProperty;
+import jsinterop.annotations.JsType;
+import jsinterop.base.Js;
+import jsinterop.base.JsPropertyMap;
+
+@JsType(
+ isNative = true,
+ name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceMetaResponse",
+ namespace = JsPackage.GLOBAL)
+public class RemoteFileSourceMetaResponse {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface GetContentUnionType {
+ @JsOverlay
+ static RemoteFileSourceMetaResponse.GetContentUnionType of(Object o) {
+ return Js.cast(o);
+ }
+
+ @JsOverlay
+ default String asString() {
+ return Js.asString(this);
+ }
+
+ @JsOverlay
+ default Uint8Array asUint8Array() {
+ return Js.cast(this);
+ }
+
+ @JsOverlay
+ default boolean isString() {
+ return (Object) this instanceof String;
+ }
+
+ @JsOverlay
+ default boolean isUint8Array() {
+ return (Object) this instanceof Uint8Array;
+ }
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface SetContentValueUnionType {
+ @JsOverlay
+ static RemoteFileSourceMetaResponse.SetContentValueUnionType of(Object o) {
+ return Js.cast(o);
+ }
+
+ @JsOverlay
+ default String asString() {
+ return Js.asString(this);
+ }
+
+ @JsOverlay
+ default Uint8Array asUint8Array() {
+ return Js.cast(this);
+ }
+
+ @JsOverlay
+ default boolean isString() {
+ return (Object) this instanceof String;
+ }
+
+ @JsOverlay
+ default boolean isUint8Array() {
+ return (Object) this instanceof Uint8Array;
+ }
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface GetContentUnionType {
+ @JsOverlay
+ static RemoteFileSourceMetaResponse.ToObjectReturnType.GetContentUnionType of(Object o) {
+ return Js.cast(o);
+ }
+
+ @JsOverlay
+ default String asString() {
+ return Js.asString(this);
+ }
+
+ @JsOverlay
+ default Uint8Array asUint8Array() {
+ return Js.cast(this);
+ }
+
+ @JsOverlay
+ default boolean isString() {
+ return (Object) this instanceof String;
+ }
+
+ @JsOverlay
+ default boolean isUint8Array() {
+ return (Object) this instanceof Uint8Array;
+ }
+ }
+
+ @JsOverlay
+ static RemoteFileSourceMetaResponse.ToObjectReturnType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ RemoteFileSourceMetaResponse.ToObjectReturnType.GetContentUnionType getContent();
+
+ @JsProperty
+ String getError();
+
+ @JsProperty
+ boolean isFound();
+
+ @JsProperty
+ void setContent(RemoteFileSourceMetaResponse.ToObjectReturnType.GetContentUnionType content);
+
+ @JsOverlay
+ default void setContent(String content) {
+ setContent(
+ Js.uncheckedCast(
+ content));
+ }
+
+ @JsOverlay
+ default void setContent(Uint8Array content) {
+ setContent(
+ Js.uncheckedCast(
+ content));
+ }
+
+ @JsProperty
+ void setError(String error);
+
+ @JsProperty
+ void setFound(boolean found);
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType0 {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface GetContentUnionType {
+ @JsOverlay
+ static RemoteFileSourceMetaResponse.ToObjectReturnType0.GetContentUnionType of(Object o) {
+ return Js.cast(o);
+ }
+
+ @JsOverlay
+ default String asString() {
+ return Js.asString(this);
+ }
+
+ @JsOverlay
+ default Uint8Array asUint8Array() {
+ return Js.cast(this);
+ }
+
+ @JsOverlay
+ default boolean isString() {
+ return (Object) this instanceof String;
+ }
+
+ @JsOverlay
+ default boolean isUint8Array() {
+ return (Object) this instanceof Uint8Array;
+ }
+ }
+
+ @JsOverlay
+ static RemoteFileSourceMetaResponse.ToObjectReturnType0 create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ RemoteFileSourceMetaResponse.ToObjectReturnType0.GetContentUnionType getContent();
+
+ @JsProperty
+ String getError();
+
+ @JsProperty
+ boolean isFound();
+
+ @JsProperty
+ void setContent(RemoteFileSourceMetaResponse.ToObjectReturnType0.GetContentUnionType content);
+
+ @JsOverlay
+ default void setContent(String content) {
+ setContent(
+ Js.uncheckedCast(
+ content));
+ }
+
+ @JsOverlay
+ default void setContent(Uint8Array content) {
+ setContent(
+ Js.uncheckedCast(
+ content));
+ }
+
+ @JsProperty
+ void setError(String error);
+
+ @JsProperty
+ void setFound(boolean found);
+ }
+
+ public static native RemoteFileSourceMetaResponse deserializeBinary(Uint8Array bytes);
+
+ public static native RemoteFileSourceMetaResponse deserializeBinaryFromReader(
+ RemoteFileSourceMetaResponse message, Object reader);
+
+ public static native void serializeBinaryToWriter(
+ RemoteFileSourceMetaResponse message, Object writer);
+
+ public static native RemoteFileSourceMetaResponse.ToObjectReturnType toObject(
+ boolean includeInstance, RemoteFileSourceMetaResponse msg);
+
+ public native RemoteFileSourceMetaResponse.GetContentUnionType getContent();
+
+ public native String getContent_asB64();
+
+ public native Uint8Array getContent_asU8();
+
+ public native String getError();
+
+ public native boolean getFound();
+
+ public native Uint8Array serializeBinary();
+
+ public native void setContent(RemoteFileSourceMetaResponse.SetContentValueUnionType value);
+
+ @JsOverlay
+ public final void setContent(String value) {
+ setContent(Js.uncheckedCast(value));
+ }
+
+ @JsOverlay
+ public final void setContent(Uint8Array value) {
+ setContent(Js.uncheckedCast(value));
+ }
+
+ public native void setError(String value);
+
+ public native void setFound(boolean value);
+
+ public native RemoteFileSourceMetaResponse.ToObjectReturnType0 toObject();
+
+ public native RemoteFileSourceMetaResponse.ToObjectReturnType0 toObject(boolean includeInstance);
+}
diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourcePluginFetchRequest.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourcePluginFetchRequest.java
new file mode 100644
index 00000000000..afc60285df6
--- /dev/null
+++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourcePluginFetchRequest.java
@@ -0,0 +1,209 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb;
+
+import elemental2.core.Uint8Array;
+import io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.ticket_pb.Ticket;
+import jsinterop.annotations.JsOverlay;
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsProperty;
+import jsinterop.annotations.JsType;
+import jsinterop.base.Js;
+import jsinterop.base.JsPropertyMap;
+
+@JsType(
+ isNative = true,
+ name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourcePluginFetchRequest",
+ namespace = JsPackage.GLOBAL)
+public class RemoteFileSourcePluginFetchRequest {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ResultIdFieldType {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface GetTicketUnionType {
+ @JsOverlay
+ static RemoteFileSourcePluginFetchRequest.ToObjectReturnType.ResultIdFieldType.GetTicketUnionType of(
+ Object o) {
+ return Js.cast(o);
+ }
+
+ @JsOverlay
+ default String asString() {
+ return Js.asString(this);
+ }
+
+ @JsOverlay
+ default Uint8Array asUint8Array() {
+ return Js.cast(this);
+ }
+
+ @JsOverlay
+ default boolean isString() {
+ return (Object) this instanceof String;
+ }
+
+ @JsOverlay
+ default boolean isUint8Array() {
+ return (Object) this instanceof Uint8Array;
+ }
+ }
+
+ @JsOverlay
+ static RemoteFileSourcePluginFetchRequest.ToObjectReturnType.ResultIdFieldType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ RemoteFileSourcePluginFetchRequest.ToObjectReturnType.ResultIdFieldType.GetTicketUnionType getTicket();
+
+ @JsProperty
+ void setTicket(
+ RemoteFileSourcePluginFetchRequest.ToObjectReturnType.ResultIdFieldType.GetTicketUnionType ticket);
+
+ @JsOverlay
+ default void setTicket(String ticket) {
+ setTicket(
+ Js.uncheckedCast(
+ ticket));
+ }
+
+ @JsOverlay
+ default void setTicket(Uint8Array ticket) {
+ setTicket(
+ Js.uncheckedCast(
+ ticket));
+ }
+ }
+
+ @JsOverlay
+ static RemoteFileSourcePluginFetchRequest.ToObjectReturnType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ String getPluginName();
+
+ @JsProperty
+ RemoteFileSourcePluginFetchRequest.ToObjectReturnType.ResultIdFieldType getResultId();
+
+ @JsProperty
+ void setPluginName(String pluginName);
+
+ @JsProperty
+ void setResultId(
+ RemoteFileSourcePluginFetchRequest.ToObjectReturnType.ResultIdFieldType resultId);
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType0 {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ResultIdFieldType {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface GetTicketUnionType {
+ @JsOverlay
+ static RemoteFileSourcePluginFetchRequest.ToObjectReturnType0.ResultIdFieldType.GetTicketUnionType of(
+ Object o) {
+ return Js.cast(o);
+ }
+
+ @JsOverlay
+ default String asString() {
+ return Js.asString(this);
+ }
+
+ @JsOverlay
+ default Uint8Array asUint8Array() {
+ return Js.cast(this);
+ }
+
+ @JsOverlay
+ default boolean isString() {
+ return (Object) this instanceof String;
+ }
+
+ @JsOverlay
+ default boolean isUint8Array() {
+ return (Object) this instanceof Uint8Array;
+ }
+ }
+
+ @JsOverlay
+ static RemoteFileSourcePluginFetchRequest.ToObjectReturnType0.ResultIdFieldType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ RemoteFileSourcePluginFetchRequest.ToObjectReturnType0.ResultIdFieldType.GetTicketUnionType getTicket();
+
+ @JsProperty
+ void setTicket(
+ RemoteFileSourcePluginFetchRequest.ToObjectReturnType0.ResultIdFieldType.GetTicketUnionType ticket);
+
+ @JsOverlay
+ default void setTicket(String ticket) {
+ setTicket(
+ Js.uncheckedCast(
+ ticket));
+ }
+
+ @JsOverlay
+ default void setTicket(Uint8Array ticket) {
+ setTicket(
+ Js.uncheckedCast(
+ ticket));
+ }
+ }
+
+ @JsOverlay
+ static RemoteFileSourcePluginFetchRequest.ToObjectReturnType0 create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ String getPluginName();
+
+ @JsProperty
+ RemoteFileSourcePluginFetchRequest.ToObjectReturnType0.ResultIdFieldType getResultId();
+
+ @JsProperty
+ void setPluginName(String pluginName);
+
+ @JsProperty
+ void setResultId(
+ RemoteFileSourcePluginFetchRequest.ToObjectReturnType0.ResultIdFieldType resultId);
+ }
+
+ public static native RemoteFileSourcePluginFetchRequest deserializeBinary(Uint8Array bytes);
+
+ public static native RemoteFileSourcePluginFetchRequest deserializeBinaryFromReader(
+ RemoteFileSourcePluginFetchRequest message, Object reader);
+
+ public static native void serializeBinaryToWriter(
+ RemoteFileSourcePluginFetchRequest message, Object writer);
+
+ public static native RemoteFileSourcePluginFetchRequest.ToObjectReturnType toObject(
+ boolean includeInstance, RemoteFileSourcePluginFetchRequest msg);
+
+ public native void clearResultId();
+
+ public native String getPluginName();
+
+ public native Ticket getResultId();
+
+ public native boolean hasResultId();
+
+ public native Uint8Array serializeBinary();
+
+ public native void setPluginName(String value);
+
+ public native void setResultId();
+
+ public native void setResultId(Ticket value);
+
+ public native RemoteFileSourcePluginFetchRequest.ToObjectReturnType0 toObject();
+
+ public native RemoteFileSourcePluginFetchRequest.ToObjectReturnType0 toObject(
+ boolean includeInstance);
+}
diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceServerMessage.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceServerMessage.java
new file mode 100644
index 00000000000..9069c393d6d
--- /dev/null
+++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceServerMessage.java
@@ -0,0 +1,173 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb;
+
+import elemental2.core.Uint8Array;
+import jsinterop.annotations.JsOverlay;
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsProperty;
+import jsinterop.annotations.JsType;
+import jsinterop.base.Js;
+import jsinterop.base.JsPropertyMap;
+
+@JsType(
+ isNative = true,
+ name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceServerMessage",
+ namespace = JsPackage.GLOBAL)
+public class RemoteFileSourceServerMessage {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface MetaRequestFieldType {
+ @JsOverlay
+ static RemoteFileSourceServerMessage.ToObjectReturnType.MetaRequestFieldType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ String getResourceName();
+
+ @JsProperty
+ void setResourceName(String resourceName);
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface SetExecutionContextResponseFieldType {
+ @JsOverlay
+ static RemoteFileSourceServerMessage.ToObjectReturnType.SetExecutionContextResponseFieldType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ boolean isSuccess();
+
+ @JsProperty
+ void setSuccess(boolean success);
+ }
+
+ @JsOverlay
+ static RemoteFileSourceServerMessage.ToObjectReturnType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ RemoteFileSourceServerMessage.ToObjectReturnType.MetaRequestFieldType getMetaRequest();
+
+ @JsProperty
+ String getRequestId();
+
+ @JsProperty
+ RemoteFileSourceServerMessage.ToObjectReturnType.SetExecutionContextResponseFieldType getSetExecutionContextResponse();
+
+ @JsProperty
+ void setMetaRequest(
+ RemoteFileSourceServerMessage.ToObjectReturnType.MetaRequestFieldType metaRequest);
+
+ @JsProperty
+ void setRequestId(String requestId);
+
+ @JsProperty
+ void setSetExecutionContextResponse(
+ RemoteFileSourceServerMessage.ToObjectReturnType.SetExecutionContextResponseFieldType setExecutionContextResponse);
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType0 {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface MetaRequestFieldType {
+ @JsOverlay
+ static RemoteFileSourceServerMessage.ToObjectReturnType0.MetaRequestFieldType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ String getResourceName();
+
+ @JsProperty
+ void setResourceName(String resourceName);
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface SetExecutionContextResponseFieldType {
+ @JsOverlay
+ static RemoteFileSourceServerMessage.ToObjectReturnType0.SetExecutionContextResponseFieldType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ boolean isSuccess();
+
+ @JsProperty
+ void setSuccess(boolean success);
+ }
+
+ @JsOverlay
+ static RemoteFileSourceServerMessage.ToObjectReturnType0 create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ RemoteFileSourceServerMessage.ToObjectReturnType0.MetaRequestFieldType getMetaRequest();
+
+ @JsProperty
+ String getRequestId();
+
+ @JsProperty
+ RemoteFileSourceServerMessage.ToObjectReturnType0.SetExecutionContextResponseFieldType getSetExecutionContextResponse();
+
+ @JsProperty
+ void setMetaRequest(
+ RemoteFileSourceServerMessage.ToObjectReturnType0.MetaRequestFieldType metaRequest);
+
+ @JsProperty
+ void setRequestId(String requestId);
+
+ @JsProperty
+ void setSetExecutionContextResponse(
+ RemoteFileSourceServerMessage.ToObjectReturnType0.SetExecutionContextResponseFieldType setExecutionContextResponse);
+ }
+
+ public static native RemoteFileSourceServerMessage deserializeBinary(Uint8Array bytes);
+
+ public static native RemoteFileSourceServerMessage deserializeBinaryFromReader(
+ RemoteFileSourceServerMessage message, Object reader);
+
+ public static native void serializeBinaryToWriter(
+ RemoteFileSourceServerMessage message, Object writer);
+
+ public static native RemoteFileSourceServerMessage.ToObjectReturnType toObject(
+ boolean includeInstance, RemoteFileSourceServerMessage msg);
+
+ public native void clearMetaRequest();
+
+ public native void clearSetExecutionContextResponse();
+
+ public native RemoteFileSourceMetaRequest getMetaRequest();
+
+ public native int getRequestCase();
+
+ public native String getRequestId();
+
+ public native SetExecutionContextResponse getSetExecutionContextResponse();
+
+ public native boolean hasMetaRequest();
+
+ public native boolean hasSetExecutionContextResponse();
+
+ public native Uint8Array serializeBinary();
+
+ public native void setMetaRequest();
+
+ public native void setMetaRequest(RemoteFileSourceMetaRequest value);
+
+ public native void setRequestId(String value);
+
+ public native void setSetExecutionContextResponse();
+
+ public native void setSetExecutionContextResponse(SetExecutionContextResponse value);
+
+ public native RemoteFileSourceServerMessage.ToObjectReturnType0 toObject();
+
+ public native RemoteFileSourceServerMessage.ToObjectReturnType0 toObject(boolean includeInstance);
+}
diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/SetExecutionContextRequest.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/SetExecutionContextRequest.java
new file mode 100644
index 00000000000..728323a187a
--- /dev/null
+++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/SetExecutionContextRequest.java
@@ -0,0 +1,105 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb;
+
+import elemental2.core.JsArray;
+import elemental2.core.Uint8Array;
+import jsinterop.annotations.JsOverlay;
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsProperty;
+import jsinterop.annotations.JsType;
+import jsinterop.base.Js;
+import jsinterop.base.JsPropertyMap;
+
+@JsType(
+ isNative = true,
+ name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.SetExecutionContextRequest",
+ namespace = JsPackage.GLOBAL)
+public class SetExecutionContextRequest {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType {
+ @JsOverlay
+ static SetExecutionContextRequest.ToObjectReturnType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ JsArray getResourcePathsList();
+
+ @JsProperty
+ boolean isIsDirty();
+
+ @JsProperty
+ void setIsDirty(boolean isDirty);
+
+ @JsProperty
+ void setResourcePathsList(JsArray resourcePathsList);
+
+ @JsOverlay
+ default void setResourcePathsList(String[] resourcePathsList) {
+ setResourcePathsList(Js.>uncheckedCast(resourcePathsList));
+ }
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType0 {
+ @JsOverlay
+ static SetExecutionContextRequest.ToObjectReturnType0 create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ JsArray getResourcePathsList();
+
+ @JsProperty
+ boolean isIsDirty();
+
+ @JsProperty
+ void setIsDirty(boolean isDirty);
+
+ @JsProperty
+ void setResourcePathsList(JsArray resourcePathsList);
+
+ @JsOverlay
+ default void setResourcePathsList(String[] resourcePathsList) {
+ setResourcePathsList(Js.>uncheckedCast(resourcePathsList));
+ }
+ }
+
+ public static native SetExecutionContextRequest deserializeBinary(Uint8Array bytes);
+
+ public static native SetExecutionContextRequest deserializeBinaryFromReader(
+ SetExecutionContextRequest message, Object reader);
+
+ public static native void serializeBinaryToWriter(
+ SetExecutionContextRequest message, Object writer);
+
+ public static native SetExecutionContextRequest.ToObjectReturnType toObject(
+ boolean includeInstance, SetExecutionContextRequest msg);
+
+ public native String addResourcePaths(String value, double index);
+
+ public native String addResourcePaths(String value);
+
+ public native void clearResourcePathsList();
+
+ public native boolean getIsDirty();
+
+ public native JsArray getResourcePathsList();
+
+ public native Uint8Array serializeBinary();
+
+ public native void setIsDirty(boolean value);
+
+ public native void setResourcePathsList(JsArray value);
+
+ @JsOverlay
+ public final void setResourcePathsList(String[] value) {
+ setResourcePathsList(Js.>uncheckedCast(value));
+ }
+
+ public native SetExecutionContextRequest.ToObjectReturnType0 toObject();
+
+ public native SetExecutionContextRequest.ToObjectReturnType0 toObject(boolean includeInstance);
+}
diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/SetExecutionContextResponse.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/SetExecutionContextResponse.java
new file mode 100644
index 00000000000..6f9b83938c7
--- /dev/null
+++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/SetExecutionContextResponse.java
@@ -0,0 +1,67 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb;
+
+import elemental2.core.Uint8Array;
+import jsinterop.annotations.JsOverlay;
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsProperty;
+import jsinterop.annotations.JsType;
+import jsinterop.base.Js;
+import jsinterop.base.JsPropertyMap;
+
+@JsType(
+ isNative = true,
+ name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.SetExecutionContextResponse",
+ namespace = JsPackage.GLOBAL)
+public class SetExecutionContextResponse {
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType {
+ @JsOverlay
+ static SetExecutionContextResponse.ToObjectReturnType create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ boolean isSuccess();
+
+ @JsProperty
+ void setSuccess(boolean success);
+ }
+
+ @JsType(isNative = true, name = "?", namespace = JsPackage.GLOBAL)
+ public interface ToObjectReturnType0 {
+ @JsOverlay
+ static SetExecutionContextResponse.ToObjectReturnType0 create() {
+ return Js.uncheckedCast(JsPropertyMap.of());
+ }
+
+ @JsProperty
+ boolean isSuccess();
+
+ @JsProperty
+ void setSuccess(boolean success);
+ }
+
+ public static native SetExecutionContextResponse deserializeBinary(Uint8Array bytes);
+
+ public static native SetExecutionContextResponse deserializeBinaryFromReader(
+ SetExecutionContextResponse message, Object reader);
+
+ public static native void serializeBinaryToWriter(
+ SetExecutionContextResponse message, Object writer);
+
+ public static native SetExecutionContextResponse.ToObjectReturnType toObject(
+ boolean includeInstance, SetExecutionContextResponse msg);
+
+ public native boolean getSuccess();
+
+ public native Uint8Array serializeBinary();
+
+ public native void setSuccess(boolean value);
+
+ public native SetExecutionContextResponse.ToObjectReturnType0 toObject();
+
+ public native SetExecutionContextResponse.ToObjectReturnType0 toObject(boolean includeInstance);
+}
diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/remotefilesourceclientmessage/RequestCase.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/remotefilesourceclientmessage/RequestCase.java
new file mode 100644
index 00000000000..402849f4f93
--- /dev/null
+++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/remotefilesourceclientmessage/RequestCase.java
@@ -0,0 +1,15 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.remotefilesourceclientmessage;
+
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsType;
+
+@JsType(
+ isNative = true,
+ name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceClientMessage.RequestCase",
+ namespace = JsPackage.GLOBAL)
+public class RequestCase {
+ public static int META_RESPONSE, REQUEST_NOT_SET, SET_EXECUTION_CONTEXT;
+}
diff --git a/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/remotefilesourceservermessage/RequestCase.java b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/remotefilesourceservermessage/RequestCase.java
new file mode 100644
index 00000000000..5d3752b8728
--- /dev/null
+++ b/web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/remotefilesourceservermessage/RequestCase.java
@@ -0,0 +1,15 @@
+//
+// Copyright (c) 2016-2026 Deephaven Data Labs and Patent Pending
+//
+package io.deephaven.javascript.proto.dhinternal.io.deephaven_core.proto.remotefilesource_pb.remotefilesourceservermessage;
+
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsType;
+
+@JsType(
+ isNative = true,
+ name = "dhinternal.io.deephaven_core.proto.remotefilesource_pb.RemoteFileSourceServerMessage.RequestCase",
+ namespace = JsPackage.GLOBAL)
+public class RequestCase {
+ public static int META_REQUEST, REQUEST_NOT_SET, SET_EXECUTION_CONTEXT_RESPONSE;
+}