mirror of
https://github.com/cline/cline.git
synced 2026-09-21 05:10:09 +08:00
Improve generated code that registers the vscode host bridge handlers. (#4972)
This commit is contained in:
+1
-161
@@ -9,7 +9,7 @@ import chalk from "chalk"
|
||||
import os from "os"
|
||||
|
||||
import { createRequire } from "module"
|
||||
import { serviceNameMap, hostServiceNameMap } from "./build-proto-config.mjs"
|
||||
import { serviceNameMap } from "./build-proto-config.mjs"
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const PROTOC = path.join(require.resolve("grpc-tools"), "../bin/protoc")
|
||||
@@ -88,9 +88,6 @@ async function main() {
|
||||
await generateProtoBusMethodRegistrations()
|
||||
await generateProtoBusGrpcClientConfig()
|
||||
|
||||
await generateHostBridgeServiceConfig()
|
||||
await generateHostBridgeMethodRegistrations()
|
||||
|
||||
console.log(chalk.bold.blue("Finished Protocol Buffer code generation."))
|
||||
}
|
||||
|
||||
@@ -420,163 +417,6 @@ service ${serviceClassName} {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate method registration files for host services
|
||||
*/
|
||||
async function generateHostBridgeMethodRegistrations() {
|
||||
log_verbose(chalk.cyan("Generating host method registration files..."))
|
||||
// Host service directories derived from imported hostServiceNameMap
|
||||
const hostServiceDirs = Object.keys(hostServiceNameMap).map((serviceKey) =>
|
||||
path.join("src/hosts/vscode/hostbridge", serviceKey),
|
||||
)
|
||||
|
||||
// Parse proto files for streaming methods
|
||||
const hostProtoFiles = await globby("*.proto", { cwd: path.join(PROTO_DIR, "host") })
|
||||
const streamingMethodsMap = await parseProtoForStreamingMethods(hostProtoFiles, path.join(PROTO_DIR, "host"))
|
||||
|
||||
for (const serviceDir of hostServiceDirs) {
|
||||
const serviceName = path.basename(serviceDir)
|
||||
const fullServiceName = hostServiceNameMap[serviceName]
|
||||
const streamingMethods = streamingMethodsMap.get(fullServiceName) || []
|
||||
const outputDir = path.join("src/generated/hosts/vscode/hostbridge", serviceName)
|
||||
|
||||
log_verbose(chalk.cyan(`Generating method registrations for host ${serviceName}...`))
|
||||
|
||||
// Get all TypeScript files in the service directory
|
||||
const files = await globby("*.ts", { cwd: serviceDir })
|
||||
|
||||
// Filter out index.ts and methods.ts
|
||||
const implementationFiles = files.filter((file) => file !== "index.ts" && file !== "methods.ts")
|
||||
|
||||
// Create the methods.ts file with header
|
||||
let methodsContent = `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated ${SCRIPT_NAME}
|
||||
|
||||
// Import all method implementations
|
||||
import { registerMethod } from "./index"\n`
|
||||
|
||||
// Import implementations directly
|
||||
for (const file of implementationFiles) {
|
||||
const baseName = path.basename(file, ".ts")
|
||||
methodsContent += `import { ${baseName} } from "@hosts/vscode/hostbridge/${serviceName}/${baseName}"\n`
|
||||
}
|
||||
|
||||
// Add streaming methods information
|
||||
if (streamingMethods.length > 0) {
|
||||
methodsContent += `\n// Streaming methods for this service
|
||||
export const streamingMethods = ${JSON.stringify(
|
||||
streamingMethods.map((m) => m.name),
|
||||
null,
|
||||
2,
|
||||
)}\n`
|
||||
}
|
||||
|
||||
// Add registration function
|
||||
methodsContent += `\n// Register all ${serviceName} service methods
|
||||
export function registerAllMethods(): void {
|
||||
\t// Register each method with the registry\n`
|
||||
|
||||
// Add registration statements
|
||||
for (const file of implementationFiles) {
|
||||
const baseName = path.basename(file, ".ts")
|
||||
const isStreaming = streamingMethods.some((m) => m.name === baseName)
|
||||
|
||||
if (isStreaming) {
|
||||
methodsContent += `\tregisterMethod("${baseName}", ${baseName}, { isStreaming: true })\n`
|
||||
} else {
|
||||
methodsContent += `\tregisterMethod("${baseName}", ${baseName})\n`
|
||||
}
|
||||
}
|
||||
|
||||
// Close the function
|
||||
methodsContent += `}`
|
||||
|
||||
// Write the methods.ts file
|
||||
const registryFile = path.join(outputDir, "methods.ts")
|
||||
await writeFileWithMkdirs(registryFile, methodsContent)
|
||||
log_verbose(chalk.green(`Generated ${registryFile}`))
|
||||
|
||||
// Generate index.ts file
|
||||
const capitalizedServiceName = serviceName.charAt(0).toUpperCase() + serviceName.slice(1)
|
||||
const indexContent = `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by ${SCRIPT_NAME}
|
||||
|
||||
import { createServiceRegistry, ServiceMethodHandler, StreamingMethodHandler } from "@hosts/vscode/hostbridge-grpc-service"
|
||||
import { StreamingResponseHandler } from "@hosts/vscode/hostbridge-grpc-handler"
|
||||
import { registerAllMethods } from "./methods"
|
||||
|
||||
// Create ${serviceName} service registry
|
||||
const ${serviceName}Service = createServiceRegistry("${serviceName}")
|
||||
|
||||
// Export the method handler types and registration function
|
||||
export type ${capitalizedServiceName}MethodHandler = ServiceMethodHandler
|
||||
export type ${capitalizedServiceName}StreamingMethodHandler = StreamingMethodHandler
|
||||
export const registerMethod = ${serviceName}Service.registerMethod
|
||||
|
||||
// Export the request handlers
|
||||
export const handle${capitalizedServiceName}ServiceRequest = ${serviceName}Service.handleRequest
|
||||
export const handle${capitalizedServiceName}ServiceStreamingRequest = ${serviceName}Service.handleStreamingRequest
|
||||
export const isStreamingMethod = ${serviceName}Service.isStreamingMethod
|
||||
|
||||
// Register all ${serviceName} methods
|
||||
registerAllMethods()`
|
||||
|
||||
// Write the index.ts file
|
||||
const indexFile = path.join(outputDir, "index.ts")
|
||||
await writeFileWithMkdirs(indexFile, indexContent)
|
||||
log_verbose(chalk.green(`Generated ${indexFile}`))
|
||||
}
|
||||
|
||||
log_verbose(chalk.green("Host method registration files generated successfully."))
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a service configuration file for host services
|
||||
*/
|
||||
async function generateHostBridgeServiceConfig() {
|
||||
log_verbose(chalk.cyan("Generating host service configuration file..."))
|
||||
|
||||
const serviceImports = []
|
||||
const serviceConfigs = []
|
||||
|
||||
// Add all services from the hostServiceNameMap
|
||||
for (const [dirName, fullServiceName] of Object.entries(hostServiceNameMap)) {
|
||||
const capitalizedName = dirName.charAt(0).toUpperCase() + dirName.slice(1)
|
||||
serviceImports.push(
|
||||
`import { handle${capitalizedName}ServiceRequest, handle${capitalizedName}ServiceStreamingRequest } from "@generated/hosts/vscode/hostbridge/${dirName}/index"`,
|
||||
)
|
||||
serviceConfigs.push(`
|
||||
"${fullServiceName}": {
|
||||
requestHandler: handle${capitalizedName}ServiceRequest,
|
||||
streamingHandler: handle${capitalizedName}ServiceStreamingRequest
|
||||
}`)
|
||||
}
|
||||
|
||||
const content = `// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
|
||||
// Generated by ${SCRIPT_NAME}
|
||||
|
||||
import { StreamingResponseHandler } from "@/hosts/vscode/hostbridge-grpc-handler"
|
||||
${serviceImports.join("\n")}
|
||||
|
||||
/**
|
||||
* Configuration for a host service handler
|
||||
*/
|
||||
export interface HostServiceHandlerConfig {
|
||||
requestHandler: (method: string, message: any) => Promise<any>;
|
||||
streamingHandler: (method: string, message: any, responseStream: StreamingResponseHandler, requestId?: string) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of host service names to their handler configurations
|
||||
*/
|
||||
export const hostServiceHandlers: Record<string, HostServiceHandlerConfig> = {${serviceConfigs.join(",")}
|
||||
};`
|
||||
|
||||
const filePath = "src/generated/hosts/vscode/hostbridge-grpc-service-config.ts"
|
||||
await writeFileWithMkdirs(filePath, content)
|
||||
log_verbose(chalk.green(`Generated host service configuration at ${filePath}`))
|
||||
}
|
||||
|
||||
async function cleanup() {
|
||||
// Clean up existing generated files
|
||||
log_verbose(chalk.cyan("Cleaning up existing generated TypeScript files..."))
|
||||
|
||||
@@ -6,10 +6,15 @@ import * as grpc from "@grpc/grpc-js"
|
||||
import * as protoLoader from "@grpc/proto-loader"
|
||||
import chalk from "chalk"
|
||||
|
||||
const IMPL_FILE = path.resolve("src/generated/standalone/host-bridge-clients.ts")
|
||||
const INTERFACE_FILE = path.resolve("src/generated/hosts/host-bridge-client-types.ts")
|
||||
const DESCRIPTOR_SET = path.resolve("dist-standalone/proto/descriptor_set.pb")
|
||||
|
||||
// Contains the interface definitions for the host bridge clients.
|
||||
const TYPES_FILE = path.resolve("src/generated/hosts/host-bridge-client-types.ts")
|
||||
// Contains the ExternalHostBridgeClientManager for the external host bridge clients (using nice-grpc).
|
||||
const EXTERNAL_CLIENT_FILE = path.resolve("src/generated/hosts/standalone/host-bridge-clients.ts")
|
||||
// Contains the handler map for the external host bridge clients (using the custom service registry).
|
||||
const VSCODE_CLIENT_FILE = path.resolve("src/generated/hosts/vscode/hostbridge-grpc-service-config.ts")
|
||||
|
||||
const typeNameToFQN = new Map()
|
||||
|
||||
function addTypeNameToFqn(name, fqn) {
|
||||
@@ -48,24 +53,23 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Generate interfaces file
|
||||
await generateInterfacesFile(hostServices)
|
||||
|
||||
// // Generate implementation file
|
||||
await generateImplementationFile(hostServices)
|
||||
await generateTypesFile(hostServices)
|
||||
await generateExternalClientFile(hostServices)
|
||||
await generateVscodeClientFile(hostServices)
|
||||
|
||||
console.log(`Generated host bridge client files at:`)
|
||||
console.log(`- ${INTERFACE_FILE}`)
|
||||
console.log(`- ${IMPL_FILE}`)
|
||||
console.log(`- ${TYPES_FILE}`)
|
||||
console.log(`- ${EXTERNAL_CLIENT_FILE}`)
|
||||
console.log(`- ${VSCODE_CLIENT_FILE}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the client interfaces file.
|
||||
*/
|
||||
async function generateInterfacesFile(hostServices) {
|
||||
async function generateTypesFile(hostServices) {
|
||||
const clientInterfaces = []
|
||||
for (const [name, def] of Object.entries(hostServices)) {
|
||||
const clientInterface = generateClientInterface(name, def)
|
||||
const clientInterface = generateClientInterfaceType(name, def)
|
||||
clientInterfaces.push(clientInterface)
|
||||
}
|
||||
const content = `// GENERATED CODE -- DO NOT EDIT!
|
||||
@@ -76,14 +80,14 @@ import { StreamingCallbacks } from "@hosts/host-provider-types"
|
||||
${clientInterfaces.join("\n\n")}
|
||||
`
|
||||
// Write output file
|
||||
await fs.mkdir(path.dirname(INTERFACE_FILE), { recursive: true })
|
||||
await fs.writeFile(INTERFACE_FILE, content)
|
||||
await fs.mkdir(path.dirname(TYPES_FILE), { recursive: true })
|
||||
await fs.writeFile(TYPES_FILE, content)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a client interface for a service.
|
||||
*/
|
||||
function generateClientInterface(serviceName, serviceDefinition) {
|
||||
function generateClientInterfaceType(serviceName, serviceDefinition) {
|
||||
// Get the methods from the service definition
|
||||
const methods = Object.entries(serviceDefinition.service)
|
||||
.map(([methodName, methodDef]) => {
|
||||
@@ -110,9 +114,9 @@ ${methods}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the client implementations file.
|
||||
* Generate the external client implementations file.
|
||||
*/
|
||||
async function generateImplementationFile(hostServices) {
|
||||
async function generateExternalClientFile(hostServices) {
|
||||
// Generate imports
|
||||
const imports = []
|
||||
// Add imports for the interfaces
|
||||
@@ -121,7 +125,7 @@ async function generateImplementationFile(hostServices) {
|
||||
}
|
||||
const clientImplementations = []
|
||||
for (const [name, def] of Object.entries(hostServices)) {
|
||||
clientImplementations.push(generateClientImplementation(name, def))
|
||||
clientImplementations.push(generateExternalClientSetup(name, def))
|
||||
}
|
||||
|
||||
const content = `// GENERATED CODE -- DO NOT EDIT!
|
||||
@@ -136,16 +140,15 @@ ${imports.join("\n")}
|
||||
|
||||
${clientImplementations.join("\n\n")}
|
||||
`
|
||||
|
||||
// Write output file
|
||||
await fs.mkdir(path.dirname(IMPL_FILE), { recursive: true })
|
||||
await fs.writeFile(IMPL_FILE, content)
|
||||
await fs.mkdir(path.dirname(EXTERNAL_CLIENT_FILE), { recursive: true })
|
||||
await fs.writeFile(EXTERNAL_CLIENT_FILE, content)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a client implementation class for a service
|
||||
*/
|
||||
function generateClientImplementation(serviceName, serviceDefinition) {
|
||||
function generateExternalClientSetup(serviceName, serviceDefinition) {
|
||||
// Get the methods from the service definition
|
||||
const methods = Object.entries(serviceDefinition.service)
|
||||
.map(([methodName, methodDef]) => {
|
||||
@@ -185,6 +188,71 @@ ${methods}
|
||||
}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the Vscode client setup file.
|
||||
*/
|
||||
async function generateVscodeClientFile(hostServices) {
|
||||
const imports = []
|
||||
const clientImplementations = []
|
||||
const handlerMap = []
|
||||
for (const [serviceName, serviceDefinition] of Object.entries(hostServices)) {
|
||||
const name = serviceName.replace(/Service$/, "").toLowerCase()
|
||||
for (const [methodName, _methodDef] of Object.entries(serviceDefinition.service)) {
|
||||
imports.push(`import { ${methodName} } from "@/hosts/vscode/hostbridge/${name}/${methodName}"`)
|
||||
}
|
||||
imports.push("")
|
||||
|
||||
clientImplementations.push(generateVscodeClientImplementation(name, serviceDefinition))
|
||||
|
||||
handlerMap.push(` "host.${serviceName}": {
|
||||
requestHandler: ${name}ServiceRegistry.handleRequest,
|
||||
streamingHandler: ${name}ServiceRegistry.handleStreamingRequest,
|
||||
},`)
|
||||
}
|
||||
|
||||
const content = `// GENERATED CODE -- DO NOT EDIT!
|
||||
// Generated by scripts/generate-host-bridge-client.mjs
|
||||
import { createServiceRegistry } from "@hosts/vscode/hostbridge-grpc-service"
|
||||
import { HostServiceHandlerConfig } from "@hosts/vscode/hostbridge-grpc-handler"
|
||||
|
||||
${imports.join("\n")}
|
||||
${clientImplementations.join("\n\n")}
|
||||
|
||||
/**
|
||||
* Map of host service names to their handler configurations
|
||||
*/
|
||||
export const hostServiceHandlers: Record<string, HostServiceHandlerConfig> = {
|
||||
${handlerMap.join("\n")}
|
||||
}
|
||||
`
|
||||
|
||||
// Write output file
|
||||
await fs.mkdir(path.dirname(VSCODE_CLIENT_FILE), { recursive: true })
|
||||
await fs.writeFile(VSCODE_CLIENT_FILE, content)
|
||||
}
|
||||
|
||||
function generateVscodeClientImplementation(serviceName, serviceDefinition) {
|
||||
// Get the methods from the service definition
|
||||
const name = serviceName.replace(/Service$/, "").toLowerCase()
|
||||
|
||||
const methods = Object.entries(serviceDefinition.service)
|
||||
.map(([methodName, methodDef]) => {
|
||||
// Get fully qualified type names
|
||||
const isStreamingResponse = methodDef.responseStream
|
||||
if (!isStreamingResponse) {
|
||||
return `${name}ServiceRegistry.registerMethod("${methodName}", ${methodName})`
|
||||
} else {
|
||||
return `${name}ServiceRegistry.registerMethod("${methodName}", ${methodName}, { isStreaming: true })`
|
||||
}
|
||||
})
|
||||
.join("\n")
|
||||
|
||||
// Generate the class
|
||||
return `// Setup ${name} service registry
|
||||
const ${name}ServiceRegistry = createServiceRegistry("${name}")
|
||||
${methods}`
|
||||
}
|
||||
|
||||
// Run the main function
|
||||
main().catch((error) => {
|
||||
console.error(chalk.red("Error:"), error)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { StreamingCallbacks } from "@/hosts/host-provider-types"
|
||||
import { HostServiceHandlerConfig, hostServiceHandlers } from "@generated/hosts/vscode/hostbridge-grpc-service-config"
|
||||
import { hostServiceHandlers } from "@generated/hosts/vscode/hostbridge-grpc-service-config"
|
||||
import { GrpcRequestRegistry } from "@core/controller/grpc-request-registry"
|
||||
|
||||
/**
|
||||
@@ -161,6 +161,19 @@ export class GrpcHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for a host service handler
|
||||
*/
|
||||
export interface HostServiceHandlerConfig {
|
||||
requestHandler: (method: string, message: any) => Promise<any>
|
||||
streamingHandler: (
|
||||
method: string,
|
||||
message: any,
|
||||
responseStream: StreamingResponseHandler,
|
||||
requestId?: string,
|
||||
) => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request registry instance
|
||||
* This allows other parts of the code to access the registry
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
EnvServiceClientImpl,
|
||||
WindowServiceClientImpl,
|
||||
DiffServiceClientImpl,
|
||||
} from "@generated/standalone/host-bridge-clients"
|
||||
} from "@generated/hosts/standalone/host-bridge-clients"
|
||||
import {
|
||||
WatchServiceClientInterface,
|
||||
WorkspaceServiceClientInterface,
|
||||
|
||||
Reference in New Issue
Block a user