From edc8563487e90a6d6e3d03f1b157b61346bda118 Mon Sep 17 00:00:00 2001 From: Olafur Geirsson Date: Wed, 21 Aug 2024 08:43:02 +0200 Subject: [PATCH 1/6] WIP --- vscode/src/chat/chat-view/ChatController.ts | 9 +++++++++ vscode/src/extension-client.ts | 2 ++ vscode/src/jsonrpc/agent-protocol.ts | 7 +++++++ 3 files changed, 18 insertions(+) diff --git a/vscode/src/chat/chat-view/ChatController.ts b/vscode/src/chat/chat-view/ChatController.ts index 60d091a48881..7ff53f85a089 100644 --- a/vscode/src/chat/chat-view/ChatController.ts +++ b/vscode/src/chat/chat-view/ChatController.ts @@ -1851,6 +1851,15 @@ export async function addWebviewViewHTML( ? vscode.Uri.parse(config?.rootDir, true) : vscode.Uri.joinPath(extensionUri, 'dist', 'webviews') // Create Webview using vscode/index.html + if ( + extensionClient.capabilities?.uriSchemeLoaders?.includes('webviewasset') && + extensionClient.readUriUTF8 + ) { + const decoded = await extensionClient.readUriUTF8( + vscode.Uri.from({ scheme: 'webviewasset', path: 'index.html' }) + ) + return + } const root = vscode.Uri.joinPath(webviewPath, 'index.html') const bytes = await vscode.workspace.fs.readFile(root) const decoded = new TextDecoder('utf-8').decode(bytes) diff --git a/vscode/src/extension-client.ts b/vscode/src/extension-client.ts index 7c3d8f7cf221..685f7968efb1 100644 --- a/vscode/src/extension-client.ts +++ b/vscode/src/extension-client.ts @@ -38,6 +38,8 @@ export interface ExtensionClient { */ openNewDocument(workspace: typeof vscode.workspace, uri: Uri): Thenable + readUriUTF8?(uri: Uri): Thenable + get clientName(): string get clientVersion(): string get capabilities(): ClientCapabilities | undefined diff --git a/vscode/src/jsonrpc/agent-protocol.ts b/vscode/src/jsonrpc/agent-protocol.ts index 701bff36b881..cefbb6d4125a 100644 --- a/vscode/src/jsonrpc/agent-protocol.ts +++ b/vscode/src/jsonrpc/agent-protocol.ts @@ -450,6 +450,9 @@ export type ServerNotifications = { 'progress/end': [{ id: string }] + 'uri/readBytes': [{ uri: string }, { base64EncodedBytes: string }] + 'uri/readUTF8': [{ uri: string }, { text: string }] + // The list of remote repositories changed. Results from remoteRepo/list // may be stale and should be requeried. 'remoteRepo/didChange': [null] @@ -624,6 +627,9 @@ export interface ClientCapabilities { // which effectively means both sidebar and custom editor chat views are supported. // Defaults to 'agentic'. webview?: 'agentic' | 'native' | undefined | null + + uriSchemeLoaders?: string[] // can be ['webviewasset'] + // If webview === 'native', describes how the client has configured webview resources. // cspSource is passed to the extension as the Webview cspSource property. // webviewBundleServingPrefix is prepended to resource paths under 'dist' in @@ -633,6 +639,7 @@ export interface ClientCapabilities { | { view: 'multiple' | 'single' cspSource: string + assetLoader: 'fs' | 'client' webviewBundleServingPrefix: string rootDir?: string | undefined | null injectScript?: string | undefined | null From 8f3483941d3d158e0c0bd06ada842b584bc29be1 Mon Sep 17 00:00:00 2001 From: jamesmcnamara Date: Tue, 27 Aug 2024 09:39:45 -0700 Subject: [PATCH 2/6] added webview asset reader --- agent/src/agent.ts | 15 ++++ vscode/src/chat/chat-view/ChatController.ts | 80 +++++++++++++++------ vscode/src/jsonrpc/agent-protocol.ts | 14 +++- 3 files changed, 84 insertions(+), 25 deletions(-) diff --git a/agent/src/agent.ts b/agent/src/agent.ts index 878bc080d2cb..ddeedc428e4a 100644 --- a/agent/src/agent.ts +++ b/agent/src/agent.ts @@ -1465,6 +1465,21 @@ export class Agent extends MessageHandler implements ExtensionClient { return result ? vscode_shim.workspace.openTextDocument(result.uri) : undefined } + public async readUriUTF8(uri: vscode.Uri): Promise { + if (this.clientInfo?.capabilities?.uriSchemeLoaders?.includes(uri.scheme)) { + const { text } = await this.request('uri/readUTF8', { + uri: uri.toString(), + }) + + return text + } + const errorMessage = + `Client does not support ${uri.scheme} documents. To fix this problem, add ${uri.scheme} to ` + + `ClientCapabilities.uriSchemeLoaders and implement the ${uri.scheme}:// protocol under uri/readUTF8` + logError('Agent', 'unsupported operation', errorMessage) + throw new Error(errorMessage) + } + private maybeExtension: ExtensionObjects | undefined public async provide(extension: ExtensionObjects): Promise { diff --git a/vscode/src/chat/chat-view/ChatController.ts b/vscode/src/chat/chat-view/ChatController.ts index 764302d4de43..2182601b8d2c 100644 --- a/vscode/src/chat/chat-view/ChatController.ts +++ b/vscode/src/chat/chat-view/ChatController.ts @@ -71,6 +71,7 @@ import type { RemoteRepoPicker } from '../../context/repo-picker' import { resolveContextItems } from '../../editor/utils/editor-context' import type { VSCodeEditor } from '../../editor/vscode-editor' import type { ExtensionClient } from '../../extension-client' +import type { ClientCapabilities } from '../../jsonrpc/agent-protocol' import { ContextStatusAggregator } from '../../local-context/enhanced-context-status' import type { LocalEmbeddingsController } from '../../local-context/local-embeddings' import type { SymfRunner } from '../../local-context/symf' @@ -1848,41 +1849,76 @@ export async function addWebviewViewHTML( return } const config = extensionClient.capabilities?.webviewNativeConfig - const webviewPath = config?.rootDir - ? vscode.Uri.parse(config?.rootDir, true) - : vscode.Uri.joinPath(extensionUri, 'dist', 'webviews') - // Create Webview using vscode/index.html - if ( - extensionClient.capabilities?.uriSchemeLoaders?.includes('webviewasset') && - extensionClient.readUriUTF8 - ) { - const decoded = await extensionClient.readUriUTF8( - vscode.Uri.from({ scheme: 'webviewasset', path: 'index.html' }) - ) - return - } - const root = vscode.Uri.joinPath(webviewPath, 'index.html') - const bytes = await vscode.workspace.fs.readFile(root) - const decoded = new TextDecoder('utf-8').decode(bytes) - const resources = view.webview.asWebviewUri(webviewPath) + const uri = getWebviewUri(extensionUri, extensionClient.capabilities) + logDebug('ChatController', 'addWebviewViewHTML', uri.toString()) + const html = await loadWebviewAsset(extensionClient, uri) + const resources = view.webview.asWebviewUri(uri) + view.webview.html = transformHTML({ html, resources, config, cspSource: view.webview.cspSource }) +} + +interface TransformHTMLOptions { + html: string + resources: vscode.Uri + config?: ClientCapabilities['webviewNativeConfig'] + cspSource: string +} +const transformHTML = ({ html, resources, config, cspSource }: TransformHTMLOptions): string => { // This replace variables from the vscode/dist/index.html with webview info // 1. Update URIs to load styles and scripts into webview (eg. path that starts with ./) // 2. Update URIs for content security policy to only allow specific scripts to be run - view.webview.html = decoded + html = html .replaceAll('./', `${resources.toString()}/`) - .replaceAll("'self'", view.webview.cspSource) - .replaceAll('{cspSource}', view.webview.cspSource) + .replaceAll("'self'", cspSource) + .replaceAll('{cspSource}', cspSource) // If a script or style is injected, replace the placeholder with the script or style // and drop the content-security-policy meta tag which prevents inline scripts and styles if (config?.injectScript || config?.injectStyle) { - // drop all text betweeb <-- START CSP --> and <-- END CSP --> - view.webview.html = decoded + // drop all text between <-- START CSP --> and <-- END CSP --> + html = html .replace(/<-- START CSP -->.*/s, '') .replaceAll('/*injectedScript*/', config?.injectScript ?? '') .replaceAll('/*injectedStyle*/', config?.injectStyle ?? '') } + + return html +} + +const loadWebviewAsset = async (extensionClient: ExtensionClient, uri: vscode.Uri): Promise => { + if ( + extensionClient.capabilities?.uriSchemeLoaders?.includes(uri.scheme) && + extensionClient.readUriUTF8 + ) { + const utf8 = await extensionClient.readUriUTF8(uri) + if (!utf8) { + throw new Error('Failed to load webview asset: ' + uri.toString()) + } + return utf8 + } + + const bytes = await vscode.workspace.fs.readFile(uri) + return new TextDecoder('utf-8').decode(bytes) +} + +const getWebviewUri = (extensionUri: vscode.Uri, capabilities?: ClientCapabilities): vscode.Uri => { + const config = capabilities?.webviewNativeConfig + + // When the client has specified a webview asset loader, and they support webviewasset://, + // we will delegate to the client to handle the URI. + if ( + config?.assetLoader === 'webviewasset' && + capabilities?.uriSchemeLoaders?.includes('webviewasset') + ) { + return vscode.Uri.from({ scheme: 'webviewasset', path: 'index.html' }) + } + // Otherwise, we will use load the webview asset from the file system, either + // from a specfic directory provided by the client, or from the default location. + const webviewPath = config?.rootDir + ? vscode.Uri.parse(config?.rootDir, true) + : vscode.Uri.joinPath(extensionUri, 'dist', 'webviews') + + return vscode.Uri.joinPath(webviewPath, 'index.html') } // This is the manual ordering of the different retrieved and explicit context sources diff --git a/vscode/src/jsonrpc/agent-protocol.ts b/vscode/src/jsonrpc/agent-protocol.ts index cefbb6d4125a..ffabfaf6329a 100644 --- a/vscode/src/jsonrpc/agent-protocol.ts +++ b/vscode/src/jsonrpc/agent-protocol.ts @@ -337,6 +337,8 @@ export type ServerRequests = { ] 'workspace/edit': [WorkspaceEditParams, boolean] + 'uri/readUTF8': [{ uri: string }, { text: string }] + // TODO: Add VSCode support for registerWebviewPanelSerializer. 'env/openExternal': [{ uri: string }, boolean] @@ -450,7 +452,6 @@ export type ServerNotifications = { 'progress/end': [{ id: string }] - 'uri/readBytes': [{ uri: string }, { base64EncodedBytes: string }] 'uri/readUTF8': [{ uri: string }, { text: string }] // The list of remote repositories changed. Results from remoteRepo/list @@ -628,7 +629,9 @@ export interface ClientCapabilities { // Defaults to 'agentic'. webview?: 'agentic' | 'native' | undefined | null - uriSchemeLoaders?: string[] // can be ['webviewasset'] + // Custom URI prefixes that this client supports. If the agent is asked to open a URI + // with one of these prefixes, it will delegate to the client to handle the URI. + uriSchemeLoaders?: string[] | undefined | null // If webview === 'native', describes how the client has configured webview resources. // cspSource is passed to the extension as the Webview cspSource property. @@ -639,7 +642,12 @@ export interface ClientCapabilities { | { view: 'multiple' | 'single' cspSource: string - assetLoader: 'fs' | 'client' + // if assetLoader is 'webviewasset', the client must implement the + // webviewasset:// protocol. The agent will call into the extension client + // to resolve the webview. If assetLoader is 'fs' or undefined, the + // agent will attempt to load the asset from the file system at + // (rootDir ?? codyPaths())/dist/webviews. + assetLoader?: 'fs' | 'webviewasset' | undefined | null webviewBundleServingPrefix: string rootDir?: string | undefined | null injectScript?: string | undefined | null From 5f7f687b53f1cfee0eb7edef00cb8dd57d042bc5 Mon Sep 17 00:00:00 2001 From: jamesmcnamara Date: Tue, 27 Aug 2024 16:02:25 -0700 Subject: [PATCH 3/6] don't relativize paths for webviewassets --- .../protocol_generated/ClientCapabilities.kt | 3 +- .../protocol_generated/CodyAgentClient.kt | 4 +++ .../agent/protocol_generated/Constants.kt | 2 ++ .../protocol_generated/Uri_ReadUTF8Params.kt | 7 ++++ .../protocol_generated/Uri_ReadUTF8Result.kt | 7 ++++ ...ConfigParams.kt => WebviewNativeConfig.kt} | 8 ++++- agent/src/NativeWebview.ts | 11 +++---- vscode/src/chat/chat-view/ChatController.ts | 16 +++++---- vscode/src/jsonrpc/agent-protocol.ts | 33 +++++++++---------- 9 files changed, 59 insertions(+), 32 deletions(-) create mode 100644 agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/Uri_ReadUTF8Params.kt create mode 100644 agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/Uri_ReadUTF8Result.kt rename agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/{WebviewNativeConfigParams.kt => WebviewNativeConfig.kt} (70%) diff --git a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/ClientCapabilities.kt b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/ClientCapabilities.kt index 0961dda77d8c..e9f7c4d6bbd9 100644 --- a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/ClientCapabilities.kt +++ b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/ClientCapabilities.kt @@ -19,7 +19,8 @@ data class ClientCapabilities( val webviewMessages: WebviewMessagesEnum? = null, // Oneof: object-encoded, string-encoded val globalState: GlobalStateEnum? = null, // Oneof: stateless, server-managed, client-managed val webview: WebviewEnum? = null, // Oneof: agentic, native - val webviewNativeConfig: WebviewNativeConfigParams? = null, + val uriSchemeLoaders: List? = null, + val webviewNativeConfig: WebviewNativeConfig? = null, ) { enum class CompletionsEnum { diff --git a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/CodyAgentClient.kt b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/CodyAgentClient.kt index fb4a12950781..1db408be33fc 100644 --- a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/CodyAgentClient.kt +++ b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/CodyAgentClient.kt @@ -22,6 +22,8 @@ interface CodyAgentClient { fun textDocument_show(params: TextDocument_ShowParams): CompletableFuture @JsonRequest("workspace/edit") fun workspace_edit(params: WorkspaceEditParams): CompletableFuture + @JsonRequest("uri/readUTF8") + fun uri_readUTF8(params: Uri_ReadUTF8Params): CompletableFuture @JsonRequest("env/openExternal") fun env_openExternal(params: Env_OpenExternalParams): CompletableFuture @@ -46,6 +48,8 @@ interface CodyAgentClient { fun progress_report(params: ProgressReportParams) @JsonNotification("progress/end") fun progress_end(params: Progress_EndParams) + @JsonNotification("uri/readUTF8") + fun uri_readUTF8(params: Uri_ReadUTF8Params) @JsonNotification("remoteRepo/didChange") fun remoteRepo_didChange(params: Null?) @JsonNotification("remoteRepo/didChangeState") diff --git a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/Constants.kt b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/Constants.kt index 1d7cdda1a9ff..5602f05ec143 100644 --- a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/Constants.kt +++ b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/Constants.kt @@ -41,6 +41,7 @@ object Constants { const val fetching = "fetching" const val file = "file" const val free = "free" + const val fs = "fs" const val function = "function" const val gateway = "gateway" const val history = "history" @@ -89,5 +90,6 @@ object Constants { const val use = "use" const val user = "user" const val warning = "warning" + const val webviewasset = "webviewasset" const val workspace = "workspace" } diff --git a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/Uri_ReadUTF8Params.kt b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/Uri_ReadUTF8Params.kt new file mode 100644 index 000000000000..06d84129f421 --- /dev/null +++ b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/Uri_ReadUTF8Params.kt @@ -0,0 +1,7 @@ +@file:Suppress("FunctionName", "ClassName", "unused", "EnumEntryName", "UnusedImport") +package com.sourcegraph.cody.agent.protocol_generated; + +data class Uri_ReadUTF8Params( + val uri: String, +) + diff --git a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/Uri_ReadUTF8Result.kt b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/Uri_ReadUTF8Result.kt new file mode 100644 index 000000000000..a134135bd771 --- /dev/null +++ b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/Uri_ReadUTF8Result.kt @@ -0,0 +1,7 @@ +@file:Suppress("FunctionName", "ClassName", "unused", "EnumEntryName", "UnusedImport") +package com.sourcegraph.cody.agent.protocol_generated; + +data class Uri_ReadUTF8Result( + val text: String, +) + diff --git a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/WebviewNativeConfigParams.kt b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/WebviewNativeConfig.kt similarity index 70% rename from agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/WebviewNativeConfigParams.kt rename to agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/WebviewNativeConfig.kt index 6b721ac6240f..0ddf093d1663 100644 --- a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/WebviewNativeConfigParams.kt +++ b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/WebviewNativeConfig.kt @@ -3,9 +3,10 @@ package com.sourcegraph.cody.agent.protocol_generated; import com.google.gson.annotations.SerializedName; -data class WebviewNativeConfigParams( +data class WebviewNativeConfig( val view: ViewEnum, // Oneof: multiple, single val cspSource: String, + val assetLoader: AssetLoaderEnum? = null, // Oneof: fs, webviewasset val webviewBundleServingPrefix: String, val rootDir: String? = null, val injectScript: String? = null, @@ -16,5 +17,10 @@ data class WebviewNativeConfigParams( @SerializedName("multiple") Multiple, @SerializedName("single") Single, } + + enum class AssetLoaderEnum { + @SerializedName("fs") Fs, + @SerializedName("webviewasset") Webviewasset, + } } diff --git a/agent/src/NativeWebview.ts b/agent/src/NativeWebview.ts index 92fbd10645ab..7fc0ea974bf8 100644 --- a/agent/src/NativeWebview.ts +++ b/agent/src/NativeWebview.ts @@ -1,18 +1,16 @@ import * as uuid from 'uuid' import * as vscode from 'vscode' import type { Agent } from './agent' -import type { DefiniteWebviewOptions } from './protocol-alias' +import type { DefiniteWebviewOptions, WebviewNativeConfig } from './protocol-alias' import * as vscode_shim from './vscode-shim' -export type NativeWebviewConfig = { cspSource: string; webviewBundleServingPrefix: string } - type NativeWebviewHandle = string /** * A delegate for adapting the VSCode Webview, WebviewPanel and WebviewView API * to a client which has a native webview implementation. */ -interface WebviewProtocolDelegate { +interface WebviewProtocolDelegate extends WebviewNativeConfig { // CSP, resource-related readonly webviewBundleLocalPrefix: vscode.Uri readonly webviewBundleServingPrefix: string @@ -88,15 +86,14 @@ export function resolveWebviewView( export function registerNativeWebviewHandlers( agent: Agent, webviewBundleLocalPrefix: vscode.Uri, - config: NativeWebviewConfig + config: WebviewNativeConfig ): void { webviewProtocolDelegate = { + ...config, // TODO: When we want to serve resources outside dist/, make Agent // include 'dist' in its bundle paths, and simply set this to // extensionUri. webviewBundleLocalPrefix, - webviewBundleServingPrefix: config.webviewBundleServingPrefix, - cspSource: config.cspSource, createWebviewPanel: (handle, viewType, title, showOptions, options) => { agent.notify('webview/createWebviewPanel', { handle, diff --git a/vscode/src/chat/chat-view/ChatController.ts b/vscode/src/chat/chat-view/ChatController.ts index 2182601b8d2c..eda841e6e1b5 100644 --- a/vscode/src/chat/chat-view/ChatController.ts +++ b/vscode/src/chat/chat-view/ChatController.ts @@ -1852,13 +1852,17 @@ export async function addWebviewViewHTML( const uri = getWebviewUri(extensionUri, extensionClient.capabilities) logDebug('ChatController', 'addWebviewViewHTML', uri.toString()) const html = await loadWebviewAsset(extensionClient, uri) - const resources = view.webview.asWebviewUri(uri) + + let resources: vscode.Uri | undefined + if (config?.assetLoader !== 'webviewasset') { + resources = view.webview.asWebviewUri(uri) + } view.webview.html = transformHTML({ html, resources, config, cspSource: view.webview.cspSource }) } interface TransformHTMLOptions { html: string - resources: vscode.Uri + resources?: vscode.Uri config?: ClientCapabilities['webviewNativeConfig'] cspSource: string } @@ -1866,11 +1870,11 @@ interface TransformHTMLOptions { const transformHTML = ({ html, resources, config, cspSource }: TransformHTMLOptions): string => { // This replace variables from the vscode/dist/index.html with webview info // 1. Update URIs to load styles and scripts into webview (eg. path that starts with ./) + if (resources) { + html = html.replaceAll('./', `${resources.toString()}/`) + } // 2. Update URIs for content security policy to only allow specific scripts to be run - html = html - .replaceAll('./', `${resources.toString()}/`) - .replaceAll("'self'", cspSource) - .replaceAll('{cspSource}', cspSource) + html = html.replaceAll("'self'", cspSource).replaceAll('{cspSource}', cspSource) // If a script or style is injected, replace the placeholder with the script or style // and drop the content-security-policy meta tag which prevents inline scripts and styles diff --git a/vscode/src/jsonrpc/agent-protocol.ts b/vscode/src/jsonrpc/agent-protocol.ts index ffabfaf6329a..04f92b847f37 100644 --- a/vscode/src/jsonrpc/agent-protocol.ts +++ b/vscode/src/jsonrpc/agent-protocol.ts @@ -638,23 +638,22 @@ export interface ClientCapabilities { // webviewBundleServingPrefix is prepended to resource paths under 'dist' in // asWebviewUri (note, multiple prefixes are not yet implemented.) // Set the view to 'single' when client only support single chat view, e.g. sidebar chat. - webviewNativeConfig?: - | { - view: 'multiple' | 'single' - cspSource: string - // if assetLoader is 'webviewasset', the client must implement the - // webviewasset:// protocol. The agent will call into the extension client - // to resolve the webview. If assetLoader is 'fs' or undefined, the - // agent will attempt to load the asset from the file system at - // (rootDir ?? codyPaths())/dist/webviews. - assetLoader?: 'fs' | 'webviewasset' | undefined | null - webviewBundleServingPrefix: string - rootDir?: string | undefined | null - injectScript?: string | undefined | null - injectStyle?: string | undefined | null - } - | undefined - | null + webviewNativeConfig?: WebviewNativeConfig | undefined | null +} + +export interface WebviewNativeConfig { + view: 'multiple' | 'single' + cspSource: string + // if assetLoader is 'webviewasset', the client must implement the + // webviewasset:// protocol. The agent will call into the extension client + // to resolve the webview. If assetLoader is 'fs' or undefined, the + // agent will attempt to load the asset from the file system at + // (rootDir ?? codyPaths())/dist/webviews. + assetLoader?: 'fs' | 'webviewasset' | undefined | null + webviewBundleServingPrefix: string + rootDir?: string | undefined | null + injectScript?: string | undefined | null + injectStyle?: string | undefined | null } export interface ServerInfo { From ba815abe39eed34061edd150719d5244dd779c58 Mon Sep 17 00:00:00 2001 From: jamesmcnamara Date: Tue, 27 Aug 2024 16:07:52 -0700 Subject: [PATCH 4/6] fixed protocol --- vscode/src/jsonrpc/agent-protocol.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/vscode/src/jsonrpc/agent-protocol.ts b/vscode/src/jsonrpc/agent-protocol.ts index 04f92b847f37..89bb54ccd924 100644 --- a/vscode/src/jsonrpc/agent-protocol.ts +++ b/vscode/src/jsonrpc/agent-protocol.ts @@ -452,8 +452,6 @@ export type ServerNotifications = { 'progress/end': [{ id: string }] - 'uri/readUTF8': [{ uri: string }, { text: string }] - // The list of remote repositories changed. Results from remoteRepo/list // may be stale and should be requeried. 'remoteRepo/didChange': [null] From 71a652a3ea5443273c6ac6356dd70da972b2d093 Mon Sep 17 00:00:00 2001 From: jamesmcnamara Date: Tue, 27 Aug 2024 16:16:20 -0700 Subject: [PATCH 5/6] regenerated --- .../cody/agent/protocol_generated/CodyAgentClient.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/CodyAgentClient.kt b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/CodyAgentClient.kt index 25b009eff821..9305eca15a65 100644 --- a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/CodyAgentClient.kt +++ b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/CodyAgentClient.kt @@ -54,8 +54,6 @@ interface CodyAgentClient { fun progress_report(params: ProgressReportParams) @JsonNotification("progress/end") fun progress_end(params: Progress_EndParams) - @JsonNotification("uri/readUTF8") - fun uri_readUTF8(params: Uri_ReadUTF8Params) @JsonNotification("remoteRepo/didChange") fun remoteRepo_didChange(params: Null?) @JsonNotification("remoteRepo/didChangeState") From 0e55ef7576dd753984414ca30a44cdfdd6ea06b2 Mon Sep 17 00:00:00 2001 From: jamesmcnamara Date: Wed, 28 Aug 2024 17:43:31 -0700 Subject: [PATCH 6/6] fixed e2e tests --- vscode/src/chat/chat-view/ChatController.ts | 30 +++++++++++---------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/vscode/src/chat/chat-view/ChatController.ts b/vscode/src/chat/chat-view/ChatController.ts index a4c7db1afe38..b8aa7e083a30 100644 --- a/vscode/src/chat/chat-view/ChatController.ts +++ b/vscode/src/chat/chat-view/ChatController.ts @@ -1825,14 +1825,14 @@ export async function addWebviewViewHTML( return } const config = extensionClient.capabilities?.webviewNativeConfig - const uri = getWebviewUri(extensionUri, extensionClient.capabilities) - logDebug('ChatController', 'addWebviewViewHTML', uri.toString()) - const html = await loadWebviewAsset(extensionClient, uri) + const baseUri = getWebviewBase(extensionUri, extensionClient.capabilities) + const html = await loadWebviewIndex(extensionClient, baseUri) let resources: vscode.Uri | undefined if (config?.assetLoader !== 'webviewasset') { - resources = view.webview.asWebviewUri(uri) + resources = view.webview.asWebviewUri(baseUri) } + view.webview.html = transformHTML({ html, resources, config, cspSource: view.webview.cspSource }) } @@ -1865,23 +1865,27 @@ const transformHTML = ({ html, resources, config, cspSource }: TransformHTMLOpti return html } -const loadWebviewAsset = async (extensionClient: ExtensionClient, uri: vscode.Uri): Promise => { +const loadWebviewIndex = async ( + extensionClient: ExtensionClient, + basePath: vscode.Uri +): Promise => { + const index = vscode.Uri.joinPath(basePath, 'index.html') if ( - extensionClient.capabilities?.uriSchemeLoaders?.includes(uri.scheme) && + extensionClient.capabilities?.uriSchemeLoaders?.includes(index.scheme) && extensionClient.readUriUTF8 ) { - const utf8 = await extensionClient.readUriUTF8(uri) + const utf8 = await extensionClient.readUriUTF8(index) if (!utf8) { - throw new Error('Failed to load webview asset: ' + uri.toString()) + throw new Error('Failed to load webview asset: ' + index.toString()) } return utf8 } - const bytes = await vscode.workspace.fs.readFile(uri) + const bytes = await vscode.workspace.fs.readFile(index) return new TextDecoder('utf-8').decode(bytes) } -const getWebviewUri = (extensionUri: vscode.Uri, capabilities?: ClientCapabilities): vscode.Uri => { +const getWebviewBase = (extensionUri: vscode.Uri, capabilities?: ClientCapabilities): vscode.Uri => { const config = capabilities?.webviewNativeConfig // When the client has specified a webview asset loader, and they support webviewasset://, @@ -1890,15 +1894,13 @@ const getWebviewUri = (extensionUri: vscode.Uri, capabilities?: ClientCapabiliti config?.assetLoader === 'webviewasset' && capabilities?.uriSchemeLoaders?.includes('webviewasset') ) { - return vscode.Uri.from({ scheme: 'webviewasset', path: 'index.html' }) + return vscode.Uri.from({ scheme: 'webviewasset', path: '/' }) } // Otherwise, we will use load the webview asset from the file system, either // from a specfic directory provided by the client, or from the default location. - const webviewPath = config?.rootDir + return config?.rootDir ? vscode.Uri.parse(config?.rootDir, true) : vscode.Uri.joinPath(extensionUri, 'dist', 'webviews') - - return vscode.Uri.joinPath(webviewPath, 'index.html') } // This is the manual ordering of the different retrieved and explicit context sources