diff --git a/apps/vs-code-designer/src/app/commands/createNewCodeProject/CodeProjectBase/__test__/CreateLogicAppWorkspace.test.ts b/apps/vs-code-designer/src/app/commands/createNewCodeProject/CodeProjectBase/__test__/CreateLogicAppWorkspace.test.ts index 0a3324598bc..755d76ecef1 100644 --- a/apps/vs-code-designer/src/app/commands/createNewCodeProject/CodeProjectBase/__test__/CreateLogicAppWorkspace.test.ts +++ b/apps/vs-code-designer/src/app/commands/createNewCodeProject/CodeProjectBase/__test__/CreateLogicAppWorkspace.test.ts @@ -112,6 +112,9 @@ describe('CreateLogicAppWorkspace - Codeful Workflows', () => { expect(templateContent).toContain('WorkflowActions.BuiltIn.Agent('); expect(templateContent).toContain('WorkflowFactory.CreateAgentWorkflow(<%= flowName %>, workflow)'); expect(templateContent).toContain('WorkflowActions.Managed.Office365("outlook").SendEmail'); + expect(templateContent).not.toContain('AgentBuilder'); + expect(templateContent).not.toContain('.Builder'); + expect(templateContent).not.toContain('CreateAgentTrigger'); expect(templateContent).not.toContain('WorkflowBuilderFactory.CreateConversationalAgent'); expect(templateContent).not.toContain('AddWorkflow()'); }); @@ -130,6 +133,10 @@ describe('CreateLogicAppWorkspace - Codeful Workflows', () => { expect(templateContent).toContain('WorkflowFactory.CreateStatefulWorkflow(<%= flowName %>, workflow)'); expect(templateContent).toContain('MessageRole.User'); expect(templateContent).toContain('WorkflowActions.Managed.Office365("outlook").SendEmail'); + expect(templateContent).not.toContain('AgentBuilder'); + expect(templateContent).not.toContain('.Builder'); + expect(templateContent).not.toContain('CreateAgentTrigger'); + expect(templateContent).not.toContain('WorkflowBuilderFactory.CreateConversationalAgent'); expect(templateContent).not.toContain('AddWorkflow()'); }); }); @@ -198,7 +205,12 @@ describe('CreateLogicAppWorkspace - Codeful Workflows', () => { expect(vi.mocked(fse.readFile)).toHaveBeenCalledWith(expect.stringContaining('AgentCodefulWorkflow'), 'utf-8'); const workflowWriteCall = vi.mocked(fse.writeFile).mock.calls.find((call: any) => call[0].includes(`${testWorkflowName}.cs`)); expect(workflowWriteCall?.[1]).toContain(`public class ${testWorkflowName} : IWorkflowProvider`); + expect(workflowWriteCall?.[1]).toContain('WorkflowActions.BuiltIn.Agent('); expect(workflowWriteCall?.[1]).toContain(`WorkflowFactory.CreateAgentWorkflow("${testWorkflowName}", workflow)`); + expect(workflowWriteCall?.[1]).not.toContain('AgentBuilder'); + expect(workflowWriteCall?.[1]).not.toContain('.Builder'); + expect(workflowWriteCall?.[1]).not.toContain('CreateAgentTrigger'); + expect(workflowWriteCall?.[1]).not.toContain('WorkflowBuilderFactory.CreateConversationalAgent'); const programWriteCall = vi.mocked(fse.writeFile).mock.calls.find((call: any) => call[0].includes('Program.cs')); expect(programWriteCall?.[1]).toContain(`namespace ${testProjectName}`); @@ -240,7 +252,12 @@ describe('CreateLogicAppWorkspace - Codeful Workflows', () => { expect(vi.mocked(fse.readFile)).toHaveBeenCalledWith(expect.stringContaining('AgenticCodefulWorkflow'), 'utf-8'); const workflowWriteCall = vi.mocked(fse.writeFile).mock.calls.find((call: any) => call[0].includes(`${testWorkflowName}.cs`)); expect(workflowWriteCall?.[1]).toContain(`public class ${testWorkflowName} : IWorkflowProvider`); + expect(workflowWriteCall?.[1]).toContain('WorkflowActions.BuiltIn.Agent('); expect(workflowWriteCall?.[1]).toContain(`WorkflowFactory.CreateStatefulWorkflow("${testWorkflowName}", workflow)`); + expect(workflowWriteCall?.[1]).not.toContain('AgentBuilder'); + expect(workflowWriteCall?.[1]).not.toContain('.Builder'); + expect(workflowWriteCall?.[1]).not.toContain('CreateAgentTrigger'); + expect(workflowWriteCall?.[1]).not.toContain('WorkflowBuilderFactory.CreateConversationalAgent'); const programWriteCall = vi.mocked(fse.writeFile).mock.calls.find((call: any) => call[0].includes('Program.cs')); expect(programWriteCall?.[1]).not.toContain(`${testWorkflowName}.AddWorkflow()`); diff --git a/apps/vs-code-designer/src/app/languageServer/__test__/bundledLspServerCodeLens.test.ts b/apps/vs-code-designer/src/app/languageServer/__test__/bundledLspServerCodeLens.test.ts new file mode 100644 index 00000000000..923de828550 --- /dev/null +++ b/apps/vs-code-designer/src/app/languageServer/__test__/bundledLspServerCodeLens.test.ts @@ -0,0 +1,268 @@ +import AdmZip from 'adm-zip'; +import type { ChildProcessWithoutNullStreams } from 'child_process'; +import { mkdtemp, rm } from 'fs/promises'; +import path from 'path'; +import { pathToFileURL } from 'url'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +interface JsonRpcMessage { + id?: number | string; + method?: string; + params?: unknown; + result?: unknown; + error?: unknown; +} + +interface CodeLensResult { + command?: { + title?: string; + command?: string; + arguments?: unknown[]; + }; +} + +class LspProcess { + private nextId = 1; + private stdoutBuffer = Buffer.alloc(0); + private readonly pending = new Map< + number, + { + resolve: (message: JsonRpcMessage) => void; + reject: (error: Error) => void; + timeout: NodeJS.Timeout; + } + >(); + private readonly stderrChunks: string[] = []; + private disposed = false; + + public constructor(private readonly child: ChildProcessWithoutNullStreams) { + child.stdout.on('data', (chunk: Buffer) => this.handleStdout(chunk)); + child.stderr.on('data', (chunk: Buffer) => this.stderrChunks.push(chunk.toString('utf8'))); + child.on('exit', (code, signal) => { + if (this.disposed) { + return; + } + + const error = new Error(`LSP server exited unexpectedly with code ${code ?? 'null'} signal ${signal ?? 'null'}.\n${this.stderr}`); + for (const pending of this.pending.values()) { + clearTimeout(pending.timeout); + pending.reject(error); + } + this.pending.clear(); + }); + } + + public get stderr(): string { + return this.stderrChunks.join(''); + } + + public async request(method: string, params: unknown): Promise { + const id = this.nextId++; + const responsePromise = new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + this.pending.delete(id); + reject(new Error(`Timed out waiting for ${method} response.\n${this.stderr}`)); + }, 30_000); + + this.pending.set(id, { resolve, reject, timeout }); + }); + + this.write({ jsonrpc: '2.0', id, method, params }); + const response = await responsePromise; + if (response.error) { + throw new Error(`LSP ${method} failed: ${JSON.stringify(response.error)}\n${this.stderr}`); + } + + return response.result as T; + } + + public notify(method: string, params: unknown): void { + this.write({ jsonrpc: '2.0', method, params }); + } + + public async dispose(): Promise { + this.disposed = true; + + try { + await this.request('shutdown', undefined); + this.notify('exit', undefined); + } catch { + // The process is being torn down; a failed shutdown request should not hide the test assertion. + } + + if (!this.child.killed) { + this.child.kill(); + } + } + + private write(message: JsonRpcMessage & { jsonrpc: '2.0' }): void { + const body = JSON.stringify(message); + const header = `Content-Length: ${Buffer.byteLength(body, 'utf8')}\r\n\r\n`; + this.child.stdin.write(header + body, 'utf8'); + } + + private handleStdout(chunk: Buffer): void { + this.stdoutBuffer = Buffer.concat([this.stdoutBuffer, chunk]); + + while (true) { + const headerEnd = this.stdoutBuffer.indexOf('\r\n\r\n'); + if (headerEnd < 0) { + return; + } + + const header = this.stdoutBuffer.subarray(0, headerEnd).toString('ascii'); + const lengthMatch = /Content-Length:\s*(\d+)/i.exec(header); + if (!lengthMatch) { + throw new Error(`Invalid LSP header: ${header}`); + } + + const contentLength = Number(lengthMatch[1]); + const bodyStart = headerEnd + 4; + const bodyEnd = bodyStart + contentLength; + if (this.stdoutBuffer.length < bodyEnd) { + return; + } + + const body = this.stdoutBuffer.subarray(bodyStart, bodyEnd).toString('utf8'); + this.stdoutBuffer = this.stdoutBuffer.subarray(bodyEnd); + const message = JSON.parse(body) as JsonRpcMessage; + if (typeof message.id === 'number') { + const pending = this.pending.get(message.id); + if (pending) { + clearTimeout(pending.timeout); + this.pending.delete(message.id); + pending.resolve(message); + } + } + } + } +} + +describe('bundled LSP server CodeLens', () => { + let tempDirectories: string[] = []; + let lspProcess: LspProcess | undefined; + + afterEach(async () => { + await lspProcess?.dispose(); + lspProcess = undefined; + + await Promise.all(tempDirectories.map((directory) => rm(directory, { recursive: true, force: true }))); + tempDirectories = []; + }); + + it('returns create agent connection CodeLens for current built-in Agent source', async () => { + const agentCodeLens = await getAgentCodeLens(); + + expect(agentCodeLens.command?.title).toBe('Agent - Create agent connection'); + expect(JSON.stringify(agentCodeLens.command?.arguments)).toContain('AgentConnection'); + expect(JSON.stringify(agentCodeLens.command?.arguments)).toContain('agent'); + }, 60_000); + + it('returns manage agent connection CodeLens when the agent connection already exists', async () => { + const agentCodeLens = await getAgentCodeLens({ + connections: { + managedApiConnections: { + agent: { + api: { id: '/providers/Microsoft.Web/locations/westus/apis/agent' }, + connection: { id: '/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/connections/agent' }, + connectionRuntimeUrl: '', + }, + }, + }, + }); + + expect(agentCodeLens.command?.title).toBe('Agent - Manage agent connection'); + expect(JSON.stringify(agentCodeLens.command?.arguments)).toContain('AgentConnection'); + expect(JSON.stringify(agentCodeLens.command?.arguments)).toContain('agent'); + }, 60_000); + + async function getAgentCodeLens(initializationOptions?: Record): Promise { + lspProcess = await startBundledLspServer(initializationOptions); + + const documentUri = pathToFileURL(path.join(await createTempDirectory(), 'AgentWorkflow.cs')).toString(); + lspProcess.notify('textDocument/didOpen', { + textDocument: { + uri: documentUri, + languageId: 'csharp', + version: 1, + text: agentWorkflowSource, + }, + }); + + const codeLenses = await lspProcess.request('textDocument/codeLens', { + textDocument: { uri: documentUri }, + }); + + await lspProcess.dispose(); + lspProcess = undefined; + + const agentCodeLens = codeLenses.find((lens) => lens.command?.title?.includes('agent connection')); + expect(agentCodeLens).toBeDefined(); + + return agentCodeLens as CodeLensResult; + } + + async function startBundledLspServer(initializationOptions?: Record): Promise { + const extractDirectory = await createTempDirectory(); + const zipPath = path.join(process.cwd(), 'src', 'assets', 'LSPServer', 'LSPServer.zip'); + new AdmZip(zipPath).extractAllTo(extractDirectory, true, true); + + const serverDllPath = path.join(extractDirectory, 'SdkLspServer.dll'); + const sdkPackagePath = path.join(process.cwd(), 'src', 'assets', 'LSPServer', 'Microsoft.Azure.Workflows.Sdk.1.0.0-preview.1.nupkg'); + const { spawn: realSpawn } = await vi.importActual('child_process'); + const child = realSpawn('dotnet', [serverDllPath, '--sdk', sdkPackagePath], { + stdio: ['pipe', 'pipe', 'pipe'], + windowsHide: true, + }); + const server = new LspProcess(child); + + await server.request('initialize', { + processId: process.pid, + rootUri: null, + capabilities: { + textDocument: { + codeLens: { + dynamicRegistration: false, + }, + }, + }, + initializationOptions, + }); + server.notify('initialized', {}); + + return server; + } + + async function createTempDirectory(): Promise { + const directory = await mkdtemp(path.join(process.cwd(), 'logicapps-lsp-codelens-')); + tempDirectories.push(directory); + return directory; + } +}); + +const agentWorkflowSource = ` +using Microsoft.Azure.Workflows.Sdk; + +public class AgentWorkflow +{ + public static FlowDefinition[] GetWorkflows() + { + var trigger = WorkflowTriggers.BuiltIn.CreateConversationalAgentTrigger(); + + var agent = WorkflowActions.BuiltIn.Agent( + agentModelType: AgentModelType.AzureOpenAI, + deploymentId: "gpt-4.1", + agentModelSettings: new AgentModelSettings(), + connectionName: "agent", + messages: () => new AgentPromptMessage[] + { + new AgentPromptMessage + { + Role = MessageRole.System, + Content = "You are a weather agent" + } + }).WithName("WeatherAgent"); + + return new[] { WorkflowFactory.CreateAgentWorkflow("AgentWorkflow", trigger.Then(agent)) }; + } +}`; diff --git a/apps/vs-code-designer/src/app/utils/__test__/codeful.test.ts b/apps/vs-code-designer/src/app/utils/__test__/codeful.test.ts index cf26e4da398..9a39caabcb3 100644 --- a/apps/vs-code-designer/src/app/utils/__test__/codeful.test.ts +++ b/apps/vs-code-designer/src/app/utils/__test__/codeful.test.ts @@ -1,7 +1,13 @@ import path from 'path'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { lspDirectory } from '../../../constants'; -import { codefulProjectsExist, invalidateCodefulSdkCacheIfNeeded, parseCsprojCopyToCodefulInfo } from '../codeful'; +import { + codefulProjectsExist, + detectAgentCodefulWorkflow, + detectCodefulWorkflow, + invalidateCodefulSdkCacheIfNeeded, + parseCsprojCopyToCodefulInfo, +} from '../codeful'; const mocks = vi.hoisted(() => ({ ensureDir: vi.fn(), @@ -205,6 +211,71 @@ describe('parseCsprojCopyToCodefulInfo', () => { }); }); +describe('detectAgentCodefulWorkflow', () => { + it('detects a conversational agent workflow that uses the current built-in Agent API', () => { + const workflowName = detectAgentCodefulWorkflow(` +namespace TestProject +{ + using Microsoft.Azure.Workflows.Sdk; + + public class TestWorkflow : IWorkflowProvider + { + public FlowDefinition[] GetWorkflows() + { + var trigger = WorkflowTriggers.BuiltIn.CreateConversationalAgentTrigger(); + var agent = WorkflowActions.BuiltIn.Agent( + agentModelType: AgentModelType.AzureOpenAI, + deploymentId: "gpt-4.1", + messages: () => new AgentPromptMessage[] + { + new AgentPromptMessage { Role = MessageRole.System, Content = "Help the user" } + } + ).WithName("WeatherAgent"); + + var workflow = trigger.Then(agent); + return new[] { WorkflowFactory.CreateAgentWorkflow("TestWorkflow", workflow) }; + } + } +}`); + + expect(workflowName).toBe('TestWorkflow'); + }); + + it('detects new agent workflow source without relying on legacy builder APIs', () => { + const workflow = detectCodefulWorkflow(` +namespace TestProject +{ + using Microsoft.Azure.Workflows.Sdk; + + public class MultilineWorkflow : IWorkflowProvider + { + public FlowDefinition[] GetWorkflows() + { + var trigger = WorkflowTriggers.BuiltIn.CreateConversationalAgentTrigger(); + var agent = + WorkflowActions + .BuiltIn + .Agent( + agentModelType: AgentModelType.AzureOpenAI, + deploymentId: "gpt-4.1", + messages: () => Array.Empty()) + .WithName("WeatherAgent"); + + var workflow = trigger.Then(agent); + return new[] + { + WorkflowFactory.CreateAgentWorkflow( + "MultilineWorkflow", + workflow) + }; + } + } +}`); + + expect(workflow).toEqual({ workflowName: 'MultilineWorkflow', workflowType: 'agent' }); + }); +}); + describe('codefulProjectsExist', () => { const codefulSettingsJson = JSON.stringify({ IsEncrypted: false, diff --git a/apps/vs-code-designer/src/assets/LSPServer/LSPServer.zip b/apps/vs-code-designer/src/assets/LSPServer/LSPServer.zip index 64d3899a41a..f53cb7c0268 100644 Binary files a/apps/vs-code-designer/src/assets/LSPServer/LSPServer.zip and b/apps/vs-code-designer/src/assets/LSPServer/LSPServer.zip differ diff --git a/apps/vs-code-designer/src/test/ui/codefulDebugTasks.test.ts b/apps/vs-code-designer/src/test/ui/codefulDebugTasks.test.ts index fda64831982..6c907be20bb 100644 --- a/apps/vs-code-designer/src/test/ui/codefulDebugTasks.test.ts +++ b/apps/vs-code-designer/src/test/ui/codefulDebugTasks.test.ts @@ -75,7 +75,7 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import { EditorView, VSBrowser, Workbench, type WebDriver } from 'vscode-extension-tester'; -import { captureScreenshot, sleep } from './helpers'; +import { captureScreenshot, dismissQuickPickIfVisible, sleep } from './helpers'; import { openWorkspaceFileInSession, waitForDependencyValidation } from './designerHelpers'; const TEST_TIMEOUT = 1500_000; @@ -116,6 +116,7 @@ interface TaskEvent { /** Recorder phases that prove F5 got past `resolveDebugConfiguration` and reached `executeTask`. */ const TASK_ACTIVITY_PHASES = new Set(['taskStart', 'taskEnd', 'processStart', 'processEnd']); +const CONNECTORS_PROMPT_FRAGMENTS = ['enable connectors in azure', 'use connectors from azure', 'skip for now']; /** * How long `vscode.debug.startDebugging` may sit with neither `debugStarted` nor @@ -268,11 +269,56 @@ interface WaitResult { timedOut: boolean; /** `vscode.debug.startDebugging` was dispatched but never settled — see DEBUG_INVOKE_HANG_TIMEOUT_MS. */ debugStartHung: boolean; + dismissedConnectorsPrompt: boolean; } -async function waitForTaskChain(variant: 'modern' | 'legacy', workspaceScope: string, timeoutMs: number): Promise { +async function dismissConnectorsPromptIfVisible(driver: WebDriver, seenOtherPrompts: Set): Promise { + let widgetText: string; + try { + await driver + .switchTo() + .defaultContent() + .catch(() => undefined); + widgetText = + (await driver.executeScript(` + const widget = document.querySelector('.quick-input-widget:not(.hidden)'); + if (!widget) { return null; } + const input = widget.querySelector('.quick-input-box input'); + const placeholder = input ? (input.getAttribute('placeholder') || '') : ''; + return placeholder + '\\n' + (widget.textContent || ''); + `)) ?? ''; + } catch { + return false; + } + + if (widgetText.trim().length === 0) { + return false; + } + + const normalized = widgetText.toLowerCase(); + if (!CONNECTORS_PROMPT_FRAGMENTS.some((fragment) => normalized.includes(fragment))) { + const summary = widgetText.replace(/\s+/g, ' ').trim().slice(0, 160); + if (!seenOtherPrompts.has(summary)) { + seenOtherPrompts.add(summary); + console.log(`[codefulDebugTasks] A QuickPick is open that is NOT the connectors prompt — leaving it alone: "${summary}"`); + } + return false; + } + + console.log('[codefulDebugTasks] Connectors QuickPick detected during F5 pre-debug path — selecting "Skip for now"'); + return dismissQuickPickIfVisible(driver); +} + +async function waitForTaskChain( + driver: WebDriver, + variant: 'modern' | 'legacy', + workspaceScope: string, + timeoutMs: number +): Promise { const deadline = Date.now() + timeoutMs; const target = normalizeFsPath(workspaceScope); + const seenOtherPrompts = new Set(); + let dismissedConnectorsPrompt = false; while (Date.now() < deadline) { const events = readEvents(); const matchScope = events.filter((e) => normalizeFsPath(e.scopeFsPath) === target); @@ -281,11 +327,15 @@ async function waitForTaskChain(variant: 'modern' | 'legacy', workspaceScope: st const funcHostStarted = matchScope.some((e) => e.phase === 'taskStart' && e.taskName === 'func: host start'); const expectedChainEnded = variant === 'legacy' ? buildEnded && publishEnded && funcHostStarted : buildEnded && funcHostStarted; if (expectedChainEnded) { - return { buildEnded, publishEnded, funcHostStarted, timedOut: false, debugStartHung: false }; + return { buildEnded, publishEnded, funcHostStarted, timedOut: false, debugStartHung: false, dismissedConnectorsPrompt }; } if (events.some((e) => e.phase === 'debugStartFailed')) { console.log('[codefulDebugTasks] waitForTaskChain: debugStartFailed event observed, bailing out'); - return { buildEnded, publishEnded, funcHostStarted, timedOut: true, debugStartHung: false }; + return { buildEnded, publishEnded, funcHostStarted, timedOut: true, debugStartHung: false, dismissedConnectorsPrompt }; + } + + if (await dismissConnectorsPromptIfVisible(driver, seenOtherPrompts)) { + dismissedConnectorsPrompt = true; } // Hang guard: the recorder logged `debugInvoke` (it is inside @@ -312,11 +362,18 @@ async function waitForTaskChain(variant: 'modern' | 'legacy', workspaceScope: st (Date.now() - debugInvokeAt) / 1000 )}s ago with no debugStarted/debugStartFailed and no task activity — treating it as hung` ); - return { buildEnded, publishEnded, funcHostStarted, timedOut: true, debugStartHung: true }; + return { buildEnded, publishEnded, funcHostStarted, timedOut: true, debugStartHung: true, dismissedConnectorsPrompt }; } await sleep(1000); } - return { buildEnded: false, publishEnded: false, funcHostStarted: false, timedOut: true, debugStartHung: false }; + return { + buildEnded: false, + publishEnded: false, + funcHostStarted: false, + timedOut: true, + debugStartHung: false, + dismissedConnectorsPrompt, + }; } async function waitForDesignTimeEvidence(workspaceScope: string, notBeforeMs: number, timeoutMs = 180_000): Promise { @@ -532,7 +589,7 @@ describe('Phase 4.10: Codeful debug F5 task pattern', function () { // precise diagnosis. That is the correct trade: the guard is a diagnostic accelerator, // and buying earlier reporting by shrinking its window is exactly what made it fail // healthy runs. - const wait = await waitForTaskChain(variant, workspaceDir, 720_000); + const wait = await waitForTaskChain(driver, variant, workspaceDir, 720_000); console.log(`[codefulDebugTasks] waitForTaskChain: ${JSON.stringify(wait)}`); // F5 was dispatched but `vscode.debug.startDebugging` never settled and no task diff --git a/apps/vs-code-designer/src/test/ui/run-e2e.ts b/apps/vs-code-designer/src/test/ui/run-e2e.ts index 01dbf336282..27dfaedc137 100644 --- a/apps/vs-code-designer/src/test/ui/run-e2e.ts +++ b/apps/vs-code-designer/src/test/ui/run-e2e.ts @@ -2088,6 +2088,7 @@ async function main(): Promise { const patchGeneratedCodefulProjectForDebugGuard = (entry: CodefulWorkspaceEntry, variant: string): void => { const workflowFile = path.join(entry.appDir, `${entry.wfName}.cs`); const programFile = path.join(entry.appDir, 'Program.cs'); + const nugetConfigFile = path.join(entry.appDir, 'nuget.config'); for (const requiredPath of [workflowFile, programFile]) { if (!fs.existsSync(requiredPath)) { @@ -2153,6 +2154,28 @@ namespace ${namespaceName} } fs.writeFileSync(programFile, patchedProgram, 'utf8'); + const { depsRoot } = getRuntimeDependencyPaths(); + const lspDirectoryPath = path.join(depsRoot, lspDirectory); + const sdkPackageSource = path.join(projectDir, 'src', 'assets', 'LSPServer', 'Microsoft.Azure.Workflows.Sdk.1.0.0-preview.1.nupkg'); + const sdkPackageDestination = path.join(lspDirectoryPath, path.basename(sdkPackageSource)); + if (!fs.existsSync(sdkPackageSource)) { + throw new Error(`Missing SDK package asset required by ${variant} codeful debug project: ${sdkPackageSource}`); + } + fs.mkdirSync(lspDirectoryPath, { recursive: true }); + fs.copyFileSync(sdkPackageSource, sdkPackageDestination); + + if (fs.existsSync(nugetConfigFile)) { + const originalNugetConfig = fs.readFileSync(nugetConfigFile, 'utf8'); + const patchedNugetConfig = originalNugetConfig.replace( + /()/, + `$1"${lspDirectoryPath}"$3` + ); + if (patchedNugetConfig === originalNugetConfig && !originalNugetConfig.includes(lspDirectoryPath)) { + throw new Error(`Could not update current package source in ${nugetConfigFile}`); + } + fs.writeFileSync(nugetConfigFile, patchedNugetConfig, 'utf8'); + } + for (const connectionArtifact of ['connections.json', 'parameters.json']) { const artifactPath = path.join(entry.appDir, connectionArtifact); if (fs.existsSync(artifactPath)) { @@ -2162,6 +2185,7 @@ namespace ${namespaceName} } console.log(` Patched ${variant} generated codeful workflow to built-in HTTP trigger + Response: ${workflowFile}`); + console.log(` Seeded ${variant} codeful SDK package source: ${sdkPackageDestination}`); }; const removeDesignTimeEvidence = async (entry: CodefulWorkspaceEntry, variant: string): Promise => {