diff --git a/core/atx-fes-client/amazon-elastic-gumby-frontend-client-1.0.0.tgz b/core/atx-fes-client/amazon-elastic-gumby-frontend-client-1.0.0.tgz new file mode 100644 index 0000000000..ad8922ed88 Binary files /dev/null and b/core/atx-fes-client/amazon-elastic-gumby-frontend-client-1.0.0.tgz differ diff --git a/package-lock.json b/package-lock.json index 1bb28757e8..73d955c324 100644 --- a/package-lock.json +++ b/package-lock.json @@ -298,92 +298,872 @@ } } }, - "chat-client/node_modules/tsconfig-paths": { - "version": "4.2.0", - "dev": true, + "client/vscode": { + "name": "awsdocuments-ls-client", + "version": "0.1.0", "license": "MIT", - "optional": true, - "peer": true, + "devDependencies": { + "@aws-sdk/credential-providers": "^3.731.1", + "@aws-sdk/types": "^3.734.0", + "@aws/chat-client-ui-types": "^0.1.63", + "@aws/language-server-runtimes": "^0.3.1", + "@types/uuid": "^9.0.8", + "@types/vscode": "^1.98.0", + "jose": "^5.2.4", + "typescript": "^5.5.3", + "vscode-languageclient": "^9.0.1" + }, + "engines": { + "vscode": "^1.74.0" + } + }, + "core/aws-lsp-core": { + "name": "@aws/lsp-core", + "version": "0.0.16", + "license": "Apache-2.0", + "dependencies": { + "@aws/language-server-runtimes": "^0.3.1", + "@gerhobbelt/gitignore-parser": "^0.2.0-9", + "cross-spawn": "7.0.6", + "jose": "^5.2.4", + "request-light": "^0.8.0", + "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.3", + "vscode-uri": "^3.1.0" + }, + "devDependencies": { + "@types/chai": "^4.3.5", + "@types/chai-as-promised": "^7.1.5", + "@types/cross-spawn": "^6.0.2", + "@types/mocha": "^10.0.9", + "@types/mock-fs": "^4.13.1", + "c8": "^10.1.2", + "chai": "^4.3.7", + "chai-as-promised": "^7.1.1", + "mocha": "^11.0.1", + "mock-fs": "^5.2.0", + "sinon": "^19.0.2", + "ts-sinon": "^2.0.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "integration-tests/q-agentic-chat-server": { + "name": "@aws/q-agentic-chat-server-integration-tests", + "version": "0.0.1", + "dependencies": { + "@aws/language-server-runtimes": "^0.3.1", + "@aws/lsp-core": "*" + }, + "devDependencies": { + "@types/chai": "^4.3.5", + "@types/chai-as-promised": "^7.1.5", + "@types/mocha": "^10.0.9", + "@types/yauzl-promise": "^4.0.1", + "chai": "^4.3.7", + "chai-as-promised": "^7.1.1", + "jose": "^5.10.0", + "json-rpc-2.0": "^1.7.1", + "mocha": "^11.0.1", + "rimraf": "^3.0.2", + "typescript": "^5.0.0", + "yauzl-promise": "^4.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client": { + "version": "1.0.0", +<<<<<<< HEAD + "resolved": "file:core/aws-atx-transform/amazon-elastic-gumby-frontend-client-1.0.0.tgz", + "integrity": "sha512-PZ3Zqjalc+SK37maU+/n/XhwmfYp9WT0YIO1PDXbVp2NzP5D4+9h6Et/C1IjF4kc2teGIu7/bV/B1AbHgYq/3w==", +======= + "resolved": "file:server/aws-lsp-codewhisperer/amazon-elastic-gumby-frontend-client-1.0.0.tgz", + "integrity": "sha512-Tlp0+TrF2UdrbAav1aQbkmpW2qCeUnPdRJOOJetn6w6cbdQgsuPd2bzSkdqZok5yXn8UEauq01iZY2lydRG/Mw==", +>>>>>>> 98ef51332d5023e7e594dabe2c85ee4cfa205086 + "bundleDependencies": [ + "@aws-sdk/types", + "@aws-sdk/util-user-agent-browser", + "@aws-sdk/util-user-agent-node", + "@smithy/config-resolver", + "@smithy/core", + "@smithy/fetch-http-handler", + "@smithy/hash-node", + "@smithy/invalid-dependency", + "@smithy/middleware-content-length", + "@smithy/middleware-endpoint", + "@smithy/middleware-retry", + "@smithy/middleware-serde", + "@smithy/middleware-stack", + "@smithy/node-config-provider", + "@smithy/node-http-handler", + "@smithy/protocol-http", + "@smithy/smithy-client", + "@smithy/types", + "@smithy/url-parser", + "@smithy/util-base64", + "@smithy/util-body-length-browser", + "@smithy/util-body-length-node", + "@smithy/util-defaults-mode-browser", + "@smithy/util-defaults-mode-node", + "@smithy/util-endpoints", + "@smithy/util-middleware", + "@smithy/util-retry", + "@smithy/util-utf8", + "tslib" + ], + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.658.1", + "@aws-sdk/middleware-host-header": "3.654.0", + "@aws-sdk/middleware-logger": "3.654.0", + "@aws-sdk/middleware-recursion-detection": "3.654.0", + "@aws-sdk/middleware-user-agent": "3.654.0", + "@aws-sdk/region-config-resolver": "3.654.0", + "@aws-sdk/types": "3.654.0", + "@aws-sdk/util-user-agent-browser": "3.654.0", + "@aws-sdk/util-user-agent-node": "3.654.0", + "@smithy/config-resolver": "^3.0.8", + "@smithy/core": "^2.4.6", + "@smithy/fetch-http-handler": "^3.2.8", + "@smithy/hash-node": "^3.0.6", + "@smithy/invalid-dependency": "^3.0.6", + "@smithy/middleware-content-length": "^3.0.8", + "@smithy/middleware-retry": "^3.0.21", + "@smithy/middleware-serde": "^3.0.6", + "@smithy/middleware-stack": "^3.0.6", + "@smithy/node-config-provider": "^3.1.7", + "@smithy/node-http-handler": "^3.2.3", + "@smithy/protocol-http": "^4.1.3", + "@smithy/smithy-client": "^3.3.5", + "@smithy/types": "^3.4.2", + "@smithy/url-parser": "^3.0.6", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.21", + "@smithy/util-defaults-mode-node": "^3.0.21", + "@smithy/util-middleware": "^3.0.6", + "@smithy/util-retry": "^3.0.6", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@aws-sdk/core": { + "version": "3.658.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.658.1.tgz", + "integrity": "sha512-vJVMoMcSKXK2gBRSu9Ywwv6wQ7tXH8VL1fqB1uVxgCqBZ3IHfqNn4zvpMPWrwgO2/3wv7XFyikGQ5ypPTCw4jA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.4.6", + "@smithy/node-config-provider": "^3.1.7", + "@smithy/property-provider": "^3.1.6", + "@smithy/protocol-http": "^4.1.3", + "@smithy/signature-v4": "^4.1.4", + "@smithy/smithy-client": "^3.3.5", + "@smithy/types": "^3.4.2", + "@smithy/util-middleware": "^3.0.6", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.654.0.tgz", + "integrity": "sha512-rxGgVHWKp8U2ubMv+t+vlIk7QYUaRCHaVpmUlJv0Wv6Q0KeO9a42T9FxHphjOTlCGQOLcjCreL9CF8Qhtb4mdQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/protocol-http": "^4.1.3", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@aws-sdk/middleware-logger": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.654.0.tgz", + "integrity": "sha512-OQYb+nWlmASyXfRb989pwkJ9EVUMP1CrKn2eyTk3usl20JZmKo2Vjis6I0tLUkMSxMhnBJJlQKyWkRpD/u1FVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.654.0.tgz", + "integrity": "sha512-gKSomgltKVmsT8sC6W7CrADZ4GHwX9epk3GcH6QhebVO3LA9LRbkL3TwOPUXakxxOLLUTYdOZLIOtFf7iH00lg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/protocol-http": "^4.1.3", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.654.0.tgz", + "integrity": "sha512-liCcqPAyRsr53cy2tYu4qeH4MMN0eh9g6k56XzI5xd4SghXH5YWh4qOYAlQ8T66ZV4nPMtD8GLtLXGzsH8moFg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@aws-sdk/util-endpoints": "3.654.0", + "@smithy/protocol-http": "^4.1.3", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.654.0.tgz", + "integrity": "sha512-ydGOrXJxj3x0sJhsXyTmvJVLAE0xxuTWFJihTl67RtaO7VRNtd82I3P3bwoMMaDn5WpmV5mPo8fEUDRlBm3fPg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/node-config-provider": "^3.1.7", + "@smithy/types": "^3.4.2", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@aws-sdk/types": { + "version": "3.654.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@aws-sdk/util-endpoints": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.654.0.tgz", + "integrity": "sha512-i902fcBknHs0Irgdpi62+QMvzxE+bczvILXigYrlHL4+PiEnlMVpni5L5W1qCkNZXf8AaMrSBuR1NZAGp6UOUw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/types": "^3.4.2", + "@smithy/util-endpoints": "^2.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.654.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/types": "^3.4.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.654.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/node-config-provider": "^3.1.7", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/abort-controller": { + "version": "3.1.9", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/config-resolver": { + "version": "3.0.13", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.12", + "@smithy/types": "^3.7.2", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/core": { + "version": "2.5.7", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^3.0.11", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-stream": "^3.3.4", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/credential-provider-imds": { + "version": "3.2.8", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.12", + "@smithy/property-provider": "^3.1.11", + "@smithy/types": "^3.7.2", + "@smithy/url-parser": "^3.0.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/fetch-http-handler": { + "version": "3.2.9", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.4", + "@smithy/querystring-builder": "^3.0.7", + "@smithy/types": "^3.5.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/hash-node": { + "version": "3.0.11", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/invalid-dependency": { + "version": "3.0.11", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/middleware-content-length": { + "version": "3.0.13", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/middleware-endpoint": { + "version": "3.2.8", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.5.7", + "@smithy/middleware-serde": "^3.0.11", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/shared-ini-file-loader": "^3.1.12", + "@smithy/types": "^3.7.2", + "@smithy/url-parser": "^3.0.11", + "@smithy/util-middleware": "^3.0.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/middleware-retry": { + "version": "3.0.34", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.12", + "@smithy/protocol-http": "^4.1.8", + "@smithy/service-error-classification": "^3.0.11", + "@smithy/smithy-client": "^3.7.0", + "@smithy/types": "^3.7.2", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-retry": "^3.0.11", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/middleware-serde": { + "version": "3.0.11", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/middleware-stack": { + "version": "3.0.11", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/node-config-provider": { + "version": "3.1.12", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^3.1.11", + "@smithy/shared-ini-file-loader": "^3.1.12", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/node-http-handler": { + "version": "3.3.3", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^3.1.9", + "@smithy/protocol-http": "^4.1.8", + "@smithy/querystring-builder": "^3.0.11", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/property-provider": { + "version": "3.1.11", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/protocol-http": { + "version": "4.1.8", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/querystring-builder": { + "version": "3.0.11", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/querystring-parser": { + "version": "3.0.11", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/service-error-classification": { + "version": "3.0.11", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.12", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/signature-v4": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.4.tgz", + "integrity": "sha512-5JWeMQYg81TgU4cG+OexAWdvDTs5JDdbEZx+Qr1iPbvo91QFGzjy0IkXAKaXUHqmKUJgSHK0ZxnCkgZpzkeNTA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.11", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/smithy-client": { + "version": "3.7.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.5.7", + "@smithy/middleware-endpoint": "^3.2.8", + "@smithy/middleware-stack": "^3.0.11", + "@smithy/protocol-http": "^4.1.8", + "@smithy/types": "^3.7.2", + "@smithy/util-stream": "^3.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/types": { + "version": "3.7.2", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/url-parser": { + "version": "3.0.11", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^3.0.11", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-base64": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-body-length-browser": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-body-length-node": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6" + "node": ">=16.0.0" } }, - "client/vscode": { - "name": "awsdocuments-ls-client", - "version": "0.1.0", - "license": "MIT", - "devDependencies": { - "@aws-sdk/credential-providers": "^3.731.1", - "@aws-sdk/types": "^3.734.0", - "@aws/chat-client-ui-types": "^0.1.63", - "@aws/language-server-runtimes": "^0.3.1", - "@types/uuid": "^9.0.8", - "@types/vscode": "^1.98.0", - "jose": "^5.2.4", - "typescript": "^5.5.3", - "vscode-languageclient": "^9.0.1" + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-config-provider": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" }, "engines": { - "vscode": "^1.74.0" + "node": ">=16.0.0" } }, - "core/aws-lsp-core": { - "name": "@aws/lsp-core", - "version": "0.0.16", + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-defaults-mode-browser": { + "version": "3.0.34", + "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@aws/language-server-runtimes": "^0.3.1", - "@gerhobbelt/gitignore-parser": "^0.2.0-9", - "cross-spawn": "7.0.6", - "jose": "^5.2.4", - "request-light": "^0.8.0", - "vscode-languageserver-textdocument": "^1.0.8", - "vscode-languageserver-types": "^3.17.3", - "vscode-uri": "^3.1.0" + "@smithy/property-provider": "^3.1.11", + "@smithy/smithy-client": "^3.7.0", + "@smithy/types": "^3.7.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" }, - "devDependencies": { - "@types/chai": "^4.3.5", - "@types/chai-as-promised": "^7.1.5", - "@types/cross-spawn": "^6.0.2", - "@types/mocha": "^10.0.9", - "@types/mock-fs": "^4.13.1", - "c8": "^10.1.2", - "chai": "^4.3.7", - "chai-as-promised": "^7.1.1", - "mocha": "^11.0.1", - "mock-fs": "^5.2.0", - "sinon": "^19.0.2", - "ts-sinon": "^2.0.2" + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-defaults-mode-node": { + "version": "3.0.34", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^3.0.13", + "@smithy/credential-provider-imds": "^3.2.8", + "@smithy/node-config-provider": "^3.1.12", + "@smithy/property-provider": "^3.1.11", + "@smithy/smithy-client": "^3.7.0", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">= 10.0.0" } }, - "integration-tests/q-agentic-chat-server": { - "name": "@aws/q-agentic-chat-server-integration-tests", - "version": "0.0.1", + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-endpoints": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.7.tgz", + "integrity": "sha512-tSfcqKcN/Oo2STEYCABVuKgJ76nyyr6skGl9t15hs+YaiU06sgMkN7QYjo0BbVw+KT26zok3IzbdSOksQ4YzVw==", + "inBundle": true, + "license": "Apache-2.0", "dependencies": { - "@aws/language-server-runtimes": "^0.3.1", - "@aws/lsp-core": "*" + "@smithy/node-config-provider": "^3.1.12", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" }, - "devDependencies": { - "@types/chai": "^4.3.5", - "@types/chai-as-promised": "^7.1.5", - "@types/mocha": "^10.0.9", - "@types/yauzl-promise": "^4.0.1", - "chai": "^4.3.7", - "chai-as-promised": "^7.1.1", - "jose": "^5.10.0", - "json-rpc-2.0": "^1.7.1", - "mocha": "^11.0.1", - "rimraf": "^3.0.2", - "typescript": "^5.0.0", - "yauzl-promise": "^4.0.0" + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-middleware": { + "version": "3.0.11", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-retry": { + "version": "3.0.11", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^3.0.11", + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, +<<<<<<< HEAD +======= + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-stream": { + "version": "3.3.4", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^4.1.3", + "@smithy/node-http-handler": "^3.3.3", + "@smithy/types": "^3.7.2", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-stream/node_modules/@smithy/fetch-http-handler": { + "version": "4.1.3", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.8", + "@smithy/querystring-builder": "^3.0.11", + "@smithy/types": "^3.7.2", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, +>>>>>>> 98ef51332d5023e7e594dabe2c85ee4cfa205086 + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/bowser": { + "version": "2.12.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/tslib": { + "version": "2.8.1", + "inBundle": true, + "license": "0BSD" + }, + "node_modules/@amazon/elastic-gumby-frontend-client/node_modules/uuid": { + "version": "9.0.1", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "inBundle": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" } }, "node_modules/@amzn/amazon-q-developer-streaming-client": { @@ -3930,6 +4710,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -4767,6 +5548,7 @@ "url": "https://opencollective.com/csstools" } ], + "peer": true, "engines": { "node": ">=18" }, @@ -4789,6 +5571,7 @@ "url": "https://opencollective.com/csstools" } ], + "peer": true, "engines": { "node": ">=18" } @@ -7073,6 +7856,7 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -9136,6 +9920,7 @@ "version": "22.18.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.8.tgz", "integrity": "sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -9339,6 +10124,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz", "integrity": "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.46.0", @@ -9368,6 +10154,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.0.tgz", "integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.0", "@typescript-eslint/types": "8.46.0", @@ -9819,6 +10606,7 @@ "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-9.17.0.tgz", "integrity": "sha512-i38o7wlipLllNrk2hzdDfAmk6nrqm3lR2MtAgWgtHbwznZAKkB84KpkNFfmUXw5Kg3iP1zKlSjwZpKqenuLc+Q==", "dev": true, + "peer": true, "engines": { "node": ">=18.20.0" }, @@ -9870,6 +10658,7 @@ "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", "dev": true, + "peer": true, "dependencies": { "chalk": "^5.1.2", "loglevel": "^1.6.0", @@ -10669,6 +11458,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "devOptional": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -10729,6 +11519,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -11687,7 +12478,8 @@ "node_modules/bare-events": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.7.0.tgz", - "integrity": "sha512-b3N5eTW1g7vXkw+0CXh/HazGTcO5KYuu/RCNaJbDMPI6LHDi+7qe8EmxKUVe1sUbY2KZOVZFyj62x0OEz9qyAA==" + "integrity": "sha512-b3N5eTW1g7vXkw+0CXh/HazGTcO5KYuu/RCNaJbDMPI6LHDi+7qe8EmxKUVe1sUbY2KZOVZFyj62x0OEz9qyAA==", + "peer": true }, "node_modules/bare-fs": { "version": "4.4.5", @@ -12148,6 +12940,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -12503,6 +13296,7 @@ "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, + "peer": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -13423,6 +14217,7 @@ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -15060,6 +15855,7 @@ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -15671,6 +16467,7 @@ "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.4.3.tgz", "integrity": "sha512-/XxRRR90gNSuNf++w1jOQjhC5LE9Ixf/iAQctVb/miEI3dwzPZTuG27/omoh5REfSLDoPXofM84vAH/ULtz35g==", "dev": true, + "peer": true, "dependencies": { "@vitest/snapshot": "^3.2.4", "deep-eql": "^5.0.2", @@ -18591,6 +19388,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -20636,6 +21434,7 @@ "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.4.tgz", "integrity": "sha512-1jYAaY8x0kAZ0XszLWu14pzsf4KV740Gld4HXkhNTXwcHx4AUEDkPzgEHg9CM5dVcW+zv036tjpsEbLraPJj4w==", "dev": true, + "peer": true, "dependencies": { "browser-stdout": "^1.3.1", "chokidar": "^4.0.1", @@ -22595,6 +23394,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -25690,6 +26490,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -25851,6 +26652,7 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, + "peer": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -25873,7 +26675,8 @@ "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "peer": true }, "node_modules/tsx": { "version": "4.20.6", @@ -26622,6 +27425,7 @@ "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.20.0.tgz", "integrity": "sha512-cqaXfahTzCFaQLlk++feZaze6tAsW8OSdaVRgmOGJRII1z2A4uh4YGHtusTpqOiZAST7OBPqycOwfh01G/Ktbg==", "dev": true, + "peer": true, "dependencies": { "@types/node": "^20.11.30", "@types/sinonjs__fake-timers": "^8.1.5", @@ -26696,6 +27500,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz", "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", "devOptional": true, + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -26744,6 +27549,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", @@ -28103,6 +28909,7 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -28203,6 +29010,11 @@ "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { +<<<<<<< HEAD + "@amazon/elastic-gumby-frontend-client": "file:../../core/aws-atx-transform/amazon-elastic-gumby-frontend-client-1.0.0.tgz", +======= + "@amazon/elastic-gumby-frontend-client": "file:amazon-elastic-gumby-frontend-client-1.0.0.tgz", +>>>>>>> 98ef51332d5023e7e594dabe2c85ee4cfa205086 "@amzn/amazon-q-developer-streaming-client": "file:../../core/q-developer-streaming-client/amzn-amazon-q-developer-streaming-client-1.0.0.tgz", "@amzn/codewhisperer": "file:../../core/codewhisperer/amzn-codewhisperer-1.0.0.tgz", "@amzn/codewhisperer-runtime": "file:../../core/codewhisperer-runtime/amzn-codewhisperer-runtime-1.0.0.tgz", @@ -28363,6 +29175,7 @@ "server/aws-lsp-codewhisperer/node_modules/picomatch": { "version": "4.0.3", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -28391,21 +29204,6 @@ } } }, - "server/aws-lsp-codewhisperer/node_modules/tsconfig-paths": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "server/aws-lsp-identity": { "name": "@aws/lsp-identity", "version": "0.0.1", @@ -28920,21 +29718,6 @@ } } }, - "server/aws-lsp-identity/node_modules/tsconfig-paths": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "server/aws-lsp-json": { "name": "@aws/lsp-json", "version": "0.1.21", @@ -28975,22 +29758,6 @@ } } }, - "server/aws-lsp-json/node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "server/aws-lsp-notification": { "name": "@aws/lsp-notification", "version": "0.0.1", @@ -29053,21 +29820,6 @@ } } }, - "server/aws-lsp-notification/node_modules/tsconfig-paths": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "server/aws-lsp-partiql": { "name": "@aws/lsp-partiql", "version": "0.0.18", @@ -29211,22 +29963,6 @@ "optional": true } } - }, - "server/hello-world-lsp/node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } } } } diff --git a/server/aws-lsp-codewhisperer/package.json b/server/aws-lsp-codewhisperer/package.json index 0f48aa1bfe..b4b9c594d4 100644 --- a/server/aws-lsp-codewhisperer/package.json +++ b/server/aws-lsp-codewhisperer/package.json @@ -29,6 +29,7 @@ "postinstall": "node ./script/install_transitive_dep.js" }, "dependencies": { + "@amazon/elastic-gumby-frontend-client": "file:../../core/aws-gumby-client/amazon-elastic-gumby-frontend-client-1.0.0.tgz", "@amzn/amazon-q-developer-streaming-client": "file:../../core/q-developer-streaming-client/amzn-amazon-q-developer-streaming-client-1.0.0.tgz", "@amzn/codewhisperer": "file:../../core/codewhisperer/amzn-codewhisperer-1.0.0.tgz", "@amzn/codewhisperer-runtime": "file:../../core/codewhisperer-runtime/amzn-codewhisperer-runtime-1.0.0.tgz", diff --git a/server/aws-lsp-codewhisperer/src/language-server/configuration/qConfigurationServer.ts b/server/aws-lsp-codewhisperer/src/language-server/configuration/qConfigurationServer.ts index 7eca4c4233..0a70726d3d 100644 --- a/server/aws-lsp-codewhisperer/src/language-server/configuration/qConfigurationServer.ts +++ b/server/aws-lsp-codewhisperer/src/language-server/configuration/qConfigurationServer.ts @@ -206,6 +206,7 @@ export class ServerConfigurationProvider { const profiles = await this.listAllAvailableProfilesHandler({ connectionType: this.credentialsProvider.getConnectionType(), logging: this.logging, + credentialsProvider: this.credentialsProvider, token: token, }) diff --git a/server/aws-lsp-codewhisperer/src/language-server/netTransform/models.ts b/server/aws-lsp-codewhisperer/src/language-server/netTransform/models.ts index 6b8eead1e4..49801912c0 100644 --- a/server/aws-lsp-codewhisperer/src/language-server/netTransform/models.ts +++ b/server/aws-lsp-codewhisperer/src/language-server/netTransform/models.ts @@ -26,6 +26,7 @@ export interface StartTransformRequest extends ExecuteCommandParams { PackageReferences?: PackageReferenceMetadata[] DmsArn?: string DatabaseSettings?: DatabaseSettings + WorkspaceId?: string } export interface StartTransformResponse { @@ -75,6 +76,26 @@ export interface DownloadArtifactsRequest extends ExecuteCommandParams { SolutionRootPath: string } +export interface GetEditablePlanRequest extends ExecuteCommandParams { + TransformationJobId: string + SolutionRootPath: string +} + +export interface GetEditablePlanResponse { + Status: boolean + PlanPath: string + ReportPath: string +} + +export interface UploadEditablePlanRequest extends ExecuteCommandParams { + TransformationJobId: string + PlanPath: string +} + +export interface UploadEditablePlanResponse { + VerificationStatus: boolean +} + export enum CancellationJobStatus { NOT_STARTED, IN_PROGRESS, diff --git a/server/aws-lsp-codewhisperer/src/language-server/netTransform/netTransformServer.ts b/server/aws-lsp-codewhisperer/src/language-server/netTransform/netTransformServer.ts index 2b3571576c..c4bb4ba909 100644 --- a/server/aws-lsp-codewhisperer/src/language-server/netTransform/netTransformServer.ts +++ b/server/aws-lsp-codewhisperer/src/language-server/netTransform/netTransformServer.ts @@ -28,10 +28,13 @@ import { GetTransformPlanRequest, GetTransformRequest, StartTransformRequest, + GetEditablePlanRequest, + UploadEditablePlanRequest, } from './models' import { TransformHandler } from './transformHandler' export const validStatesForGettingPlan = ['COMPLETED', 'PARTIALLY_COMPLETED', 'PLANNED', 'TRANSFORMING', 'TRANSFORMED'] +export const validStatesForAssessment = ['Planning', 'AWAITING_HUMAN_INPUT'] export const validStatesForComplete = ['COMPLETED'] export const failureStates = ['FAILED', 'STOPPING', 'STOPPED', 'REJECTED'] const StartTransformCommand = 'aws/qNetTransform/startTransform' @@ -42,6 +45,11 @@ const GetTransformPlanCommand = 'aws/qNetTransform/getTransformPlan' const CancelTransformCommand = 'aws/qNetTransform/cancelTransform' const DownloadArtifactsCommand = 'aws/qNetTransform/downloadArtifacts' const CancelPollingCommand = 'aws/qNetTransform/cancelPolling' +const ListWorkspacesCommand = 'aws/qNetTransform/listWorkspaces' +const CreateWorkspaceCommand = 'aws/qNetTransform/createWorkspace' +const GetEditablePlanCommand = 'aws/qNetTransform/getEditablePlan' +const UploadEditablePlanCommand = 'aws/qNetTransform/uploadEditablePlan' +const PollTransformForAssessmentCommand = 'aws/qNetTransform/pollTransformForAssessment' import { SDKInitializator } from '@aws/language-server-runtimes/server-interface' import { AmazonQTokenServiceManager } from '../../shared/amazonQServiceManager/AmazonQTokenServiceManager' @@ -123,6 +131,61 @@ export const QNetTransformServerToken = await transformHandler.cancelPollingAsync() emitCancelPollingTelemetry(telemetry) } + case ListWorkspacesCommand: { + logging.log('LSP: Received ListWorkspacesCommand request') + const workspaces = await transformHandler.listWorkspaces() + logging.log(`LSP: ListWorkspaces returned ${workspaces?.length || 0} workspaces`) + return { workspaces } + } + case CreateWorkspaceCommand: { + logging.log('LSP: Received CreateWorkspaceCommand request') + const request = params.arguments?.[0] as { workspaceName?: string } + const workspaceName = request?.workspaceName || null // Let backend generate default name + logging.log(`LSP: Creating workspace with name: ${workspaceName || 'auto-generated'}`) + + try { + const workspaceId = await transformHandler.createWorkspace(workspaceName) + if (workspaceId) { + logging.log(`LSP: CreateWorkspace returned workspaceId: ${workspaceId}`) + return { workspaceId } + } else { + logging.error('LSP: CreateWorkspace returned null - workspace creation failed') + throw new Error('Failed to create workspace - API returned null') + } + } catch (error) { + logging.error( + `LSP: CreateWorkspace error: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + throw new Error( + `Workspace creation failed: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + } + } + case GetEditablePlanCommand: { + logging.log('LSP: Received GetEditablePlanCommand request') + const request = params as GetEditablePlanRequest + const response = await transformHandler.getEditablePlan(request) + + return response + } + case UploadEditablePlanCommand: { + logging.log('LSP: Received UploadEditablePlanCommand request') + const request = params as UploadEditablePlanRequest + const response = await transformHandler.uploadEditablePlan(request) + + return response + } + case PollTransformForAssessmentCommand: { + logging.log('LSP: Received PollTransform For Assessment request') + const request = params as GetTransformRequest + + const response = await transformHandler.pollTransformation( + request, + validStatesForAssessment, + failureStates + ) + return response + } } return } catch (e: any) { @@ -193,6 +256,10 @@ export const QNetTransformServerToken = CancelTransformCommand, DownloadArtifactsCommand, CancelPollingCommand, + ListWorkspacesCommand, + CreateWorkspaceCommand, + GetEditablePlanCommand, + UploadEditablePlanCommand, ], }, }, diff --git a/server/aws-lsp-codewhisperer/src/language-server/netTransform/transformHandler.ts b/server/aws-lsp-codewhisperer/src/language-server/netTransform/transformHandler.ts index 72c4f9fba1..28a9df985a 100644 --- a/server/aws-lsp-codewhisperer/src/language-server/netTransform/transformHandler.ts +++ b/server/aws-lsp-codewhisperer/src/language-server/netTransform/transformHandler.ts @@ -1,8 +1,35 @@ import { ExportIntent } from '@amzn/codewhisperer-streaming' import { Logging, Runtime, Workspace } from '@aws/language-server-runtimes/server-interface' import * as fs from 'fs' +import * as archiver from 'archiver' import got from 'got' import { v4 as uuidv4 } from 'uuid' +import { + ElasticGumbyFrontendClient, + GetHitlTaskCommand, + UpdateHitlTaskResponse, +} from '@amazon/elastic-gumby-frontend-client' +import { + VerifySessionCommand, + ListWorkspacesCommand, + CreateWorkspaceCommand, + CreateJobCommand, + StartJobCommand, + GetJobCommand, + StopJobCommand, + ListJobPlanStepsCommand, + CreateArtifactUploadUrlCommand, + CreateArtifactDownloadUrlCommand, + CompleteArtifactUploadCommand, + ListAvailableProfilesCommand, + ListArtifactsCommand, + CategoryType, + ListHitlTasksCommand, + SubmitStandardHitlTaskCommand, + UpdateHitlTaskCommand, + FileType, + ListWorklogsCommand, +} from '@amazon/elastic-gumby-frontend-client' import { CreateUploadUrlResponse, GetTransformationRequest, @@ -24,20 +51,49 @@ import { StartTransformResponse, TransformProjectMetadata, PollTransformationStatus, + TransformationErrorCode, + GetEditablePlanRequest, + GetEditablePlanResponse, + UploadEditablePlanRequest, + UploadEditablePlanResponse, } from './models' import * as validation from './validation' import path = require('path') import AdmZip = require('adm-zip') import { AmazonQTokenServiceManager } from '../../shared/amazonQServiceManager/AmazonQTokenServiceManager' +import { DEFAULT_ATX_FES_REGION, DEFAULT_ATX_FES_ENDPOINT } from '../../shared/constants' +import * as https from 'https' +import { URL } from 'url' const workspaceFolderName = 'artifactWorkspace' +/** + * Generates a UUID for idempotency tokens + */ +function generateUUID(): string { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0 + const v = c == 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) +} + export class TransformHandler { private serviceManager: AmazonQTokenServiceManager private workspace: Workspace private logging: Logging private runtime: Runtime private cancelPollingEnabled: Boolean = false + private logATXFESResponse(apiName: string, response: any): void { + this.logging.log(`ATX FES ${apiName} response received`) + } + + private currentWorkspaceId: string | null = null + private cachedApplicationUrl: string | null = null + private fesClient: any = null + private atxClient: ElasticGumbyFrontendClient | null = null + private cachedHitlId: string | null = null + private solutionRootPath: string | null = null constructor(serviceManager: AmazonQTokenServiceManager, workspace: Workspace, logging: Logging, runtime: Runtime) { this.serviceManager = serviceManager @@ -46,7 +102,96 @@ export class TransformHandler { this.runtime = runtime } + /** + * Ensure ATX client is initialized with proper authentication + */ + private async ensureATXClient(): Promise { + if (this.atxClient) { + return true + } + + try { + const endpoint = process.env.TCP_ENDPOINT || DEFAULT_ATX_FES_ENDPOINT + + this.logging.log(`ATX client initializing with endpoint: ${endpoint}`) + + this.atxClient = new ElasticGumbyFrontendClient({ + region: DEFAULT_ATX_FES_REGION, + endpoint: endpoint, + }) + + this.logging.log('ATX client initialized successfully') + return true + } catch (error) { + this.logging.error( + `Failed to initialize ATX client: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + return false + } + } + + /** + * Add bearer token and Origin header to command via middleware + */ + private async addBearerTokenToCommand(command: any): Promise { + const credentialsProvider = (this.serviceManager as any).features?.credentialsProvider + if (!credentialsProvider) return + + const credentials = await credentialsProvider.getCredentials('bearer') + if (!credentials?.token) return + + const applicationUrl = await this.getActiveTransformProfileApplicationUrl() + const cleanOrigin = applicationUrl + ? applicationUrl.endsWith('/') + ? applicationUrl.slice(0, -1) + : applicationUrl + : '' + + command.middlewareStack?.add( + (next: any) => async (args: any) => { + if (!args.request.headers) { + args.request.headers = {} + } + args.request.headers['Authorization'] = `Bearer ${credentials.token}` + if (cleanOrigin) { + args.request.headers['Origin'] = cleanOrigin + } + + try { + if (cleanOrigin) { + const tenantMatch = cleanOrigin.match(/https:\/\/([^.]+)\./) + if (tenantMatch) { + args.request.headers['x-tenant-id'] = tenantMatch[1] + } + } + + const profileArn = await this.getActiveTransformProfileArn() + if (profileArn) { + args.request.headers['x-amzn-qt-profileArn'] = profileArn + + const accountMatch = profileArn.match(/arn:aws:transform:[^:]+:([^:]+):/) + if (accountMatch) { + args.request.headers['x-amzn-qt-accountId'] = accountMatch[1] + } + } + } catch (error) { + this.logging.log( + `Warning: Could not extract tenant/profile headers: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + } + + args.request.headers['Content-Type'] = 'application/json; charset=UTF-8' + args.request.headers['Content-Encoding'] = 'amz-1.0' + return next(args) + }, + { step: 'build', name: 'addBearerTokenAndOriginAndHeaders' } + ) + } + async startTransformation(userInputrequest: StartTransformRequest): Promise { + // Store SolutionRootPath globally + this.solutionRootPath = userInputrequest.SolutionRootPath + var unsupportedProjects: string[] = [] const isProject = validation.isProject(userInputrequest) const containsUnsupportedViews = await validation.checkForUnsupportedViews( @@ -55,6 +200,27 @@ export class TransformHandler { this.logging ) + // Check if current profile is Transform profile + const activeProfileArn = this.serviceManager.getActiveProfileArn() + const isTransformProfile = activeProfileArn?.includes(':transform:') ?? false + + this.logging.log(`Active profile: ${activeProfileArn}`) + this.logging.log(`Is Transform profile: ${isTransformProfile}`) + + if (isTransformProfile) { + this.logging.log('=== Using ATX FES Flow for Transform Profile ===') + return await this.startTransformationATXFES(userInputrequest, unsupportedProjects, containsUnsupportedViews) + } else { + this.logging.log('=== Using RTS Flow for Q Developer Profile ===') + return await this.startTransformationRTS(userInputrequest, unsupportedProjects, containsUnsupportedViews) + } + } + + private async startTransformationRTS( + userInputrequest: StartTransformRequest, + unsupportedProjects: string[], + containsUnsupportedViews: boolean + ): Promise { const artifactManager = new ArtifactManager( this.workspace, this.logging, @@ -88,405 +254,2857 @@ export class TransformHandler { this.logging.log(errorMessage) throw new Error(errorMessage) } finally { - const env = this.runtime.getConfiguration('RUNENV') ?? '' - if (env.toUpperCase() != 'TEST') { - artifactManager.cleanup() + artifactManager.cleanup() + } + } + + private async startTransformationATXFES( + userInputrequest: StartTransformRequest, + unsupportedProjects: string[], + containsUnsupportedViews: boolean + ): Promise { + this.logging.log('=== ATX FES Transformation Flow ===') + + try { + const artifactManager = new ArtifactManager( + this.workspace, + this.logging, + this.getWorkspacePath(userInputrequest.SolutionRootPath), + userInputrequest.SolutionRootPath + ) + const payloadFilePath = await this.zipCodeAsync(userInputrequest, artifactManager) + this.logging.log('Payload path: ' + payloadFilePath) + + const applicationUrl = await this.getActiveTransformProfileApplicationUrl() + if (!applicationUrl) { + throw new Error('Could not get applicationUrl for active Transform profile') + } + + this.logging.log('Getting or creating ATX FES workspace...') + this.logging.log(`Requested workspace ID: ${userInputrequest.WorkspaceId || 'none specified'}`) + const workspaceResult = await this.getOrCreateWorkspace(applicationUrl, userInputrequest.WorkspaceId) + if (!workspaceResult) { + throw new Error('Failed to create ATX FES workspace') + } + + this.currentWorkspaceId = workspaceResult.workspaceId + this.logging.log(`ATX FES Workspace created successfully: ${workspaceResult.workspaceId}`) + + this.logging.log('Creating ATX FES job with DOTNET_IDE...') + const jobResult = await this.createJobFESClient(workspaceResult.workspaceId) + if (!jobResult) { + throw new Error('Failed to create ATX FES job') + } + + this.logging.log(`ATX FES Job created successfully: ${jobResult.jobId}`) + + this.logging.log('Creating ATX FES artifact upload URL...') + const uploadResult = await this.createArtifactUploadUrlFESClient( + workspaceResult.workspaceId, + jobResult.jobId, + payloadFilePath + ) + if (!uploadResult) { + throw new Error('Failed to create ATX FES artifact upload URL') + } + + this.logging.log(`ATX FES Upload URL created successfully: ${uploadResult.uploadId}`) + + this.logging.log('Uploading artifact to S3...') + const uploadSuccess = await this.uploadArtifactToS3ATX( + payloadFilePath, + uploadResult.uploadUrl, + uploadResult.requestHeaders + ) + if (!uploadSuccess) { + throw new Error('Failed to upload artifact to S3') + } + + this.logging.log('ATX FES S3 upload completed successfully') + + this.logging.log('Completing ATX FES artifact upload...') + const completeResult = await this.completeArtifactUploadFESClient( + workspaceResult.workspaceId, + jobResult.jobId, + uploadResult.uploadId + ) + if (!completeResult) { + throw new Error('Failed to complete ATX FES artifact upload') + } + + this.logging.log('ATX FES artifact upload completed successfully') + + this.logging.log('Starting ATX FES transformation job...') + const startResult = await this.startJobFESClient(workspaceResult.workspaceId, jobResult.jobId) + if (!startResult) { + throw new Error('Failed to start ATX FES transformation job') } + + this.logging.log('ATX FES transformation job started successfully') + + // TODO: Implement polling APIs to replace RTS calls + + this.logging.log('=== ATX FES CreateJob SUCCESS ===') + this.logging.log(`Job ID: ${jobResult.jobId}`) + this.logging.log(`Job Status: ${jobResult.status}`) + + return { + TransformationJobId: jobResult.jobId, + UploadId: uploadResult.uploadId, + ArtifactPath: '', + UnSupportedProjects: unsupportedProjects, + ContainsUnsupportedViews: containsUnsupportedViews, + } as StartTransformResponse + } catch (error) { + this.logging.error( + `ATX FES transformation failed: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + + // Return error response + throw new Error( + `ATX FES transformation failed: ${error instanceof Error ? error.message : 'Unknown error'}` + ) } } - async preTransformationUploadCode(payloadFilePath: string): Promise { + /** + * Verifies session via ATX VerifySession API using official client + * No bearer token needed per Smithy model @authenticate(strategy: "NONE") + */ + private async verifyATXFESSession(applicationUrl: string): Promise<{ userId: string } | null> { try { - const uploadId = await this.uploadPayloadAsync(payloadFilePath) - this.logging.log('Artifact was successfully uploaded. Upload tracking id: ' + uploadId) - return uploadId + this.logging.log(`=== ATX VerifySession Operation (Official Client) ===`) + + if (!(await this.ensureATXClient())) { + this.logging.error('VerifySession: Failed to initialize ATX client') + return null + } + + // VerifySession needs both Origin header and bearer token (despite Smithy model) + const command = new VerifySessionCommand({}) + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse('VerifySession', result) + + this.logging.log(`VerifySession: SUCCESS - Session verified`) + return { userId: result.userId || 'verified' } } catch (error) { - const errorMessage = (error as Error).message ?? 'Failed to upload zip file' - throw new Error(errorMessage) + this.logging.error(`Error in VerifySession: ${error instanceof Error ? error.message : 'Unknown error'}`) + + // Log detailed error information + if (error && typeof error === 'object') { + const errorObj = error as any + this.logging.error( + `VerifySession error details: ${JSON.stringify({ + name: errorObj.name, + message: errorObj.message, + code: errorObj.code, + statusCode: errorObj.$metadata?.httpStatusCode, + requestId: errorObj.$metadata?.requestId, + })}` + ) + } + + return null } } - async uploadPayloadAsync(payloadFileName: string): Promise { - const sha256 = await ArtifactManager.getSha256Async(payloadFileName) - let response: CreateUploadUrlResponse + /** + * Gets a specific workspace by ID or creates a new one if none specified + */ + private async getOrCreateWorkspace( + applicationUrl: string, + selectedWorkspaceId?: string + ): Promise<{ workspaceId: string; name: string } | null> { try { - response = await this.serviceManager.getCodewhispererService().codeModernizerCreateUploadUrl({ - contentChecksum: sha256, - contentChecksumType: 'SHA_256', - uploadIntent: 'TRANSFORMATION', + this.logging.log(`=== ATX GetOrCreateWorkspace Operation ===`) + this.logging.log(`Selected workspace ID: ${selectedWorkspaceId || 'none - will use first available'}`) + + if (!(await this.ensureATXClient())) { + this.logging.error('GetOrCreateWorkspace: Failed to initialize ATX client') + return null + } + + // First verify session to establish tenant mapping (required for all ATX operations) + this.logging.log('GetOrCreateWorkspace: Calling verifySession first to establish tenant mapping...') + const sessionResult = await this.verifyATXFESSession(applicationUrl) + + if (!sessionResult) { + this.logging.error('GetOrCreateWorkspace: VerifySession failed - cannot establish tenant mapping') + return null + } + + this.logging.log('GetOrCreateWorkspace: Session verified, proceeding with workspace operations...') + + // If a specific workspace was selected, use it + if (selectedWorkspaceId) { + this.logging.log(`Using selected workspace: ${selectedWorkspaceId}`) + // For now, we'll assume the workspace ID is valid + // In a full implementation, we might want to validate it exists + return { workspaceId: selectedWorkspaceId, name: selectedWorkspaceId } + } + + // No specific workspace selected - list existing workspaces + this.logging.log('No workspace selected, checking for existing workspaces...') + const listCommand = new ListWorkspacesCommand({ maxResults: 10 }) + await this.addBearerTokenToCommand(listCommand) + const listResult = await this.atxClient!.send(listCommand) + this.logATXFESResponse('ListWorkspaces', listResult) + + if (listResult.items && listResult.items.length > 0) { + // Use the first existing workspace as fallback + const workspace = listResult.items[0] + this.logging.log(`Using first available workspace: ${workspace.name} (${workspace.id})`) + return { workspaceId: workspace.id!, name: workspace.name! } + } + + // No existing workspaces, create a new one + this.logging.log('No existing workspaces found, creating new workspace...') + const createCommand = new CreateWorkspaceCommand({ + name: `Transform-Workspace-${Date.now()}`, + description: 'Workspace for .NET Framework to .NET 8.0 transformation', + idempotencyToken: this.generateUUID(), }) - } catch (e: any) { - const errorMessage = (e as Error).message ?? 'Error in CreateUploadUrl API call' - this.logging.log('Error when creating upload url: ' + errorMessage) - throw new Error(errorMessage) + await this.addBearerTokenToCommand(createCommand) + const createResult = await this.atxClient!.send(createCommand) + this.logATXFESResponse('CreateWorkspace', createResult) + + if (createResult.workspace) { + this.logging.log( + `CreateWorkspace: SUCCESS - Workspace created with ID: ${createResult.workspace.id}, Name: ${createResult.workspace.name}` + ) + return { workspaceId: createResult.workspace.id!, name: createResult.workspace.name! } + } else { + this.logging.error(`CreateWorkspace: No workspace in response`) + return null + } + } catch (error) { + this.logging.error( + `Error in GetOrCreateWorkspace: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + return null } + } + /** + * Creates a new workspace via manual HTTP (FES client is broken) + */ + private async createWorkspaceManualHTTP( + applicationUrl: string + ): Promise<{ workspaceId: string; name: string } | null> { try { - await this.uploadArtifactToS3Async(payloadFileName, response, sha256) - } catch (e: any) { - const errorMessage = (e as Error).message ?? 'Error in uploadArtifactToS3 call' - this.logging.log('Error when calling uploadArtifactToS3Async: ' + errorMessage) - throw new Error(errorMessage) + this.logging.log(`=== ATX FES CreateWorkspace Operation (Manual HTTP) ===`) + this.logging.log(`Using applicationUrl: ${applicationUrl}`) + + // Get credentials provider from service manager + const credentialsProvider = (this.serviceManager as any).features?.credentialsProvider + if (!credentialsProvider || !credentialsProvider.hasCredentials('bearer')) { + this.logging.log(`CreateWorkspace: No bearer token credentials available`) + return null + } + + // Get bearer token from credentials provider + const credentials = await credentialsProvider.getCredentials('bearer') + if (!credentials || !credentials.token) { + this.logging.log(`CreateWorkspace: Failed to get bearer token`) + return null + } + + const bearerToken = credentials.token + this.logging.log(`CreateWorkspace: Got bearer token, making API call`) + + // ATX FES endpoint + const endpoint = process.env.TCP_ENDPOINT || DEFAULT_ATX_FES_ENDPOINT + const url = new URL(endpoint) + + // Generate request body + const requestBody = JSON.stringify({ + name: `Transform-Workspace-${Date.now()}`, + description: 'Workspace for .NET Framework to .NET 8.0 transformation', + idempotencyToken: this.generateUUID(), + }) + + // Clean up applicationUrl for Origin header + const cleanApplicationUrl = applicationUrl.endsWith('/') ? applicationUrl.slice(0, -1) : applicationUrl + + // Prepare ATX FES headers for CreateWorkspace + const headers = { + 'Content-Type': 'application/json; charset=UTF-8', + 'Content-Encoding': 'amz-1.0', + 'X-Amz-Target': 'com.amazon.elasticgumbyfrontendservice.ElasticGumbyFrontEndService.CreateWorkspace', + Authorization: `Bearer ${bearerToken}`, + Origin: cleanApplicationUrl, + 'Content-Length': Buffer.byteLength(requestBody).toString(), + } + + const path = `/workspaces` + this.logging.log(`CreateWorkspace: Making request to ${endpoint}${path}`) + this.logging.log(`CreateWorkspace: Request body: ${requestBody}`) + + // Make the ATX FES CreateWorkspace API call + const https = await import('https') + const response = await new Promise<{ statusCode: number; statusMessage: string; data: string }>( + (resolve, reject) => { + const req = https.request( + { + hostname: url.hostname, + port: url.port || 443, + path: path, + method: 'POST', + headers: headers, + }, + res => { + let data = '' + res.on('data', chunk => { + data += chunk + }) + res.on('end', () => { + resolve({ + statusCode: res.statusCode || 0, + statusMessage: res.statusMessage || '', + data: data, + }) + }) + } + ) + + req.on('error', error => { + reject(error) + }) + + req.write(requestBody) + req.end() + } + ) + + this.logging.log(`CreateWorkspace: Response status: ${response.statusCode} ${response.statusMessage}`) + this.logging.log(`CreateWorkspace: Raw response: ${response.data}`) + + if (response.statusCode < 200 || response.statusCode >= 300) { + this.logging.error(`CreateWorkspace: API call failed: ${response.statusCode} ${response.statusMessage}`) + this.logging.error(`CreateWorkspace: Error response: ${response.data}`) + return null + } + + const data = JSON.parse(response.data) + this.logging.log(`CreateWorkspace: Parsed response: ${JSON.stringify(data)}`) + + const workspaceId = data.workspace?.id || data.workspaceId + const name = data.workspace?.name || data.name + + if (workspaceId) { + this.logging.log(`CreateWorkspace: SUCCESS - Workspace created with ID: ${workspaceId}, Name: ${name}`) + return { workspaceId, name } + } else { + this.logging.error(`CreateWorkspace: No workspaceId in response`) + return null + } + } catch (error) { + this.logging.error(`Error in CreateWorkspace: ${error instanceof Error ? error.message : 'Unknown error'}`) + return null } - return response.uploadId } - async zipCodeAsync(request: StartTransformRequest, artifactManager: ArtifactManager): Promise { + private generateUUID(): string { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0 + const v = c == 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) + } + private async createATXFESWorkspace(applicationUrl: string): Promise<{ workspaceId: string; name: string } | null> { try { - return await artifactManager.createZip(request) - } catch (e: any) { - this.logging.log('Error creating zip: ' + e) - throw e + this.logging.log(`=== ATX FES CreateWorkspace Operation (FES Client) ===`) + this.logging.log(`Using applicationUrl: ${applicationUrl}`) + + if (!(await this.ensureATXClient())) { + this.logging.error('CreateWorkspace: Failed to initialize FES client') + return null + } + + // Call FES client createWorkspace method + this.logging.log( + `CreateWorkspace: Calling FES client with request: ${JSON.stringify({ + name: `Transform-Workspace-${Date.now()}`, + description: 'Workspace for .NET Framework to .NET 8.0 transformation', + idempotencyToken: generateUUID(), + })}` + ) + + const result = await this.fesClient.createWorkspace({ + name: `Transform-Workspace-${Date.now()}`, + description: 'Workspace for .NET Framework to .NET 8.0 transformation', + idempotencyToken: generateUUID(), + }) + + this.logging.log(`CreateWorkspace: FES client response received`) + + if (result && result.success && result.data) { + const workspaceId = result.data.workspace?.id || result.data.workspaceId + const name = result.data.workspace?.name || result.data.name + + if (workspaceId) { + this.logging.log( + `CreateWorkspace: SUCCESS - Workspace created with ID: ${workspaceId}, Name: ${name}` + ) + return { workspaceId, name } + } else { + this.logging.error( + `CreateWorkspace: No workspaceId found in response - API may not be returning workspace ID` + ) + this.logging.log(`CreateWorkspace: Available fields: ${Object.keys(result.data || {}).join(', ')}`) + return null + } + } else { + this.logging.error(`CreateWorkspace: Workspace creation failed`) + return null + } + } catch (error) { + this.logging.error(`Error in CreateWorkspace: ${error instanceof Error ? error.message : 'Unknown error'}`) + return null } } - async uploadArtifactToS3Async(fileName: string, resp: CreateUploadUrlResponse, sha256: string) { - const headersObj = this.getHeadersObj(sha256, resp.kmsKeyArn) + /** + * Uploads artifact to S3 using ATX FES pre-signed URL and headers + */ + private async uploadArtifactToS3ATX( + filePath: string, + s3PreSignedUrl: string, + requestHeaders: any + ): Promise { try { - const fileStream = fs.createReadStream(fileName) - const response = await got.put(resp.uploadUrl ?? 'invalid-url', { + this.logging.log('=== ATX FES S3 Upload Operation ===') + this.logging.log(`Uploading file: ${filePath}`) + this.logging.log(`S3 URL: ${s3PreSignedUrl}`) + + // Prepare headers for S3 upload + const headers: any = {} + + // Add required headers from ATX FES response + if (requestHeaders) { + Object.keys(requestHeaders).forEach(key => { + const value = requestHeaders[key] + // Handle array values (take first element) + headers[key] = Array.isArray(value) ? value[0] : value + }) + } + + this.logging.log(`S3 Upload headers: ${JSON.stringify(Object.keys(headers))}`) + + // Create file stream + const fileStream = fs.createReadStream(filePath) + + // Upload to S3 using PUT request + const response = await got.put(s3PreSignedUrl, { body: fileStream, - headers: headersObj, + headers: headers, + timeout: { request: 300000 }, // 5 minutes timeout + retry: { limit: 0 }, }) - this.logging.log(`CodeTransform: Response from S3 Upload = ${response.statusCode}`) - } catch (e: any) { - const error = e as Error - const errorMessage = `error: ${error.message || 'Error in S3 UploadZip API call'}, please see https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/troubleshooting-code-transformation.html#project-upload-fail` - this.logging.log(errorMessage) - throw new Error(errorMessage) - } - } + this.logging.log(`S3 Upload: Response status: ${response.statusCode} ${response.statusMessage}`) - getHeadersObj(sha256: string, kmsKeyArn: string | undefined) { - let headersObj = {} - if (kmsKeyArn === undefined || kmsKeyArn.length === 0) { - headersObj = { - 'x-amz-checksum-sha256': sha256, - 'Content-Type': 'application/zip', + if (response.statusCode === 200) { + this.logging.log('S3 Upload: SUCCESS - File uploaded successfully') + return true + } else { + this.logging.error(`S3 Upload: Failed with status: ${response.statusCode}`) + return false } - } else { - headersObj = { - 'x-amz-checksum-sha256': sha256, - 'Content-Type': 'application/zip', - 'x-amz-server-side-encryption': 'aws:kms', - 'x-amz-server-side-encryption-aws-kms-key-id': kmsKeyArn, + } catch (error) { + this.logging.error(`Error in S3 upload: ${error instanceof Error ? error.message : 'Unknown error'}`) + + // Log detailed error information + if (error && typeof error === 'object') { + const errorObj = error as any + if (errorObj.response) { + this.logging.error(`S3 Upload: Error response status: ${errorObj.response.statusCode}`) + this.logging.error(`S3 Upload: Error response body: ${errorObj.response.body}`) + } } + + return false } - return headersObj } + /** - * Retrieves the status and details of a transformation job. - * Includes error code when the job has failed. - * - * @param request - The request containing the transformation job ID - * @returns The transformation job details with error code if applicable, or null if the request fails + * Starts the transformation job in ATX FES using FES TypeScript client */ - async getTransformation(request: GetTransformRequest) { + private async startJob(workspaceId: string, jobId: string): Promise { + return await this.startJobFESClient(workspaceId, jobId) + } + + /** + * Stops the transformation job in ATX FES using FES TypeScript client + */ + private async stopJobATXFES(workspaceId: string, jobId: string): Promise { try { - const getCodeTransformationRequest = { - transformationJobId: request.TransformationJobId, - } as GetTransformationRequest - const response = await this.serviceManager - .getCodewhispererService() - .codeModernizerGetCodeTransformation(getCodeTransformationRequest) - this.logging.log('Transformation status: ' + response.transformationJob?.status) + this.logging.log('=== ATX FES StopJob Operation (FES Client) ===') + this.logging.log(`Stopping job: ${jobId} in workspace: ${workspaceId}`) - // Use validation function to determine the error code - const errorCode = validation.getTransformationErrorCode(response.transformationJob) + if (!(await this.ensureATXClient())) { + this.logging.error('StopJob: Failed to initialize FES client') + return false + } - return { - TransformationJob: response.transformationJob, - ErrorCode: errorCode, - } as GetTransformResponse - } catch (e: any) { - const errorMessage = (e as Error).message ?? 'Error in GetTransformation API call' - this.logging.log('Error: ' + errorMessage) - return null + // Call FES client stopJob method + const command = new StopJobCommand({ + workspaceId: workspaceId, + jobId: jobId, + }) + + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse('StopJob', result) + + this.logging.log(`StopJob: FES client response: ${JSON.stringify(result)}`) + this.logging.log(`StopJob: SUCCESS - Job stop request submitted`) + return true + } catch (error) { + this.logging.error(`Error in StopJob: ${error instanceof Error ? error.message : 'Unknown error'}`) + return false } } - async getTransformationPlan(request: GetTransformPlanRequest) { - let getTransformationPlanAttempt = 0 - let getTransformationPlanMaxAttempts = 3 - while (true) { - try { - const getCodeTransformationPlanRequest = { - transformationJobId: request.TransformationJobId, - } as GetTransformationRequest - const response = await this.serviceManager - .getCodewhispererService() - .codeModernizerGetCodeTransformationPlan(getCodeTransformationPlanRequest) + + /** + * Creates an artifact download URL using ATX FES API + */ + private async createArtifactDownloadUrlATXFES( + workspaceId: string, + jobId: string, + artifactId: string + ): Promise<{ downloadUrl: string; requestHeaders?: any } | null> { + try { + this.logging.log('=== ATX FES CreateArtifactDownloadUrl Operation ===') + this.logging.log(`Creating download URL for artifact: ${artifactId} in job: ${jobId}`) + + // Get applicationUrl for this Transform profile + const applicationUrl = await this.getActiveTransformProfileApplicationUrl() + if (!applicationUrl) { + this.logging.error('CreateArtifactDownloadUrl: No applicationUrl found for active Transform profile') + return null + } + + // Get credentials for the active profile + const credentialsProvider = (this.serviceManager as any).features?.credentialsProvider + if (!credentialsProvider) { + this.logging.error('CreateArtifactDownloadUrl: No credentials provider available') + return null + } + + const credentials = await credentialsProvider.getCredentials('bearer') + if (!credentials || !credentials.token) { + this.logging.error('CreateArtifactDownloadUrl: No bearer token available') + return null + } + + const bearerToken = credentials.token + this.logging.log(`CreateArtifactDownloadUrl: Got bearer token, making API call`) + + // ATX FES endpoint + const endpoint = DEFAULT_ATX_FES_ENDPOINT + + // Generate request body per Smithy model + const requestBody = JSON.stringify({ + workspaceId: workspaceId, + jobId: jobId, + artifactId: artifactId, + }) + + // Clean applicationUrl for Origin header + const cleanApplicationUrl = applicationUrl.endsWith('/') ? applicationUrl.slice(0, -1) : applicationUrl + + // Prepare ATX FES headers for CreateArtifactDownloadUrl + const headers = { + 'Content-Type': 'application/json; charset=UTF-8', + 'Content-Encoding': 'amz-1.0', + 'X-Amz-Target': + 'com.amazon.elasticgumbyfrontendservice.ElasticGumbyFrontEndService.CreateArtifactDownloadUrl', + Authorization: `Bearer ${bearerToken}`, + Origin: cleanApplicationUrl, + 'Content-Length': Buffer.byteLength(requestBody).toString(), + } + + const path = `/workspaces/${workspaceId}/jobs/${jobId}/artifacts/${artifactId}/download` + this.logging.log(`CreateArtifactDownloadUrl: Making request to ${endpoint}${path}`) + this.logging.log(`CreateArtifactDownloadUrl: Request body: ${requestBody}`) + + // Make the ATX FES CreateArtifactDownloadUrl API call + const response = await got.post(`${endpoint}${path}`, { + body: requestBody, + headers: headers, + timeout: { request: 60000 }, + retry: { limit: 0 }, + }) + + this.logging.log( + `CreateArtifactDownloadUrl: Response status: ${response.statusCode} ${response.statusMessage}` + ) + this.logging.log(`CreateArtifactDownloadUrl: Raw response: ${response.body}`) + + if (response.statusCode !== 200) { + this.logging.error( + `CreateArtifactDownloadUrl: API call failed: ${response.statusCode} ${response.statusMessage}` + ) + this.logging.error(`CreateArtifactDownloadUrl: Error response body: ${response.body}`) + return null + } + + // Parse response + const data = JSON.parse(response.body) + this.logging.log(`CreateArtifactDownloadUrl: Parsed response: ${JSON.stringify(data)}`) + + if (data.downloadUrl) { + this.logging.log(`CreateArtifactDownloadUrl: SUCCESS - Download URL created`) return { - TransformationPlan: response.transformationPlan, - } as GetTransformPlanResponse - } catch (e: any) { - const errorMessage = (e as Error).message ?? 'Error in GetTransformationPlan API call' - this.logging.log('Error: ' + errorMessage) + downloadUrl: data.downloadUrl, + requestHeaders: data.requestHeaders, + } + } else { + this.logging.error(`CreateArtifactDownloadUrl: No downloadUrl in response`) + return null + } + } catch (error) { + this.logging.error( + `Error in CreateArtifactDownloadUrl: ${error instanceof Error ? error.message : 'Unknown error'}` + ) - getTransformationPlanAttempt += 1 - if (getTransformationPlanAttempt >= getTransformationPlanMaxAttempts) { - this.logging.log(`GetTransformationPlan failed after ${getTransformationPlanMaxAttempts} attempts.`) - throw e + // Log detailed error information + if (error && typeof error === 'object') { + const errorObj = error as any + if (errorObj.response) { + this.logging.error( + `CreateArtifactDownloadUrl: Error response status: ${errorObj.response.statusCode}` + ) + this.logging.error(`CreateArtifactDownloadUrl: Error response body: ${errorObj.response.body}`) } + } - const expDelayMs = this.getExpDelayForApiRetryMs(getTransformationPlanAttempt) - this.logging.log( - `Attempt ${getTransformationPlanAttempt}/${getTransformationPlanMaxAttempts} to get transformation plan failed, retry in ${expDelayMs} seconds` - ) - await this.sleep(expDelayMs * 1000) + return null + } + } + + /** + * Gets job status from ATX FES using FES TypeScript client (replaces RTS getTransformation) + */ + private async getJobATXFES(workspaceId: string, jobId: string): Promise { + try { + this.logging.log('=== ATX FES GetJob Operation (FES Client) ===') + this.logging.log(`Getting job status: ${jobId} in workspace: ${workspaceId}`) + + if (!(await this.ensureATXClient())) { + this.logging.error('GetJob: Failed to initialize FES client') + return null + } + + // Call FES client getJob method + const result = await this.fesClient.getJob({ + workspaceId: workspaceId, + jobId: jobId, + includeObjective: true, + }) + + this.logging.log(`GetJob: FES client response: ${JSON.stringify(result)}`) + + if (result && result.success && result.data && result.data.job) { + const job = result.data.job + const status = job.statusDetails?.status || 'Unknown' + this.logging.log(`GetJob: SUCCESS - Job status: ${status}`) + return job + } else { + this.logging.error(`GetJob: No job in response`) + return null } + } catch (error) { + this.logging.error(`Error in GetJob: ${error instanceof Error ? error.message : 'Unknown error'}`) + return null } } - async cancelTransformation(request: CancelTransformRequest) { - let cancelTransformationAttempt = 0 - let cancelTransformationMaxAttempts = 3 - while (true) { - try { - const stopCodeTransformationRequest = { - transformationJobId: request.TransformationJobId, - } as StopTransformationRequest - this.logging.log( - 'Sending CancelTransformRequest with job Id: ' + stopCodeTransformationRequest.transformationJobId + /** + * Gets the applicationUrl for the active Transform profile + */ + private async getActiveTransformProfileApplicationUrl(): Promise { + try { + // Return cached URL if available (avoids expensive profile discovery) + // if (this.cachedApplicationUrl) { + // this.logging.log(`Using cached applicationUrl: ${this.cachedApplicationUrl}`) + // return this.cachedApplicationUrl + // } + + this.logging.log('Getting applicationUrl for active Transform profile...') + + // Get all available profiles (this includes ATX FES profiles with applicationUrl) + const credentialsProvider = (this.serviceManager as any).features?.credentialsProvider + const logging = this.logging + + if (!credentialsProvider) { + this.logging.log('No credentials provider available') + return null + } + + // Import the profile handler + const { getListAllAvailableProfilesHandler } = await import( + '../../shared/amazonQServiceManager/qDeveloperProfiles' + ) + const handler = getListAllAvailableProfilesHandler((region: string, endpoint: string) => { + return this.serviceManager.getServiceFactory()(region, endpoint) + }) + + // Get all profiles including ATX FES profiles + const profiles = await handler({ + connectionType: credentialsProvider.getConnectionType(), + logging: logging, + credentialsProvider: credentialsProvider, + token: { isCancellationRequested: false } as any, + }) + + // Get active profile ARN + const activeProfileArn = this.serviceManager.getActiveProfileArn() + this.logging.log(`Looking for applicationUrl for profile: ${activeProfileArn}`) + + // Log profile scopes for debugging + profiles.forEach(profile => { + this.logging.log(`Profile: ${profile.name} (${profile.arn})`) + this.logging.log(` Profile type: ${profile.arn.includes(':transform:') ? 'Transform' : 'Q Developer'}`) + if (profile.applicationUrl) { + this.logging.log(` ApplicationUrl: ${profile.applicationUrl}`) + } + }) + + // Find the exact matching profile with applicationUrl + const matchingProfile = profiles.find(p => p.arn === activeProfileArn && p.applicationUrl) + + if (matchingProfile && matchingProfile.applicationUrl) { + this.logging.log(`Found applicationUrl: ${matchingProfile.applicationUrl}`) + + // Cache the applicationUrl for future calls + this.cachedApplicationUrl = matchingProfile.applicationUrl + return matchingProfile.applicationUrl + } else { + // Check if profile exists but without applicationUrl + const profileExists = profiles.find(p => p.arn === activeProfileArn) + if (profileExists) { + this.logging.error(`Transform profile ${activeProfileArn} exists but has no applicationUrl`) + this.logging.error('This profile may not be compatible with ATX FES operations') + } else { + this.logging.error(`Transform profile ${activeProfileArn} not found in available profiles`) + } + + // List available Transform profiles with applicationUrl for debugging + const availableTransformProfiles = profiles.filter( + p => p.arn?.includes(':transform:') && p.applicationUrl ) - const response = await this.serviceManager - .getCodewhispererService() - .codeModernizerStopCodeTransformation(stopCodeTransformationRequest) - this.logging.log('Transformation status: ' + response.transformationStatus) - let status: CancellationJobStatus - switch (response.transformationStatus) { - case 'STOPPED': - status = CancellationJobStatus.SUCCESSFULLY_CANCELLED - break - default: - status = CancellationJobStatus.FAILED_TO_CANCEL - break + if (availableTransformProfiles.length > 0) { + this.logging.log('Available Transform profiles with applicationUrl:') + availableTransformProfiles.forEach(p => { + this.logging.log(` - ${p.name} (${p.arn})`) + }) } + + return null + } + } catch (error) { + this.logging.error( + `Error getting applicationUrl: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + return null + } + } + + /** + * Gets the ARN for the active Transform profile + */ + private async getActiveTransformProfileArn(): Promise { + try { + // Get active profile ARN directly from service manager + const activeProfileArn = this.serviceManager.getActiveProfileArn() + + // Verify it's a Transform profile + if (activeProfileArn && activeProfileArn.includes(':transform:')) { + return activeProfileArn + } + + this.logging.log(`Active profile ${activeProfileArn} is not a Transform profile`) + return null + } catch (error) { + this.logging.error( + `Error getting active Transform profile ARN: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + return null + } + } + + /** + * Refreshes the bearer token when it expires (403 errors) + */ + private async refreshBearerToken(): Promise { + try { + this.logging.log('=== Token Refresh Operation ===') + this.logging.log('Requesting new bearer token from credentials provider...') + + // Log current active profile for debugging + try { + const activeProfileArn = this.serviceManager.getActiveProfileArn() + this.logging.log(`Token refresh: Current active profile ARN: ${activeProfileArn}`) + } catch (e) { + this.logging.log(`Token refresh: Could not get active profile ARN: ${e}`) + } + + // Get credentials provider from service manager + const credentialsProvider = (this.serviceManager as any).features?.credentialsProvider + if (!credentialsProvider) { + this.logging.error('Token refresh: No credentials provider available') + return false + } + + // Request fresh credentials (this should trigger token refresh in the IDE) + const newCredentials = await credentialsProvider.getCredentials('bearer') + if (!newCredentials || !newCredentials.token) { + this.logging.error('Token refresh: Failed to get new bearer token') + return false + } + + this.logging.log('Token refresh: SUCCESS - Got new bearer token') + this.logging.log(`Token refresh: New token length: ${newCredentials.token.length}`) + this.logging.log( + `Token refresh: New token preview: ${newCredentials.token.substring(0, 20)}...${newCredentials.token.substring(newCredentials.token.length - 20)}` + ) + + return true + } catch (error) { + this.logging.error(`Token refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`) + return false + } + } + + /** + * Maps ATX FES job statuses to RTS-compatible statuses + */ + private mapATXStatusToRTS(atxStatus: string): string { + switch (atxStatus) { + case 'CREATED': + return 'CREATED' + case 'STARTING': + case 'ASSESSING': + case 'PLANNING': + case 'PLANNED': + case 'EXECUTING': + case 'AWAITING_HUMAN_INPUT': + return 'IN_PROGRESS' + case 'COMPLETED': + return 'COMPLETED' + case 'FAILED': + return 'FAILED' + case 'STOPPED': + case 'STOPPING': + return 'STOPPED' + default: + this.logging.log(`Unknown ATX FES status: ${atxStatus}, mapping to IN_PROGRESS`) + return 'IN_PROGRESS' + } + } + + /** + * Get job status from ATX FES GetJob API with complete workflow handling + */ + private async getATXFESJobStatus( + jobId: string + ): Promise<{ status: string; createdAt?: string; originalStatus: string } | null> { + try { + this.logging.log('=== ATX FES GetJob Operation (FES Client) ===') + this.logging.log(`Getting status for job: ${jobId}`) + + const credentialsProvider = (this.serviceManager as any).features?.credentialsProvider + if (!credentialsProvider?.hasCredentials('bearer')) { + this.logging.error('GetJob: No bearer token credentials available') + return null + } + + const credentials = await credentialsProvider.getCredentials('bearer') + if (!credentials?.token) { + this.logging.error('GetJob: Failed to get bearer token') + return null + } + + // Get workspace ID from cached data + const workspaceId = this.currentWorkspaceId || 'default-workspace' + + const result = await this.getJobFESClient(workspaceId, jobId) + if (result) { + const atxStatus = result.job?.statusDetails?.status + this.logging.log(`GetJob: ATX FES Status: ${atxStatus}`) + + // Handle ATX FES workflow based on status + await this.handleATXFESWorkflow(workspaceId, jobId, atxStatus) + + // Map to RTS status for compatibility + const mappedStatus = this.mapATXStatusToRTS(atxStatus) + this.logging.log(`GetJob: Mapped to RTS Status: ${mappedStatus}`) + return { - TransformationJobStatus: status, - } as CancelTransformResponse - } catch (e: any) { - const errorMessage = (e as Error).message ?? 'Error in CancelTransformation API call' - this.logging.log('Error: ' + errorMessage) + status: mappedStatus, + createdAt: result.job?.creationTime || result.creationTime, + originalStatus: atxStatus, + } + } else { + this.logging.error('GetJob: Failed to get job status') + return null + } + } catch (error) { + this.logging.error(`Error in GetJob: ${error instanceof Error ? error.message : 'Unknown error'}`) + return null + } + } - cancelTransformationAttempt += 1 - if (cancelTransformationAttempt >= cancelTransformationMaxAttempts) { - this.logging.log(`CancelTransformation failed after ${cancelTransformationMaxAttempts} attempts.`) - return { - TransformationJobStatus: CancellationJobStatus.FAILED_TO_CANCEL, - } as CancelTransformResponse + /** + * Handle ATX FES workflow based on job status + */ + private async handleATXFESWorkflow(workspaceId: string, jobId: string, status: string): Promise { + try { + switch (status) { + case 'PLANNED': + // Job plan is ready - fetch the transformation steps + await this.handlePlannedStatus(workspaceId, jobId) + break + + case 'COMPLETED': + // Job completed - download artifacts + await this.handleCompletedStatus(workspaceId, jobId) + break + + case 'FAILED': + // Job failed - log details + this.logging.error(`ATX FES Job ${jobId} failed`) + break + + default: + // Other statuses (CREATED, STARTING, ASSESSING, PLANNING, EXECUTING) - just log + this.logging.log(`ATX FES Job ${jobId} status: ${status}`) + break + } + } catch (error) { + this.logging.error( + `Error handling ATX FES workflow for status ${status}: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + } + } + + /** + * Handle PLANNED status - fetch job plan steps + */ + private async handlePlannedStatus(workspaceId: string, jobId: string): Promise { + try { + this.logging.log(`ATX FES Job ${jobId} reached PLANNED status - fetching plan steps...`) + + const steps = await this.listJobPlanStepsFESClient(workspaceId, jobId) + if (steps && steps.length > 0) { + this.logging.log(`ATX FES Job ${jobId} - Found ${steps.length} transformation steps:`) + steps.forEach((step, index) => { + this.logging.log(` Step ${index + 1}: ${step.stepName} (${step.status || 'Unknown status'})`) + }) + } else { + this.logging.log(`ATX FES Job ${jobId} - No transformation steps found`) + } + } catch (error) { + this.logging.error( + `Error fetching plan steps for job ${jobId}: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + } + } + + /** + * Handle COMPLETED status - job completed, ready for download + */ + private async handleCompletedStatus(workspaceId: string, jobId: string): Promise { + try { + this.logging.log(`ATX FES Job ${jobId} completed - transformation finished successfully`) + this.logging.log(`ATX FES Job ${jobId} - Artifacts are ready for download when IDE requests them`) + + // Note: Actual download will happen when IDE calls downloadExportResultArchive + // We don't download here to avoid duplicate operations + } catch (error) { + this.logging.error( + `Error handling completed status for job ${jobId}: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + } + } + private async getATXFESJobStatusWithRetry( + jobId: string, + isRetry: boolean + ): Promise<{ status: string; createdAt?: string } | null> { + try { + this.logging.log(`=== ATX FES GetJob Operation ${isRetry ? '(Retry)' : ''} (FES Client) ===`) + this.logging.log(`Getting status for job: ${jobId}`) + + if (!(await this.ensureATXClient())) { + this.logging.error('GetJob: Failed to initialize FES client') + return { status: 'FAILED', createdAt: new Date().toISOString() } + } + + // We need workspaceId to call GetJob - get it from stored context + const workspaceId = this.currentWorkspaceId + if (!workspaceId) { + this.logging.error('GetJob: No workspace ID available') + return { status: 'FAILED', createdAt: new Date().toISOString() } + } + + // Call FES client getJob method + const result = await this.fesClient.getJob({ + workspaceId: workspaceId, + jobId: jobId, + includeObjective: false, + }) + + this.logging.log(`GetJob: FES client response: ${JSON.stringify(result)}`) + + // Response format: { job: { statusDetails: { status: "CREATED" }, creationTime: "..." } } + const job = result.job + if (job && job.statusDetails) { + const atxStatus = job.statusDetails.status + const mappedStatus = this.mapATXStatusToRTS(atxStatus) + + this.logging.log(`GetJob: ATX FES status: ${atxStatus} -> Mapped to RTS: ${mappedStatus}`) + + return { + status: mappedStatus, + createdAt: job.creationTime, } + } else { + this.logging.error('GetJob: Invalid response format - missing job.statusDetails') + return { status: 'FAILED', createdAt: new Date().toISOString() } + } + } catch (error) { + this.logging.error(`GetJob error: ${error instanceof Error ? error.message : 'Unknown error'}`) - const expDelayMs = this.getExpDelayForApiRetryMs(cancelTransformationAttempt) + // Handle token expiration with retry (like RTS pattern) + if ( + !isRetry && + error instanceof Error && + (error.message.includes('401') || error.message.includes('403')) + ) { + this.logging.log('GetJob: Token expired, retrying with fresh token...') + // Clear cached applicationUrl to force refresh if needed + this.cachedApplicationUrl = null + // Retry once with fresh token + return this.getATXFESJobStatusWithRetry(jobId, true) + } + + return { status: 'FAILED', createdAt: new Date().toISOString() } + } + } + + /** + * Get job plan steps from ATX FES ListJobPlanSteps API using FES TypeScript client + * Only returns steps if job status >= PLANNED + */ + private async getATXFESJobPlanSteps(jobId: string): Promise { + try { + const workspaceId = this.currentWorkspaceId + if (!workspaceId) { + this.logging.error('ListJobPlanSteps: No workspace ID available') + return null + } + + // First check job status - only get plan steps if job is PLANNED or later + this.logging.log('ATX FES: Checking job status before getting plan steps...') + const jobResponse = await this.getJobFESClient(workspaceId, jobId) + + if (!jobResponse || !jobResponse.job || !jobResponse.job.statusDetails) { + this.logging.error('ATX FES: Could not get job status - invalid response structure') + return null + } + + const currentStatus = jobResponse.job.statusDetails.status.toUpperCase() + this.logging.log(`ATX FES: Current job status: ${currentStatus}`) + + // Only get plan steps if job has reached PLANNED status or later (per Smithy model) + const plannedStatuses = [ + 'PLANNED', + 'EXECUTING', + 'AWAITING_HUMAN_INPUT', + 'COMPLETED', + 'FAILED', + 'STOPPING', + 'STOPPED', + ] + if (!plannedStatuses.includes(currentStatus)) { this.logging.log( - `Attempt ${cancelTransformationAttempt}/${cancelTransformationMaxAttempts} to get transformation plan failed, retry in ${expDelayMs} seconds` + `ATX FES: Job status ${currentStatus} - plan steps not available yet. Need status >= PLANNED` ) - await this.sleep(expDelayMs * 1000) + return null // Return null so IDE knows plan isn't ready yet + } + + this.logging.log(`ATX FES: Job status ${currentStatus} - getting plan steps with substeps...`) + const result = await this.listJobPlanStepsFESClient(workspaceId, jobId) + if (result) { + const steps = result || [] + this.logging.log(`ListJobPlanSteps: SUCCESS - Found ${steps.length} plan steps with substeps`) + return steps } + return null + } catch (error) { + this.logging.error(`ListJobPlanSteps error: ${error instanceof Error ? error.message : 'Unknown error'}`) + return null } } - async sleep(duration = 0): Promise { - return new Promise(r => setTimeout(r, Math.max(duration, 0))) - } + async preTransformationUploadCode(payloadFilePath: string): Promise { + try { + const uploadId = await this.uploadPayloadAsync(payloadFilePath) + this.logging.log('Artifact was successfully uploaded. Upload tracking id: ' + uploadId) + return uploadId + } catch (error) { + const errorMessage = (error as Error).message ?? 'Failed to upload zip file' + throw new Error(errorMessage) + } + } + + async uploadPayloadAsync(payloadFileName: string): Promise { + const sha256 = await ArtifactManager.getSha256Async(payloadFileName) + let response: CreateUploadUrlResponse + try { + response = await this.serviceManager.getCodewhispererService().codeModernizerCreateUploadUrl({ + contentChecksum: sha256, + contentChecksumType: 'SHA_256', + uploadIntent: 'TRANSFORMATION', + }) + } catch (e: any) { + const errorMessage = (e as Error).message ?? 'Error in CreateUploadUrl API call' + this.logging.log('Error when creating upload url: ' + errorMessage) + throw new Error(errorMessage) + } + + try { + await this.uploadArtifactToS3Async(payloadFileName, response, sha256) + } catch (e: any) { + const errorMessage = (e as Error).message ?? 'Error in uploadArtifactToS3 call' + this.logging.log('Error when calling uploadArtifactToS3Async: ' + errorMessage) + throw new Error(errorMessage) + } + return response.uploadId || '' + } + + async zipCodeAsync(request: StartTransformRequest, artifactManager: ArtifactManager): Promise { + try { + return await artifactManager.createZip(request) + } catch (e: any) { + this.logging.log('Error creating zip: ' + e) + throw e + } + } + + async uploadArtifactToS3Async(fileName: string, resp: CreateUploadUrlResponse, sha256: string) { + const headersObj = this.getHeadersObj(sha256, resp.kmsKeyArn) + try { + const fileStream = fs.createReadStream(fileName) + const response = await got.put(resp.uploadUrl || '', { + body: fileStream, + headers: headersObj, + }) + + this.logging.log(`CodeTransform: Response from S3 Upload = ${response.statusCode}`) + } catch (e: any) { + const error = e as Error + const errorMessage = `error: ${error.message || 'Error in S3 UploadZip API call'}, please see https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/troubleshooting-code-transformation.html#project-upload-fail` + this.logging.log(errorMessage) + throw new Error(errorMessage) + } + } + + getHeadersObj(sha256: string, kmsKeyArn: string | undefined) { + let headersObj = {} + if (kmsKeyArn === undefined || kmsKeyArn.length === 0) { + headersObj = { + 'x-amz-checksum-sha256': sha256, + 'Content-Type': 'application/zip', + } + } else { + headersObj = { + 'x-amz-checksum-sha256': sha256, + 'Content-Type': 'application/zip', + 'x-amz-server-side-encryption': 'aws:kms', + 'x-amz-server-side-encryption-aws-kms-key-id': kmsKeyArn, + } + } + return headersObj + } + /** + * Retrieves the status and details of a transformation job. + * Includes error code when the job has failed. + * + * @param request - The request containing the transformation job ID + * @returns The transformation job details with error code if applicable, or null if the request fails + */ + async getTransformation(request: GetTransformRequest) { + try { + // Check if we should use ATX FES or RTS + if (this.serviceManager.isAWSTransformProfile()) { + this.logging.log('Using ATX FES GetJob for Transform profile') + + try { + // Get real job status from ATX FES + const jobStatus = await this.getATXFESJobStatus(request.TransformationJobId) + + if (jobStatus) { + return { + TransformationJob: { + jobId: request.TransformationJobId, + status: jobStatus.status, + creationTime: jobStatus.createdAt ? new Date(jobStatus.createdAt) : new Date(), + } as any, + ErrorCode: TransformationErrorCode.NONE, + } as GetTransformResponse + } else { + // Fallback if API call fails + this.logging.log('ATX FES GetJob failed, using fallback response') + return { + TransformationJob: { + jobId: request.TransformationJobId, + status: 'IN_PROGRESS', + creationTime: new Date(), + } as any, + ErrorCode: TransformationErrorCode.NONE, + } as GetTransformResponse + } + } catch (error) { + this.logging.error( + `ATX FES GetJob error: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + // Return fallback response on error + return { + TransformationJob: { + jobId: request.TransformationJobId, + status: 'IN_PROGRESS', + creationTime: new Date(), + } as any, + ErrorCode: TransformationErrorCode.NONE, + } as GetTransformResponse + } + } + + // Original RTS implementation + const getCodeTransformationRequest = { + transformationJobId: request.TransformationJobId, + } as GetTransformationRequest + const response = await this.serviceManager + .getCodewhispererService() + .codeModernizerGetCodeTransformation(getCodeTransformationRequest) + this.logging.log('Transformation status: ' + response.transformationJob?.status) + + // Use validation function to determine the error code + const errorCode = validation.getTransformationErrorCode(response.transformationJob) + + return { + TransformationJob: response.transformationJob, + ErrorCode: errorCode, + } as GetTransformResponse + } catch (e: any) { + const errorMessage = (e as Error).message ?? 'Error in GetTransformation API call' + this.logging.log('Error: ' + errorMessage) + + // Never return null - always return a valid response with error status + return { + TransformationJob: { + jobId: request.TransformationJobId, + status: 'FAILED', + creationTime: new Date(), + reason: errorMessage, + } as any, + ErrorCode: TransformationErrorCode.UNKNOWN_ERROR, + } as GetTransformResponse + } + } + async getTransformationPlan(request: GetTransformPlanRequest) { + // Check if we should use ATX FES or RTS + if (this.serviceManager.isAWSTransformProfile()) { + this.logging.log('Using ATX FES for Transform profile - real ListJobPlanSteps') + + try { + // Get real plan steps from ATX FES (only if job status >= PLANNED) + const planSteps = await this.getATXFESJobPlanSteps(request.TransformationJobId) + + if (planSteps) { + this.logging.log(`ATX FES: Found ${planSteps.length} transformation steps`) + + // Sort steps by score (primary) and startTime (tiebreaker) to match RTS ordering + planSteps.sort((a: any, b: any) => { + const scoreDiff = (a.score || 0) - (b.score || 0) + if (scoreDiff !== 0) return scoreDiff + + // Tiebreaker for identical scores: sort by startTime + const timeA = a.startTime ? new Date(a.startTime).getTime() : 0 + const timeB = b.startTime ? new Date(b.startTime).getTime() : 0 + return timeA - timeB + }) + + this.logging.log(`PlanSteps response: ` + JSON.stringify(planSteps)) + // Return in exact same format as RTS with all required fields + const mappedResponse = { + TransformationPlan: { + transformationSteps: planSteps.map((step: any, index: number) => { + try { + // Map substeps to ProgressUpdates for IDE display + const progressUpdates = (step.substeps || []).map((substep: any) => { + // Map ATX substep status to IDE TransformationProgressUpdateStatus enum values + let substepStatus = 'IN_PROGRESS' // Default - no NOT_STARTED in this enum + switch (substep.status) { + case 'SUCCEEDED': + case 'COMPLETED': + substepStatus = 'COMPLETED' + break + case 'IN_PROGRESS': + case 'RUNNING': + substepStatus = 'IN_PROGRESS' + break + case 'FAILED': + substepStatus = 'FAILED' + break + case 'SKIPPED': + substepStatus = 'SKIPPED' + break + case 'NOT_STARTED': + case 'CREATED': + default: + substepStatus = 'IN_PROGRESS' // No NOT_STARTED option in ProgressUpdate enum + break + } + + return { + name: substep.stepName || 'Unknown Substep', + description: substep.description || '', + status: substepStatus, + startTime: substep.startTime ? new Date(substep.startTime) : undefined, + endTime: substep.endTime ? new Date(substep.endTime) : undefined, + } + }) + + // Use ATX status directly - IDE supports most values, minimal mapping needed + let mappedStatus = step.status || 'NOT_STARTED' + // Only map the few values IDE doesn't have + if (mappedStatus === 'SUCCEEDED') { + mappedStatus = 'COMPLETED' + } else if (mappedStatus === 'RUNNING') { + mappedStatus = 'IN_PROGRESS' + } else if (mappedStatus === 'CREATED') { + mappedStatus = 'NOT_STARTED' + } + + // Use ATX step data directly without hardcoded ordering + const stepNumber = index + 1 + const stepName = `Step ${stepNumber} - ${step.stepName || 'Unknown Step'}` + + this.logging.log( + `ATX Step ${stepNumber}: ${step.stepName} (${step.status} → ${mappedStatus}) with ${progressUpdates.length} substeps` + ) + + return { + id: step.stepId || `step-${stepNumber}`, + name: stepName, + description: step.description || '', + status: mappedStatus, + progressUpdates: progressUpdates, + startTime: step.startTime ? new Date(step.startTime) : undefined, + endTime: step.endTime ? new Date(step.endTime) : undefined, + } + } catch (error) { + this.logging.error( + `ATX FES: Error mapping step ${index}: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + // Return a safe fallback step + const stepNumber = index + 1 + return { + id: step.stepId || `fallback-${stepNumber}`, + name: `Step ${stepNumber} - ${step.stepName || `Step ${stepNumber}`}`, + description: step.description || '', + status: 'NOT_STARTED', + progressUpdates: [], + startTime: undefined, + endTime: undefined, + } + } + }), + }, + } as GetTransformPlanResponse + try { + await this.listWorklogsFESClient(this.currentWorkspaceId || '', request.TransformationJobId) + + // Save worklogs for each step + // if (planSteps && planSteps.length > 0) { + // for (const step of planSteps) { + // if (step.stepId && step.description) { + // await this.saveWorklogsToJson(request.TransformationJobId, step.stepId, step.description) + // } + + // // Save worklogs for substeps + // if (step.substeps && step.substeps.length > 0) { + // for (const substep of step.substeps) { + // if (substep.stepId && substep.description) { + // await this.saveWorklogsToJson(request.TransformationJobId, substep.stepId, substep.description) + // } + // } + // } + // } + // } + } catch (e) { + this.logging.log( + `ATX FES: Could not get worklog for workspaces: ${this.currentWorkspaceId}, job id: ${request.TransformationJobId}` + ) + } + + this.logging.log( + `ATX FES: Successfully mapped ${mappedResponse.TransformationPlan.transformationSteps?.length || 0} steps` + ) + if (mappedResponse.TransformationPlan.transformationSteps?.[0]) { + this.logging.log( + `ATX FES: First step mapped - id: ${mappedResponse.TransformationPlan.transformationSteps[0].id}, name: ${mappedResponse.TransformationPlan.transformationSteps[0].name}` + ) + } + + return mappedResponse + } else { + this.logging.log('ATX FES: No plan steps available yet - returning empty plan') + return { + TransformationPlan: { + transformationSteps: [] as any, + } as any, + } as GetTransformPlanResponse + } + } catch (error) { + this.logging.error( + `ATX FES getTransformationPlan error: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + // Return empty plan on error + return { + TransformationPlan: { + transformationSteps: [] as any, + } as any, + } as GetTransformPlanResponse + } + } + + // Original RTS implementation + let getTransformationPlanAttempt = 0 + let getTransformationPlanMaxAttempts = 3 + while (true) { + try { + const getCodeTransformationPlanRequest = { + transformationJobId: request.TransformationJobId, + } as GetTransformationRequest + const response = await this.serviceManager + .getCodewhispererService() + .codeModernizerGetCodeTransformationPlan(getCodeTransformationPlanRequest) + return { + TransformationPlan: response.transformationPlan, + } as GetTransformPlanResponse + } catch (e: any) { + const errorMessage = (e as Error).message ?? 'Error in GetTransformationPlan API call' + this.logging.log('Error: ' + errorMessage) + + getTransformationPlanAttempt += 1 + if (getTransformationPlanAttempt >= getTransformationPlanMaxAttempts) { + this.logging.log(`GetTransformationPlan failed after ${getTransformationPlanMaxAttempts} attempts.`) + throw e + } + + const expDelayMs = this.getExpDelayForApiRetryMs(getTransformationPlanAttempt) + this.logging.log( + `Attempt ${getTransformationPlanAttempt}/${getTransformationPlanMaxAttempts} to get transformation plan failed, retry in ${expDelayMs} seconds` + ) + await this.sleep(expDelayMs * 1000) + } + } + } + + async cancelTransformation(request: CancelTransformRequest) { + // Check if we should use ATX FES or RTS + if (this.serviceManager.isAWSTransformProfile()) { + this.logging.log('Using ATX FES StopJob for Transform profile') + + try { + // We need workspaceId to call StopJob - get it from stored context + const workspaceId = this.currentWorkspaceId + if (!workspaceId) { + this.logging.error('StopJob: No workspace ID available') + return { + TransformationJobStatus: CancellationJobStatus.FAILED_TO_CANCEL, + } as CancelTransformResponse + } + + // Call ATX FES StopJob + const stopResult = await this.stopJobATXFES(workspaceId, request.TransformationJobId) + + if (stopResult) { + this.logging.log('ATX FES StopJob: SUCCESS') + return { + TransformationJobStatus: CancellationJobStatus.SUCCESSFULLY_CANCELLED, + } as CancelTransformResponse + } else { + this.logging.log('ATX FES StopJob: FAILED') + return { + TransformationJobStatus: CancellationJobStatus.FAILED_TO_CANCEL, + } as CancelTransformResponse + } + } catch (error) { + this.logging.error(`ATX FES StopJob error: ${error instanceof Error ? error.message : 'Unknown error'}`) + return { + TransformationJobStatus: CancellationJobStatus.FAILED_TO_CANCEL, + } as CancelTransformResponse + } + } + + // Original RTS implementation + let cancelTransformationAttempt = 0 + let cancelTransformationMaxAttempts = 3 + while (true) { + try { + const stopCodeTransformationRequest = { + transformationJobId: request.TransformationJobId, + } as StopTransformationRequest + this.logging.log( + 'Sending CancelTransformRequest with job Id: ' + stopCodeTransformationRequest.transformationJobId + ) + const response = await this.serviceManager + .getCodewhispererService() + .codeModernizerStopCodeTransformation(stopCodeTransformationRequest) + this.logging.log('Transformation status: ' + response.transformationStatus) + let status: CancellationJobStatus + switch (response.transformationStatus) { + case 'STOPPED': + status = CancellationJobStatus.SUCCESSFULLY_CANCELLED + break + default: + status = CancellationJobStatus.FAILED_TO_CANCEL + break + } + return { + TransformationJobStatus: status, + } as CancelTransformResponse + } catch (e: any) { + const errorMessage = (e as Error).message ?? 'Error in CancelTransformation API call' + this.logging.log('Error: ' + errorMessage) + + cancelTransformationAttempt += 1 + if (cancelTransformationAttempt >= cancelTransformationMaxAttempts) { + this.logging.log(`CancelTransformation failed after ${cancelTransformationMaxAttempts} attempts.`) + return { + TransformationJobStatus: CancellationJobStatus.FAILED_TO_CANCEL, + } as CancelTransformResponse + } + + const expDelayMs = this.getExpDelayForApiRetryMs(cancelTransformationAttempt) + this.logging.log( + `Attempt ${cancelTransformationAttempt}/${cancelTransformationMaxAttempts} to get transformation plan failed, retry in ${expDelayMs} seconds` + ) + await this.sleep(expDelayMs * 1000) + } + } + } + + async sleep(duration = 0): Promise { + return new Promise(r => setTimeout(r, Math.max(duration, 0))) + } + + async pollTransformation(request: GetTransformRequest, validExitStatus: string[], failureStates: string[]) { + // Check if we should use ATX FES or RTS + if (this.serviceManager.isAWSTransformProfile()) { + this.logging.log('Using ATX FES for Transform profile - real polling') + + if (!validExitStatus.includes('Planning')) { + validExitStatus = ['AWAITING_HUMAN_INPUT'] + } + + try { + // Get real job status from ATX FES + var count = 0 + while (count < 300) { + const jobStatus = await this.getATXFESJobStatus(request.TransformationJobId) + + if (jobStatus && validExitStatus.includes(jobStatus.originalStatus)) { + return { + TransformationJob: { + jobId: request.TransformationJobId, + status: jobStatus.status, + creationTime: jobStatus.createdAt ? new Date(jobStatus.createdAt) : new Date(), + } as any, + ErrorCode: TransformationErrorCode.NONE, + } as GetTransformResponse + } else if (jobStatus && failureStates.includes(jobStatus.originalStatus)) { + // Fallback to placeholder if API call fails + this.logging.log('ATX FES polling failed, using placeholder') + return { + TransformationJob: { + jobId: request.TransformationJobId, + status: 'IN_PROGRESS', + creationTime: new Date(), + } as any, + ErrorCode: TransformationErrorCode.NONE, + } as GetTransformResponse + } else { + this.logging.log('ATX FES polling in progress....') + await this.sleep(10 * 1000) + count++ + } + } + } catch (error) { + this.logging.error(`ATX FES polling error: ${error instanceof Error ? error.message : 'Unknown error'}`) + // Return fallback response on error + return { + TransformationJob: { + jobId: request.TransformationJobId, + status: 'IN_PROGRESS', + creationTime: new Date(), + } as any, + ErrorCode: TransformationErrorCode.NONE, + } as GetTransformResponse + } + } + + // Original RTS implementation + let timer = 0 + let getTransformAttempt = 0 + let getTransformMaxAttempts = 3 + const getCodeTransformationRequest = { + transformationJobId: request.TransformationJobId, + } as GetTransformationRequest + let response = await this.serviceManager + .getCodewhispererService() + .codeModernizerGetCodeTransformation(getCodeTransformationRequest) + this.logging.log('Start polling for transformation plan.') + this.logging.log('The valid status to exit polling are: ' + validExitStatus) + this.logging.log('The failure status are: ' + failureStates) + + this.logging.log('Transformation status: ' + response.transformationJob?.status) + let status = response?.transformationJob?.status ?? PollTransformationStatus.NOT_FOUND + + while (status != PollTransformationStatus.TIMEOUT && !failureStates.includes(status)) { + try { + if (this.cancelPollingEnabled) { + // Reset the flag + this.cancelPollingEnabled = false + return { + TransformationJob: response.transformationJob, + } as GetTransformResponse + } + const apiStartTime = Date.now() + + const getCodeTransformationRequest = { + transformationJobId: request.TransformationJobId, + } as GetTransformationRequest + response = await this.serviceManager + .getCodewhispererService() + .codeModernizerGetCodeTransformation(getCodeTransformationRequest) + this.logging.log('Transformation status: ' + response.transformationJob?.status) + + if (validExitStatus.includes(status)) { + this.logging.log('Exiting polling for transformation plan with transformation status: ' + status) + break + } + + status = response.transformationJob?.status! + await this.sleep(10 * 1000) + timer += 10 + + if (timer > 24 * 3600 * 1000) { + status = PollTransformationStatus.TIMEOUT + break + } + getTransformAttempt = 0 // a successful polling will reset attempt + } catch (e: any) { + const errorMessage = (e as Error).message ?? 'Error in GetTransformation API call' + this.logging.log('Error polling transformation job from the server: ' + errorMessage) + + getTransformAttempt += 1 + if (getTransformAttempt >= getTransformMaxAttempts) { + this.logging.log(`GetTransformation failed after ${getTransformMaxAttempts} attempts.`) + status = PollTransformationStatus.NOT_FOUND + break + } + + const expDelayMs = this.getExpDelayForApiRetryMs(getTransformAttempt) + this.logging.log( + `Attempt ${getTransformAttempt}/${getTransformMaxAttempts} to get transformation plan failed, retry in ${expDelayMs} seconds` + ) + await this.sleep(expDelayMs * 1000) + } + } + this.logging.log('Returning response from server : ' + JSON.stringify(response)) + this.logSuggestionForFailureResponse(request, response.transformationJob!, failureStates) + return { + TransformationJob: response.transformationJob, + } as GetTransformResponse + } + + /** + * ATX FES version of downloadExportResultArchive + */ + async downloadExportResultArchiveATXFES(exportId: string, saveToDir: string): Promise { + try { + this.logging.log('=== ATX FES Download Export Result Archive ===') + this.logging.log(`Called with exportId: ${exportId}, saveToDir: ${saveToDir}`) + + // Get workspace and job IDs from cached data + const workspaceId = this.currentWorkspaceId + const jobId = exportId // In ATX FES context, exportId should be the jobId + + this.logging.log(`Using workspaceId: ${workspaceId}, jobId: ${jobId}`) + + if (!workspaceId) { + throw new Error('No workspace ID available for ATX FES download') + } + + this.logging.log(`Listing CUSTOMER_OUTPUT artifacts for job: ${jobId}`) + const artifacts = await this.listArtifactsFESClient(workspaceId, jobId) + if (!artifacts || artifacts.length === 0) { + throw new Error('No CUSTOMER_OUTPUT artifacts available for download') + } + + const artifact = artifacts[0] + const artifactId = artifact.artifactId + this.logging.log(`Found artifact: ${artifactId}, size: ${artifact.sizeInBytes} bytes`) + + this.logging.log(`Creating download URL for artifactId: ${artifactId}`) + const downloadInfo = await this.createArtifactDownloadUrlFESClient(workspaceId, jobId, artifactId) + if (!downloadInfo) { + throw new Error('Failed to get ATX FES download URL') + } + + this.logging.log(`Got S3 download URL`) + this.logging.log(`Request headers: ${JSON.stringify(Object.keys(downloadInfo.requestHeaders || {}))}`) + + this.logging.log('Starting S3 download...') + const got = await import('got') + const s3Response = await got.default.get(downloadInfo.downloadUrl, { + headers: downloadInfo.requestHeaders || {}, + timeout: { request: 300000 }, + responseType: 'buffer', + }) + + this.logging.log( + `S3 download completed - Status: ${s3Response.statusCode}, Size: ${s3Response.body.length} bytes` + ) + + const buffer = [s3Response.body] + const saveToWorkspace = path.join(saveToDir, workspaceFolderName) + this.logging.log(`Saving artifacts to workspace: ${saveToWorkspace}`) + + const pathContainingArchive = await this.archivePathGenerator(artifactId, buffer, saveToWorkspace) + this.logging.log(`Archive extracted to: ${pathContainingArchive}`) + + const downloadResponse = { + PathTosave: pathContainingArchive, + } as DownloadArtifactsResponse + + this.logging.log(`=== ATX FES Download SUCCESS ===`) + this.logging.log(`Returning response to IDE: ${JSON.stringify(downloadResponse)}`) + return downloadResponse + } catch (error) { + this.logging.error(`ATX FES download failed: ${error instanceof Error ? error.message : 'Unknown error'}`) + this.logging.error(`Error stack: ${error instanceof Error ? error.stack : 'No stack trace'}`) + return { + Error: error instanceof Error ? error.message : 'ATX FES download failed', + } as DownloadArtifactsResponse + } + } + + async downloadExportResultArchive(exportId: string, saveToDir: string): Promise { + // Check if we should use ATX FES or RTS + if (this.serviceManager.isAWSTransformProfile()) { + this.logging.log('Using ATX FES for artifact download') + return await this.downloadExportResultArchiveATXFES(exportId, saveToDir) + } + + // Original RTS implementation + let result + try { + result = await this.serviceManager.getStreamingClient().exportResultArchive({ + exportId, + exportIntent: ExportIntent.TRANSFORMATION, + }) + + const buffer = [] + this.logging.log('Artifact was successfully downloaded.') + + if (result.body === undefined) { + throw new Error('Empty response from CodeWhisperer streaming service.') + } + + for await (const chunk of result.body) { + if (chunk.binaryPayloadEvent) { + const chunkData = chunk.binaryPayloadEvent + if (chunkData.bytes) { + buffer.push(chunkData.bytes) + } + } + } + const saveToWorkspace = path.join(saveToDir, workspaceFolderName) + this.logging.log(`Identified path of directory to save artifacts is ${saveToDir}`) + const pathContainingArchive = await this.archivePathGenerator(exportId, buffer, saveToWorkspace) + this.logging.log('PathContainingArchive :' + pathContainingArchive) + return { + PathTosave: pathContainingArchive, + } as DownloadArtifactsResponse + } catch (error) { + const errorMessage = (error as Error).message ?? 'Failed to download the artifacts' + return { + Error: errorMessage, + } as DownloadArtifactsResponse + } + } + + async cancelPollingAsync() { + this.cancelPollingEnabled = true + } + + async extractAllEntriesTo(pathContainingArchive: string, zipEntries: AdmZip.IZipEntry[]) { + for (const entry of zipEntries) { + try { + const entryPath = path.join(pathContainingArchive, entry.entryName) + if (entry.isDirectory) { + await fs.promises.mkdir(entryPath, { recursive: true }) + } else { + const parentDir = path.dirname(entryPath) + await fs.promises.mkdir(parentDir, { recursive: true }) + await fs.promises.writeFile(entryPath, entry.getData()) + } + } catch (extractError: any) { + if (extractError instanceof Error && 'code' in extractError && extractError.code === 'ENOENT') { + this.logging.log(`Attempted to extract a file that does not exist : ${entry.entryName}`) + } else { + throw extractError + } + } + } + } + + async archivePathGenerator(exportId: string, buffer: Uint8Array[], saveToDir: string) { + try { + const tempDir = path.join(saveToDir, exportId) + const pathToArchive = path.join(tempDir, 'ExportResultsArchive.zip') + await this.directoryExists(tempDir) + await fs.writeFileSync(pathToArchive, Buffer.concat(buffer)) + let pathContainingArchive = '' + pathContainingArchive = path.dirname(pathToArchive) + const zip = new AdmZip(pathToArchive) + const zipEntries = zip.getEntries() + await this.extractAllEntriesTo(pathContainingArchive, zipEntries) + return pathContainingArchive + } catch (error) { + this.logging.log(`error received ${JSON.stringify(error)}`) + return '' + } + } + + async directoryExists(directoryPath: any) { + try { + await fs.accessSync(directoryPath) + } catch (error) { + // Directory doesn't exist, create it + this.logging.log(`Directory doesn't exist, creating it ${directoryPath}`) + await fs.mkdirSync(directoryPath, { recursive: true }) + } + } + + getWorkspacePath(solutionRootPath: string): string { + const randomPath = uuidv4().substring(0, 8) + const workspacePath = path.join(solutionRootPath, workspaceFolderName, randomPath) + if (!fs.existsSync(workspacePath)) { + fs.mkdirSync(workspacePath, { recursive: true }) + } + return workspacePath + } + + getExpDelayForApiRetryMs(attempt: number): number { + const exponentialDelayFactor = 2 + const exponentialDelay = 10 * Math.pow(exponentialDelayFactor, attempt) + const jitteredDelay = Math.floor(Math.random() * 10) + return exponentialDelay + jitteredDelay // returns in milliseconds + } + + logSuggestionForFailureResponse(request: GetTransformRequest, job: TransformationJob, failureStates: string[]) { + let status = job?.status ?? PollTransformationStatus.NOT_FOUND + let reason = job?.reason ?? '' + if (failureStates.includes(status)) { + let suggestion = '' + if (reason.toLowerCase().includes('build validation failed')) { + suggestion = + 'Please close Visual Studio, delete the directories where build artifacts are generated (e.g. bin and obj), and try running the transformation again.' + } + this.logging + .log(`Transformation job for job ${request.TransformationJobId} is ${status} due to "${reason}". +                ${suggestion}`) + } + } + + // ===== FES CLIENT METHODS (Replace Manual HTTP) ===== + + /** + * Creates a transformation job using FES client + */ + private async createJobFESClient(workspaceId: string): Promise<{ jobId: string; status: string } | null> { + try { + this.logging.log('=== ATX FES CreateJob Operation (FES Client) ===') + this.logging.log(`Creating job for workspace: ${workspaceId}`) + + if (!(await this.ensureATXClient())) { + this.logging.error('CreateJob: Failed to initialize ATX client') + return null + } + + const command = new CreateJobCommand({ + workspaceId: workspaceId, + objective: JSON.stringify({ target_framework: '.NET 8.0' }), + jobType: 'DOTNET_IDE' as any, // Now available in package 2 + jobName: `transform-job-${Date.now()}`, + intent: 'LANGUAGE_UPGRADE', + idempotencyToken: uuidv4(), + }) + + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse('CreateJob', result) + + if (result && result.jobId && result.status) { + this.logging.log(`CreateJob: SUCCESS - Job created with ID: ${result.jobId}, Status: ${result.status}`) + return { jobId: result.jobId, status: result.status } + } else { + this.logging.error('CreateJob: Missing jobId or status in response') + return null + } + } catch (error) { + this.logging.error(`CreateJob error: ${error instanceof Error ? error.message : 'Unknown error'}`) + return null + } + } + + /** + * Starts a transformation job using FES client + */ + private async startJobFESClient(workspaceId: string, jobId: string): Promise { + try { + this.logging.log('=== ATX FES StartJob Operation (FES Client) ===') + this.logging.log(`Starting job: ${jobId} in workspace: ${workspaceId}`) + + if (!(await this.ensureATXClient())) { + this.logging.error('StartJob: Failed to initialize ATX client') + return false + } + + const command = new StartJobCommand({ + workspaceId: workspaceId, + jobId: jobId, + idempotencyToken: uuidv4(), + }) + + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse('StartJob', result) + + this.logging.log(`StartJob: SUCCESS - Status: ${result.status}`) + return true + } catch (error) { + this.logging.error(`StartJob error: ${error instanceof Error ? error.message : 'Unknown error'}`) + return false + } + } + + /** + * Gets job status using FES client + */ + private async getJobFESClient(workspaceId: string, jobId: string): Promise { + try { + this.logging.log('=== ATX FES GetJob Operation (FES Client) ===') + this.logging.log(`Getting job: ${jobId} in workspace: ${workspaceId}`) + + if (!(await this.ensureATXClient())) { + this.logging.error('GetJob: Failed to initialize ATX client') + return null + } + + const command = new GetJobCommand({ + workspaceId: workspaceId, + jobId: jobId, + includeObjective: false, + }) + + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse('GetJob', result) + + this.logging.log(`GetJob: SUCCESS - Job data received`) + return result + } catch (error) { + this.logging.error(`GetJob error: ${error instanceof Error ? error.message : 'Unknown error'}`) + return null + } + } + + /** + * Creates artifact upload URL using FES client + */ + private async createArtifactUploadUrlFESClient( + workspaceId: string, + jobId: string, + payloadFilePath: string, + categoryType: CategoryType = 'CUSTOMER_INPUT', + fileType: FileType = 'ZIP' + ): Promise<{ uploadId: string; uploadUrl: string; requestHeaders?: any } | null> { + try { + this.logging.log('=== ATX FES CreateArtifactUploadUrl Operation (FES Client) ===') + + if (!(await this.ensureATXClient())) { + this.logging.error('CreateArtifactUploadUrl: Failed to initialize ATX client') + return null + } + + // Calculate file checksum + const sha256 = await ArtifactManager.getSha256Async(payloadFilePath) + + const command = new CreateArtifactUploadUrlCommand({ + workspaceId: workspaceId, + jobId: jobId, + contentDigest: { Sha256: sha256 }, + artifactReference: { + artifactType: { + categoryType: categoryType, // ✅ Fixed: User uploads source code + fileType: fileType, + }, + }, + }) + + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse('CreateArtifactUploadUrl', result) + + if (result && result.artifactId && result.s3PreSignedUrl) { + this.logging.log(`CreateArtifactUploadUrl: SUCCESS - Upload URL created`) + return { + uploadId: result.artifactId, + uploadUrl: result.s3PreSignedUrl, + requestHeaders: result.requestHeaders, + } + } else { + this.logging.error('CreateArtifactUploadUrl: Missing artifactId or s3PreSignedUrl in response') + return null + } + } catch (error) { + this.logging.error( + `CreateArtifactUploadUrl error: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + return null + } + } + + /** + * Completes artifact upload using FES client + */ + private async completeArtifactUploadFESClient( + workspaceId: string, + jobId: string, + artifactId: string + ): Promise { + try { + this.logging.log('=== ATX FES CompleteArtifactUpload Operation (FES Client) ===') + + if (!(await this.ensureATXClient())) { + this.logging.error('CompleteArtifactUpload: Failed to initialize ATX client') + return false + } + + const command = new CompleteArtifactUploadCommand({ + workspaceId: workspaceId, + jobId: jobId, + artifactId: artifactId, + }) + + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse('CompleteArtifactUpload', result) + + this.logging.log(`CompleteArtifactUpload: SUCCESS`) + return true + } catch (error) { + this.logging.error( + `CompleteArtifactUpload error: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + return false + } + } + + /** + * Lists job plan steps using FES client with recursive substep fetching + */ + private async listJobPlanStepsFESClient(workspaceId: string, jobId: string): Promise { + try { + this.logging.log('=== ATX FES ListJobPlanSteps Operation (FES Client) ===') + + if (!(await this.ensureATXClient())) { + this.logging.error('ListJobPlanSteps: Failed to initialize ATX client') + return null + } + + // Get root steps first + const rootSteps = await this.getStepsRecursive(workspaceId, jobId, 'root') + + if (rootSteps && rootSteps.length > 0) { + // For each root step, get its substeps + for (const step of rootSteps) { + this.logging.log(`Getting substeps for step: ${step.stepName} (ID: ${step.stepId})`) + const substeps = await this.getStepsRecursive(workspaceId, jobId, step.stepId) + step.substeps = substeps || [] + + // Sort substeps by score (primary) and startTime (tiebreaker) to match RTS ordering + if (step.substeps.length > 0) { + step.substeps.sort((a: any, b: any) => { + const scoreDiff = (a.score || 0) - (b.score || 0) + if (scoreDiff !== 0) return scoreDiff + + // Tiebreaker for identical scores: sort by startTime + const timeA = a.startTime ? new Date(a.startTime).getTime() : 0 + const timeB = b.startTime ? new Date(b.startTime).getTime() : 0 + return timeA - timeB + }) + } + + this.logging.log(`Step ${step.stepName}: Found ${step.substeps.length} substeps`) + + // Log substep details for debugging + if (step.substeps.length > 0) { + step.substeps.forEach((substep: any, index: number) => { + this.logging.log( + ` Substep ${index + 1}: ${substep.stepName} (${substep.status || 'No status'})` + ) + }) + } + } + + this.logging.log(`ListJobPlanSteps: SUCCESS - Found ${rootSteps.length} steps with substeps`) + return rootSteps + } + + this.logging.log('ListJobPlanSteps: No root steps found') + return null + } catch (error) { + this.logging.error(`ListJobPlanSteps error: ${error instanceof Error ? error.message : 'Unknown error'}`) + return null + } + } + + /** + * Recursively gets steps for a given parent step ID + */ + private async getStepsRecursive(workspaceId: string, jobId: string, parentStepId: string): Promise { + try { + const command = new ListJobPlanStepsCommand({ + workspaceId: workspaceId, + jobId: jobId, + parentStepId: parentStepId, + maxResults: 100, + }) + + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse(`ListJobPlanSteps(${parentStepId})`, result) + + if (result && result.steps && result.steps.length > 0) { + this.logging.log(`Found ${result.steps.length} steps for parent: ${parentStepId}`) + return result.steps + } + + return null + } catch (error) { + this.logging.error( + `Error getting steps for parent ${parentStepId}: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + return null + } + } + + /** + * Lists available profiles using FES client + */ + private async listAvailableProfilesFESClient(): Promise { + try { + this.logging.log('=== ATX FES ListAvailableProfiles Operation (FES Client) ===') + + if (!(await this.ensureATXClient())) { + this.logging.error('ListAvailableProfiles: Failed to initialize ATX client') + return null + } + + const command = new ListAvailableProfilesCommand({ + maxResults: 100, + }) + + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse('ListAvailableProfiles', result) + + this.logging.log(`ListAvailableProfiles: SUCCESS - Found ${result.profiles?.length || 0} profiles`) + return result.profiles || [] + } catch (error) { + this.logging.error( + `ListAvailableProfiles error: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + return null + } + } + + /** + * Lists artifacts using FES client with filtering - default to CUSTOMER_OUTPUT + */ + private async listArtifactsFESClient( + workspaceId: string, + jobId: string, + filter: CategoryType = 'CUSTOMER_OUTPUT' + ): Promise { + try { + this.logging.log('=== ATX FES ListArtifacts Operation (FES Client) ===') + this.logging.log(`Listing artifacts for job: ${jobId} in workspace: ${workspaceId}`) + + if (!(await this.ensureATXClient())) { + this.logging.error('ListArtifacts: Failed to initialize ATX client') + return null + } + + const command = new ListArtifactsCommand({ + workspaceId: workspaceId, + jobFilter: { + jobId: jobId, + categoryType: filter, // Server-side filtering for customer output artifacts + }, + }) + + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse('ListArtifacts', result) + + this.logging.log( + `ListArtifacts: SUCCESS - Found ${result.artifacts?.length || 0} CUSTOMER_OUTPUT artifacts` + ) + return result.artifacts || [] + } catch (error) { + this.logging.error(`ListArtifacts error: ${error instanceof Error ? error.message : 'Unknown error'}`) + return null + } + } + + /** + * Lists artifacts using FES client with CUSTOMER_OUTPUT filtering + */ + private async listWorklogsFESClient(workspaceId: string, jobId: string, stepId?: string): Promise { + try { + this.logging.log('=== ATX FES ListWorklog Operation (FES Client) ===') + this.logging.log(`Listing ListWorklog for job: ${jobId} in workspace: ${workspaceId}`) + + if (!(await this.ensureATXClient())) { + this.logging.error('ListWorklog: Failed to initialize ATX client') + return null + } + + const command = new ListWorklogsCommand({ + workspaceId: workspaceId, + jobId: jobId, + ...(stepId && { + worklogFilter: { + stepIdFilter: { + stepId: stepId, + }, + }, + }), + // objective: JSON.stringify({ target_framework: '.NET 8.0' }), + // jobType: 'DOTNET_IDE' as any, // Now available in package 2 + // jobName: jobId, + // intent: 'LANGUAGE_UPGRADE', + // idempotencyToken: uuidv4(), + }) + + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse('ListWorklog', result) + + this.logging.log(`ListWorklog: SUCCESS - Found ${result.worklogs?.entries.length || 0} wokrlog entries`) + result.worklogs?.forEach(async (value, index) => { + const currentStepId = value.attributeMap?.STEP_ID || stepId || 'Progress' + this.logging.log(`worklog entry: ${value.description}`) + await this.saveWorklogsToJson(jobId, currentStepId, value.description || '') + }) + + return result.worklogs || [] + } catch (error) { + this.logging.error(`ListArtifacts error: ${error instanceof Error ? error.message : 'Unknown error'}`) + return null + } + } + + /** + * Saves worklogs to JSON file with stepId as key and description as value + */ + private async saveWorklogsToJson(jobId: string, stepId: string | null, description: string): Promise { + try { + if (!this.solutionRootPath) return + + const worklogDir = path.join(this.solutionRootPath, workspaceFolderName, jobId) + const worklogPath = path.join(worklogDir, 'worklogs.json') + + await this.directoryExists(worklogDir) + + let worklogData: Record = {} + + // Read existing worklog if it exists + if (fs.existsSync(worklogPath)) { + const existingData = fs.readFileSync(worklogPath, 'utf8') + worklogData = JSON.parse(existingData) + } + + if (stepId == null) { + stepId = 'ProgressUpdates' + } + + // Initialize array if stepId doesn't exist + if (!worklogData[stepId]) { + worklogData[stepId] = [] + } + + // Add description if not already present + if (!worklogData[stepId].includes(description)) { + worklogData[stepId].push(description) + } + + // Write back to file + fs.writeFileSync(worklogPath, JSON.stringify(worklogData, null, 2)) + + this.logging.log(`Worklog saved to: ${worklogPath}`) + } catch (error) { + this.logging.error(`Error saving worklog: ${error instanceof Error ? error.message : 'Unknown error'}`) + } + } + + /** + * Creates artifact download URL using FES client + */ + private async createArtifactDownloadUrlFESClient( + workspaceId: string, + jobId: string, + artifactId: string + ): Promise<{ downloadUrl: string; requestHeaders?: any } | null> { + try { + this.logging.log('=== ATX FES CreateArtifactDownloadUrl Operation (FES Client) ===') + + if (!(await this.ensureATXClient())) { + this.logging.error('CreateArtifactDownloadUrl: Failed to initialize ATX client') + return null + } + + const command = new CreateArtifactDownloadUrlCommand({ + workspaceId: workspaceId, + jobId: jobId, + artifactId: artifactId, + }) + + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse('CreateArtifactDownloadUrl', result) + + if (result && result.s3PreSignedUrl) { + this.logging.log(`CreateArtifactDownloadUrl: SUCCESS - Download URL created`) + + const normalizedHeaders: Record = {} + if (result.requestHeaders) { + for (const [key, value] of Object.entries(result.requestHeaders)) { + normalizedHeaders[key] = Array.isArray(value) ? value[0] : value + } + } + + return { + downloadUrl: result.s3PreSignedUrl, + requestHeaders: normalizedHeaders, + } + } else { + this.logging.error('CreateArtifactDownloadUrl: Missing s3PreSignedUrl in response') + return null + } + } catch (error) { + this.logging.error( + `CreateArtifactDownloadUrl error: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + return null + } + } + + /** + * Lists available workspaces for the user using ATX client + */ + async listWorkspaces(): Promise { + try { + this.logging.log('=== ATX FES ListWorkspaces Operation (ATX Client) ===') + + if (!(await this.ensureATXClient())) { + this.logging.error('ListWorkspaces: Failed to initialize ATX client') + return [] + } + + const command = new ListWorkspacesCommand({ maxResults: 10 }) + await this.addBearerTokenToCommand(command) + const response = await this.atxClient!.send(command) + + this.logging.log(`ListWorkspaces: SUCCESS - Found ${response.items?.length || 0} workspaces`) + + const workspaces = (response.items || []).map(workspace => ({ + Id: workspace.id, + Name: workspace.name, + })) + + this.logging.log(`ListWorkspaces: Returning ${workspaces.length} workspaces`) + return workspaces + } catch (error) { + this.logging.error(`ListWorkspaces error: ${error instanceof Error ? error.message : 'Unknown error'}`) + return [] + } + } + + /** + * Creates a new workspace with the given name using ATX client + */ + async createWorkspace(name: string | null): Promise { + try { + this.logging.log('=== ATX FES CreateWorkspace Operation (ATX Client) ===') + this.logging.log(`Creating workspace: ${name || 'auto-generated'}`) + + if (!(await this.ensureATXClient())) { + this.logging.error('CreateWorkspace: Failed to initialize ATX client') + return null + } + + for (let attempt = 1; attempt <= 3; attempt++) { + try { + this.logging.log(`CreateWorkspace: Attempt ${attempt}/3`) + + this.logging.log('CreateWorkspace: Calling verifySession first to establish tenant mapping...') + const verifyCommand = new VerifySessionCommand({}) + await this.addBearerTokenToCommand(verifyCommand) + const sessionResult = await this.atxClient!.send(verifyCommand) + + if (!sessionResult || !sessionResult.userId) { + this.logging.error('CreateWorkspace: VerifySession failed - cannot establish tenant mapping') + if (attempt === 3) return null + continue + } - async pollTransformation(request: GetTransformRequest, validExitStatus: string[], failureStates: string[]) { - let timer = 0 - let getTransformAttempt = 0 - let getTransformMaxAttempts = 3 - const getCodeTransformationRequest = { - transformationJobId: request.TransformationJobId, - } as GetTransformationRequest - let response = await this.serviceManager - .getCodewhispererService() - .codeModernizerGetCodeTransformation(getCodeTransformationRequest) - this.logging.log('Start polling for transformation plan.') - this.logging.log('The valid status to exit polling are: ' + validExitStatus) - this.logging.log('The failure status are: ' + failureStates) + this.logging.log('CreateWorkspace: Session verified, proceeding with CreateWorkspace...') - this.logging.log('Transformation status: ' + response.transformationJob?.status) - let status = response?.transformationJob?.status ?? PollTransformationStatus.NOT_FOUND + await new Promise(resolve => setTimeout(resolve, 100)) - while (status != PollTransformationStatus.TIMEOUT && !failureStates.includes(status)) { - try { - if (this.cancelPollingEnabled) { - // Reset the flag - this.cancelPollingEnabled = false - return { - TransformationJob: response.transformationJob, - } as GetTransformResponse - } - const apiStartTime = Date.now() + const command = new CreateWorkspaceCommand({ + name: name || undefined, + idempotencyToken: uuidv4(), + }) - const getCodeTransformationRequest = { - transformationJobId: request.TransformationJobId, - } as GetTransformationRequest - response = await this.serviceManager - .getCodewhispererService() - .codeModernizerGetCodeTransformation(getCodeTransformationRequest) - this.logging.log('Transformation status: ' + response.transformationJob?.status) + await this.addBearerTokenToCommand(command) + const response = await this.atxClient!.send(command) - if (validExitStatus.includes(status)) { - this.logging.log('Exiting polling for transformation plan with transformation status: ' + status) - break - } + const workspaceId = response.workspace?.id + const workspaceName = response.workspace?.name + this.logging.log(`CreateWorkspace: SUCCESS - Created workspace ${workspaceId}`) + this.logging.log(`CreateWorkspace: Workspace name: ${workspaceName || 'not provided'}`) - status = response.transformationJob?.status! - await this.sleep(10 * 1000) - timer += 10 + if (workspaceId && workspaceName) { + return `${workspaceId}|${workspaceName}` + } + return workspaceId || null + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error' + this.logging.error(`CreateWorkspace attempt ${attempt} error: ${errorMessage}`) - if (timer > 24 * 3600 * 1000) { - status = PollTransformationStatus.TIMEOUT - break - } - getTransformAttempt = 0 // a successful polling will reset attempt - } catch (e: any) { - const errorMessage = (e as Error).message ?? 'Error in GetTransformation API call' - this.logging.log('Error polling transformation job from the server: ' + errorMessage) + if (errorMessage.includes('User to tenant mapping does not exist') && attempt < 3) { + this.logging.log(`Retrying CreateWorkspace due to tenant mapping error...`) + this.atxClient = null + this.cachedApplicationUrl = null + await this.ensureATXClient() + continue + } - getTransformAttempt += 1 - if (getTransformAttempt >= getTransformMaxAttempts) { - this.logging.log(`GetTransformation failed after ${getTransformMaxAttempts} attempts.`) - status = PollTransformationStatus.NOT_FOUND - break + if (attempt === 3) { + this.logging.error(`CreateWorkspace failed after ${attempt} attempts`) + return null + } } - - const expDelayMs = this.getExpDelayForApiRetryMs(getTransformAttempt) - this.logging.log( - `Attempt ${getTransformAttempt}/${getTransformMaxAttempts} to get transformation plan failed, retry in ${expDelayMs} seconds` - ) - await this.sleep(expDelayMs * 1000) } + + return null + } catch (error) { + this.logging.error(`CreateWorkspace error: ${error instanceof Error ? error.message : 'Unknown error'}`) + return null } - this.logging.log('Returning response from server : ' + JSON.stringify(response)) - this.logSuggestionForFailureResponse(request, response.transformationJob, failureStates) - return { - TransformationJob: response.transformationJob, - } as GetTransformResponse } - async downloadExportResultArchive(exportId: string, saveToDir: string) { - let result + /** + * Lists Hitls using FES client + */ + private async listHitlsFESClient(workspaceId: string, jobId: string): Promise { try { - result = await this.serviceManager.getStreamingClient().exportResultArchive({ - exportId, - exportIntent: ExportIntent.TRANSFORMATION, + this.logging.log('=== ATX FES ListHitls Operation (FES Client) ===') + this.logging.log(`Listing Hitls for job: ${jobId} in workspace: ${workspaceId}`) + + if (!(await this.ensureATXClient())) { + this.logging.error('ListHitls: Failed to initialize ATX client') + return null + } + + const command = new ListHitlTasksCommand({ + workspaceId: workspaceId, + jobId: jobId, + taskType: 'NORMAL', }) - const buffer = [] - this.logging.log('Artifact was successfully downloaded.') + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse('ListHitls', result) - if (result.body === undefined) { - throw new Error('Empty response from CodeWhisperer streaming service.') - } + this.logging.log(`ListHitls: SUCCESS - Found ${result.hitlTasks?.length || 0} HITL_FROM_USER artifacts`) + return result.hitlTasks || [] + } catch (error) { + this.logging.error(`ListHitls error: ${error instanceof Error ? error.message : 'Unknown error'}`) + return null + } + } - for await (const chunk of result.body) { - if (chunk.binaryPayloadEvent) { - const chunkData = chunk.binaryPayloadEvent - if (chunkData.bytes) { - buffer.push(chunkData.bytes) - } - } + /** + * Update Hitl using FES client + */ + private async updateHitlFESClient( + workspaceId: string, + jobId: string, + taskId: string, + humanArtifactId: string + ): Promise { + try { + this.logging.log('=== ATX FES UpdateHitl Operation (FES Client) ===') + this.logging.log(`Updating Hitl: ${taskId} for job: ${jobId} in workspace: ${workspaceId}`) + + if (!(await this.ensureATXClient())) { + this.logging.error('UpdateHitl: Failed to initialize ATX client') + return null } - const saveToWorkspace = path.join(saveToDir, workspaceFolderName) - this.logging.log(`Identified path of directory to save artifacts is ${saveToDir}`) - const pathContainingArchive = await this.archivePathGenerator(exportId, buffer, saveToWorkspace) - this.logging.log('PathContainingArchive :' + pathContainingArchive) - return { - PathTosave: pathContainingArchive, - } as DownloadArtifactsResponse + + const command = new UpdateHitlTaskCommand({ + workspaceId: workspaceId, + jobId: jobId, + taskId: taskId, + humanArtifact: { + artifactId: humanArtifactId, + }, + postUpdateAction: 'SEND_FOR_APPROVAL', + }) + + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse('UpdateHitl', result) + + this.logging.log(`UpdateHitl: SUCCESS - task status: ${result.status || 'UNKNOWN'} `) + return result } catch (error) { - const errorMessage = (error as Error).message ?? 'Failed to download the artifacts' - return { - Error: errorMessage, - } as DownloadArtifactsResponse + this.logging.error(`ListHitls error: ${error instanceof Error ? error.message : 'Unknown error'}`) + return null } } - async cancelPollingAsync() { - this.cancelPollingEnabled = true + private async getHitlStatusFES(workspaceId: string, jobId: string, taskId: string): Promise { + try { + this.logging.log('=== ATX FES Get Hitl Operation (FES Client) ===') + this.logging.log(`Getting Hitl: ${jobId} in workspace: ${workspaceId}`) + + if (!(await this.ensureATXClient())) { + this.logging.error('GetHitl: Failed to initialize ATX client') + return null + } + + const command = new GetHitlTaskCommand({ + workspaceId: workspaceId, + jobId: jobId, + taskId: taskId, + }) + + await this.addBearerTokenToCommand(command) + const result = await this.atxClient!.send(command) + this.logATXFESResponse('Get Hitl', result) + + this.logging.log(`GetHitl: SUCCESS - Job data received`) + return result.task || null + } catch (error) { + this.logging.error(`GetHitl error: ${error instanceof Error ? error.message : 'Unknown error'}`) + return null + } } - async extractAllEntriesTo(pathContainingArchive: string, zipEntries: AdmZip.IZipEntry[]) { - for (const entry of zipEntries) { - try { - const entryPath = path.join(pathContainingArchive, entry.entryName) - if (entry.isDirectory) { - await fs.promises.mkdir(entryPath, { recursive: true }) - } else { - const parentDir = path.dirname(entryPath) - await fs.promises.mkdir(parentDir, { recursive: true }) - await fs.promises.writeFile(entryPath, entry.getData()) - } - } catch (extractError: any) { - if (extractError instanceof Error && 'code' in extractError && extractError.code === 'ENOENT') { - this.logging.log(`Attempted to extract a file that does not exist : ${entry.entryName}`) + private async pollHitlFESClient(workspaceId: string, jobId: string, taskId: string): Promise { + this.logging.log('Starting polling for hitl after upload') + + try { + var count = 0 + while (count < 300) { + const jobStatus = await this.getHitlStatusFES(workspaceId, jobId, taskId) + + if (jobStatus && jobStatus.status == 'CLOSED') { + this.logging.log('Hitl Polling get status CLOSED') + return true + } else if (jobStatus && jobStatus.status == 'CANCELED') { + // Fallback to placeholder if API call fails + this.logging.log('Hitl Polling get status CANCELED') + return false } else { - throw extractError + this.logging.log('Hitl polling in progress....') + await this.sleep(10 * 1000) + count++ } } + + this.logging.log('Returning false, 300 polls and no approve or reject') + return false + } catch (error) { + this.logging.error(`Hitl polling error: ${error instanceof Error ? error.message : 'Unknown error'}`) + // Return placeholder on error + return false } } - async archivePathGenerator(exportId: string, buffer: Uint8Array[], saveToDir: string) { + async getEditablePlan(request: GetEditablePlanRequest): Promise { + this.logging.log('Getting editable plan for path') + try { - const tempDir = path.join(saveToDir, exportId) - const pathToArchive = path.join(tempDir, 'ExportResultsArchive.zip') + this.logging.log('=== ATX FES Get Editable Plan ===') + + // Get workspace and job IDs from cached data + const workspaceId = this.currentWorkspaceId + const jobId = request.TransformationJobId + + if (!workspaceId) { + throw new Error('No workspace ID available for ATX FES download') + } + + // List hitls + + const hitls = await this.listHitlsFESClient(workspaceId, jobId) + + if (hitls && hitls.length != 1) { + this.logging.log(`ATX FES Job ${jobId} - Found ${hitls.length} hitls`) + } else if (!hitls) { + this.logging.log(`ATX FES Job ${jobId} - no or many hitls available for download (expects 1 hitl)`) + + // Need to remove this later + + return { + Status: true, + PlanPath: path.join( + request.SolutionRootPath, + workspaceFolderName, + 'temp', + 'transformation-plan.md' + ), + ReportPath: path.join( + request.SolutionRootPath, + workspaceFolderName, + 'temp', + 'assessment-report.json' + ), + } as GetEditablePlanResponse + + // throw new Error("no or many HITLE_FROM_USER artifacts available for download (expects 1 artifact)") + } + + const hitl = hitls[0] + + this.cachedHitlId = hitl.taskId + + const downloadInfo = await this.createArtifactDownloadUrlFESClient( + workspaceId, + jobId, + hitl.agentArtifact.artifactId + ) + + if (!downloadInfo) { + throw new Error('Failed to get ATX FES download URL') + } + + this.logging.log(`ATX FES Job ${jobId} - Artifact download URL created: ${downloadInfo.downloadUrl}`) + + const headers = {} + if (downloadInfo.requestHeaders) { + downloadInfo.requestHeaders.host = + downloadInfo.requestHeaders.host?.[0] ?? downloadInfo.requestHeaders.host + } + + // Download from S3 + const got = await import('got') + const response = await got.default.get(downloadInfo.downloadUrl, { + headers: downloadInfo.requestHeaders || {}, + timeout: { request: 300000 }, // 5 minutes + }) + + // Save, extract, and return paths + const buffer = [Buffer.from(response.body)] + + const tempDir = path.join(request.SolutionRootPath, workspaceFolderName, request.TransformationJobId) await this.directoryExists(tempDir) + const pathToArchive = path.join(tempDir, 'downloaded-transformation-plans.json') await fs.writeFileSync(pathToArchive, Buffer.concat(buffer)) - let pathContainingArchive = '' - pathContainingArchive = path.dirname(pathToArchive) - const zip = new AdmZip(pathToArchive) - const zipEntries = zip.getEntries() - await this.extractAllEntriesTo(pathContainingArchive, zipEntries) - return pathContainingArchive + this.logging.log(`Downloaded plan to ${pathToArchive}`) + + // Temporary + const pathToPlan = pathToArchive + const pathToReport = pathToArchive + // let pathContainingArchive = '' + // pathContainingArchive = path.dirname(pathToArchive) + // const zip = new AdmZip(pathToArchive) + // const zipEntries = zip.getEntries() + // await this.extractAllEntriesTo(pathContainingArchive, zipEntries) + + // const extractedPaths = zipEntries.map(entry => path.join(pathContainingArchive, entry.entryName)) + + // const pathToPlan = extractedPaths.find(filePath => path.basename(filePath) === 'transformation-plan.md') + // const pathToReport = extractedPaths.find(filePath => path.basename(filePath) === 'assessment-report.json') + + return { + Status: true, + PlanPath: pathToPlan, + ReportPath: pathToReport, + } as GetEditablePlanResponse } catch (error) { - this.logging.log(`error received ${JSON.stringify(error)}`) - return '' + this.logging.error(`ATX FES download failed: ${error instanceof Error ? error.message : 'Unknown error'}`) + return { + Status: false, + } as GetEditablePlanResponse } } - async directoryExists(directoryPath: any) { + async uploadEditablePlan(request: UploadEditablePlanRequest): Promise { + this.logging.log('Uploading editable plan with') + this.logging.log(JSON.stringify(request, null, 2)) try { - await fs.accessSync(directoryPath) + // zip transformation-plan + const pathToPlan = request.PlanPath + // const pathToZip = path.join(tempDir, 'transformation-plan.zip') + + // await this.zipFile(pathToPlan, pathToZip) + + const workspaceId = this.currentWorkspaceId + const jobId = request.TransformationJobId + + if (!workspaceId) { + throw new Error('No workspace ID available for ATX FES download') + } + + // List Hitls + + // const hitls = await this.listHitlsFESClient(workspaceId, jobId) + + // if (hitls && hitls.length != 1) { + // this.logging.log(`ATX FES Job ${jobId} - Found ${hitls.length} hitls`) + // } else if (!hitls) { + // this.logging.log(`ATX FES Job ${jobId} - no or many hitls available (expects 1 hitl)`) + // throw new Error('no or many hitls available (expects 1 hitl)') + // } + + // const hitl = hitls[0] + + // createartifactuploadurl + + this.logging.log('Creating ATX FES artifact upload URL for transformation-plan.zip...') + + const uploadResult = await this.createArtifactUploadUrlFESClient( + this.currentWorkspaceId!, + request.TransformationJobId, + pathToPlan, + 'HITL_FROM_USER', + 'JSON' + ) + if (!uploadResult) { + throw new Error('Failed to create ATX FES artifact upload URL') + } + + this.logging.log(`ATX FES Upload URL created successfully: ${uploadResult.uploadId}`) + + // Upload to S3 + this.logging.log('Uploading transformationplan to S3...') + const uploadSuccess = await this.uploadArtifactToS3ATX( + pathToPlan, + uploadResult.uploadUrl, + uploadResult.requestHeaders + ) + if (!uploadSuccess) { + throw new Error('Failed to upload artifact to S3') + } + + this.logging.log('ATX FES S3 upload completed successfully') + + // CompleteArtifactUpload (using FES client) + this.logging.log('Completing ATX FES artifact upload for transformation plan') + const completeResult = await this.completeArtifactUploadFESClient( + this.currentWorkspaceId!, + request.TransformationJobId, + uploadResult.uploadId + ) + if (!completeResult) { + throw new Error('Failed to complete ATX FES artifact upload') + } + + // Update Hitl Task + + this.logging.log('Updating Hitl Task') + const updateResult = await this.updateHitlFESClient( + this.currentWorkspaceId!, + request.TransformationJobId, + this.cachedHitlId!, + uploadResult.uploadId + ) + if (!updateResult) { + throw new Error('Failed to update hitl') + } + + this.logging.log('ATX FES Updated hitl successfully') } catch (error) { - // Directory doesn't exist, create it - this.logging.log(`Directory doesn't exist, creating it ${directoryPath}`) - await fs.mkdirSync(directoryPath, { recursive: true }) + this.logging.error( + `Upload transformation plan failed: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + + // Return error response + return { + VerificationStatus: false, + } as UploadEditablePlanResponse } - } - getWorkspacePath(solutionRootPath: string): string { - const randomPath = uuidv4().substring(0, 8) - const workspacePath = path.join(solutionRootPath, workspaceFolderName, randomPath) - if (!fs.existsSync(workspacePath)) { - fs.mkdirSync(workspacePath, { recursive: true }) + // validate plan + + const validation = await this.pollHitlFESClient( + this.currentWorkspaceId!, + request.TransformationJobId, + this.cachedHitlId! + ) + + if (validation) { + return { + VerificationStatus: true, + } as UploadEditablePlanResponse } - return workspacePath - } - getExpDelayForApiRetryMs(attempt: number): number { - const exponentialDelayFactor = 2 - const exponentialDelay = 10 * Math.pow(exponentialDelayFactor, attempt) - const jitteredDelay = Math.floor(Math.random() * 10) - return exponentialDelay + jitteredDelay // returns in milliseconds + return { + VerificationStatus: false, + } as UploadEditablePlanResponse } - logSuggestionForFailureResponse( - request: GetTransformRequest, - job: TransformationJob | undefined, - failureStates: string[] - ) { - let status = job?.status ?? PollTransformationStatus.NOT_FOUND - let reason = job?.reason ?? '' - if (failureStates.includes(status)) { - let suggestion = '' - if (reason.toLowerCase().includes('build validation failed')) { - suggestion = - 'Please close Visual Studio, delete the directories where build artifacts are generated (e.g. bin and obj), and try running the transformation again.' - } - this.logging.log(`Transformation job for job ${request.TransformationJobId} is ${status} due to "${reason}". -                ${suggestion}`) - } + private async zipFile(sourceFilePath: string, outputZipPath: string): Promise { + const archive = archiver('zip', { zlib: { level: 9 } }) + const stream = fs.createWriteStream(outputZipPath) + + return new Promise((resolve, reject) => { + archive + .file(sourceFilePath, { name: path.basename(sourceFilePath) }) + .on('error', err => reject(err)) + .pipe(stream) + + stream.on('close', () => resolve()) + void archive.finalize() + }) } } diff --git a/server/aws-lsp-codewhisperer/src/shared/amazonQServiceManager/AmazonQTokenServiceManager.ts b/server/aws-lsp-codewhisperer/src/shared/amazonQServiceManager/AmazonQTokenServiceManager.ts index ee706c0b22..167546d4ae 100644 --- a/server/aws-lsp-codewhisperer/src/shared/amazonQServiceManager/AmazonQTokenServiceManager.ts +++ b/server/aws-lsp-codewhisperer/src/shared/amazonQServiceManager/AmazonQTokenServiceManager.ts @@ -633,6 +633,10 @@ export class AmazonQTokenServiceManager extends BaseAmazonQServiceManager< return this.serviceFactory } + public override isAWSTransformProfile(): boolean { + return this.activeIdcProfile?.arn?.includes(':transform:') || false + } + public getEnableDeveloperProfileSupport(): boolean { return this.enableDeveloperProfileSupport === undefined ? false : this.enableDeveloperProfileSupport } diff --git a/server/aws-lsp-codewhisperer/src/shared/amazonQServiceManager/BaseAmazonQServiceManager.ts b/server/aws-lsp-codewhisperer/src/shared/amazonQServiceManager/BaseAmazonQServiceManager.ts index d8aa1b0b48..e2a2d5c089 100644 --- a/server/aws-lsp-codewhisperer/src/shared/amazonQServiceManager/BaseAmazonQServiceManager.ts +++ b/server/aws-lsp-codewhisperer/src/shared/amazonQServiceManager/BaseAmazonQServiceManager.ts @@ -141,6 +141,10 @@ export abstract class BaseAmazonQServiceManager< return undefined // No-op / default implementation } + public isAWSTransformProfile(): boolean { + return false // Default implementation - not a Transform profile + } + protected updateCachedServiceConfig(): void { if (this.cachedCodewhispererService) { const customizationArn = this.configurationCache.getProperty('customizationArn') diff --git a/server/aws-lsp-codewhisperer/src/shared/amazonQServiceManager/qDeveloperProfiles.ts b/server/aws-lsp-codewhisperer/src/shared/amazonQServiceManager/qDeveloperProfiles.ts index 495a49b572..3083af10d2 100644 --- a/server/aws-lsp-codewhisperer/src/shared/amazonQServiceManager/qDeveloperProfiles.ts +++ b/server/aws-lsp-codewhisperer/src/shared/amazonQServiceManager/qDeveloperProfiles.ts @@ -7,14 +7,17 @@ import { SsoConnectionType, } from '@aws/language-server-runtimes/server-interface' import { isBool, isObject } from '../utils' -import { AWS_Q_ENDPOINTS } from '../../shared/constants' +import { AWS_Q_ENDPOINTS, ATX_FES_ENDPOINTS } from '../../shared/constants' import { CodeWhispererServiceToken } from '../codeWhispererService' import { AmazonQServiceProfileThrottlingError } from './errors' +import * as https from 'https' +import { URL } from 'url' export interface AmazonQDeveloperProfile { - arn: string | undefined - name: string | undefined + arn: string + name: string identityDetails?: IdentityDetails + applicationUrl?: string } interface IdentityDetails { @@ -25,6 +28,8 @@ export interface ListAllAvailableProfilesHandlerParams { connectionType: SsoConnectionType logging: Logging endpoints?: Map // override option for flexibility, we default to all (AWS_Q_ENDPOINTS) + atxFesEndpoints?: Map // ATX FES endpoints for Transform profiles + credentialsProvider?: any // Add credentials provider for ATX FES calls token: CancellationToken } @@ -37,7 +42,14 @@ const MAX_Q_DEVELOPER_PROFILES_PER_PAGE = 10 export const getListAllAvailableProfilesHandler = (service: (region: string, endpoint: string) => CodeWhispererServiceToken): ListAllAvailableProfilesHandler => - async ({ connectionType, logging, endpoints, token }): Promise => { + async ({ + connectionType, + logging, + endpoints, + atxFesEndpoints, + credentialsProvider, + token, + }): Promise => { if (!connectionType || connectionType !== 'identityCenter') { logging.debug('Connection type is not set or not identityCenter - returning empty response.') return [] @@ -45,42 +57,72 @@ export const getListAllAvailableProfilesHandler = let allProfiles: AmazonQDeveloperProfile[] = [] const qEndpoints = endpoints ?? AWS_Q_ENDPOINTS + const atxEndpoints = atxFesEndpoints ?? ATX_FES_ENDPOINTS - // Log all regions we're going to try - logging.log( - `Attempting to fetch profiles from ${qEndpoints.size} regions: ${Array.from(qEndpoints.keys()).join(', ')}` - ) + // Log dual authentication approach + logging.log('=== Dual Authentication Profile Discovery ===') + logging.log(`RTS endpoints (Q Developer): ${Array.from(qEndpoints.keys()).join(', ')}`) + logging.log(`ATX FES endpoints (Transform): ${Array.from(atxEndpoints.keys()).join(', ')}`) if (token.isCancellationRequested) { return [] } - const result = await Promise.allSettled( - Array.from(qEndpoints.entries(), ([region, endpoint]) => { - logging.log(`Creating service client for region: ${region}`) - const codeWhispererService = service(region, endpoint) - return fetchProfilesFromRegion(codeWhispererService, region, logging, token) - }) - ) + // Fetch from both RTS and ATX FES endpoints + const rtsPromises = Array.from(qEndpoints.entries()).map(([region, endpoint]) => { + logging.log(`Creating RTS service client for region: ${region}`) + const codeWhispererService = service(region, endpoint) + return fetchProfilesFromRTS(codeWhispererService, region, logging, token) + }) + + const atxPromises = Array.from(atxEndpoints.entries()).map(([region, endpoint]) => { + logging.log(`Creating ATX FES client for region: ${region}`) + return fetchProfilesFromATXFES(region, endpoint, logging, credentialsProvider || null, token) + }) + + const allPromises = [...rtsPromises, ...atxPromises] + const result = await Promise.allSettled(allPromises) if (token.isCancellationRequested) { return [] } - // Log detailed results from each region + // Log detailed results from each endpoint try { - result.forEach((settledResult, index) => { - const [region, endpoint] = Array.from(qEndpoints.entries())[index] + const rtsResults = result.slice(0, rtsPromises.length) + const atxResults = result.slice(rtsPromises.length) + + rtsResults.forEach((settledResult, index) => { + const [region] = Array.from(qEndpoints.entries())[index] if (settledResult.status === 'fulfilled') { const profiles = settledResult.value - logging.log(`Successfully fetched ${profiles.length} profiles from region: ${region}`) + logging.log(`RTS ${region}: Successfully fetched ${profiles.length} profiles`) + profiles.forEach(profile => { + const profileType = isAWSTransformProfile(profile.arn || '') ? 'Transform' : 'Q Developer' + logging.log(` RTS Profile: ${profile.name} (${profileType}) - ${profile.arn}`) + }) } else { - logging.error( - `Failed to fetch profiles from region: ${region}, error: ${settledResult.reason?.name || 'unknown'}, message: ${settledResult.reason?.message || 'No message'}` - ) + logging.error(`RTS ${region}: Failed - ${settledResult.reason?.message || 'unknown error'}`) + } + }) + + atxResults.forEach((settledResult, index) => { + const [region] = Array.from(atxEndpoints.entries())[index] + if (settledResult.status === 'fulfilled') { + const profiles = settledResult.value + logging.log(`ATX FES ${region}: Successfully fetched ${profiles.length} profiles`) + profiles.forEach(profile => { + logging.log( + ` ATX Profile: ${profile.name} (${profile.arn}) - applicationUrl: ${profile.applicationUrl || 'N/A'}` + ) + }) + } else { + logging.error(`ATX FES ${region}: Failed - ${settledResult.reason?.message || 'unknown error'}`) } }) - } catch (loggingError) {} + } catch (loggingError) { + logging.error(`Error in detailed logging: ${loggingError}`) + } const fulfilledResults = result.filter(settledResult => settledResult.status === 'fulfilled') const hasThrottlingError = result.some( @@ -94,19 +136,43 @@ export const getListAllAvailableProfilesHandler = logging.error(throttlingErrorMessage) throw new AmazonQServiceProfileThrottlingError(throttlingErrorMessage) } - throw new ResponseError(LSPErrorCodes.RequestFailed, `Failed to retrieve profiles from all queried regions`) + throw new ResponseError( + LSPErrorCodes.RequestFailed, + `Failed to retrieve profiles from all queried endpoints (RTS and ATX FES)` + ) } fulfilledResults.forEach(fulfilledResult => allProfiles.push(...fulfilledResult.value)) - // Log summary of all profiles fetched + // Log summary of all profiles fetched with dual authentication try { + logging.log(`=== Dual Authentication Summary ===`) logging.log(`Total profiles fetched: ${allProfiles.length}`) if (allProfiles.length > 0) { - logging.log(`Profile names: ${allProfiles.map(p => p.name).join(', ')}`) - logging.log(`Profile regions: ${allProfiles.map(p => p.identityDetails?.region).join(', ')}`) + const qDeveloperProfiles = allProfiles.filter(p => !isAWSTransformProfile(p.arn || '')) + const transformProfiles = allProfiles.filter(p => isAWSTransformProfile(p.arn || '')) + + logging.log(`Q Developer profiles: ${qDeveloperProfiles.length}`) + logging.log(`Transform profiles: ${transformProfiles.length}`) + + if (qDeveloperProfiles.length > 0) { + logging.log(`Q Developer profile names: ${qDeveloperProfiles.map(p => p.name).join(', ')}`) + } + if (transformProfiles.length > 0) { + logging.log(`Transform profile names: ${transformProfiles.map(p => p.name).join(', ')}`) + transformProfiles.forEach(profile => { + logging.log( + ` Transform Profile: ${profile.name} - applicationUrl: ${profile.applicationUrl || 'N/A'}` + ) + }) + + // Note: CreateJob will be called later when user clicks "Port Solution" + // Not during profile discovery + } } - } catch (loggingError) {} + } catch (loggingError) { + logging.error(`Error in summary logging: ${loggingError}`) + } // Check for partial throttling if (hasThrottlingError && allProfiles.length == 0) { @@ -117,7 +183,319 @@ export const getListAllAvailableProfilesHandler = return allProfiles } -async function fetchProfilesFromRegion( +const AWSQCapabilitiesKey = 'q' +const developerProfilesEnabledKey = 'developerProfiles' + +/** + * @returns true if AWSInitializationOptions has the Q developer profiles flag set explicitly to true + * + * @example + * The function expects to receive the following structure: + * ```ts + * { + * awsClientCapabilities?: { + * q?: { + * developerProfiles?: boolean + * } + * } + * } + * ``` + */ +export function signalsAWSQDeveloperProfilesEnabled(initializationOptions: AWSInitializationOptions): boolean { + const qCapabilities = initializationOptions.awsClientCapabilities?.[AWSQCapabilitiesKey] + + if ( + isObject(qCapabilities) && + !(qCapabilities instanceof Array) && + developerProfilesEnabledKey in qCapabilities && + isBool(qCapabilities[developerProfilesEnabledKey]) + ) { + return qCapabilities[developerProfilesEnabledKey] + } + + return false +} + +/** + * Determines if a profile ARN represents an AWS Transform profile + */ +function isAWSTransformProfile(profileArn: string): boolean { + return profileArn.includes(':transform:') +} + +/** + * Generates a UUID for idempotency tokens + */ +function generateUUID(): string { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0 + const v = c == 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) +} + +/** + * Creates a new Transform job via ATX FES CreateJob API + */ +async function createATXFESJob( + region: string, + endpoint: string, + logging: Logging, + credentialsProvider: any | null, + applicationUrl: string, + workspaceId: string = 'test-workspace-001' +): Promise<{ jobId: string; status: string } | null> { + try { + logging.log(`=== ATX FES CreateJob Operation ===`) + logging.log(`Starting CreateJob for workspace: ${workspaceId}`) + + if (!credentialsProvider || !credentialsProvider.hasCredentials('bearer')) { + logging.log(`CreateJob ${region}: No bearer token credentials available, skipping`) + return null + } + + // Get bearer token from credentials provider + const credentials = await credentialsProvider.getCredentials('bearer') + if (!credentials || !credentials.token) { + logging.log(`CreateJob ${region}: Failed to get bearer token, skipping`) + return null + } + + const bearerToken = credentials.token + logging.log(`CreateJob ${region}: Got bearer token, making API call`) + + // Parse endpoint URL + const url = new URL(endpoint) + + // Generate request body + const requestBody = JSON.stringify({ + workspaceId: workspaceId, // Add workspaceId to request body + objective: 'Transform .NET Framework project to .NET 8.0', + jobType: 'DOT_NET', + jobName: `transform-job-${Date.now()}`, + intent: 'LANGUAGE_UPGRADE', + idempotencyToken: generateUUID(), + }) + + // Prepare ATX FES headers for CreateJob + const headers = { + 'Content-Type': 'application/json; charset=UTF-8', + 'Content-Encoding': 'amz-1.0', + 'X-Amz-Target': 'com.amazon.elasticgumbyfrontendservice.ElasticGumbyFrontEndService.CreateJob', + Authorization: `Bearer ${bearerToken}`, + Origin: applicationUrl, + 'Content-Length': Buffer.byteLength(requestBody).toString(), + } + + const path = `/workspaces/${workspaceId}/jobs` + logging.log(`CreateJob ${region}: Making request to ${endpoint}${path}`) + logging.log(`CreateJob ${region}: Request body: ${requestBody}`) + logging.debug(`CreateJob ${region}: Headers: ${JSON.stringify(Object.keys(headers))}`) + + // Make the ATX FES CreateJob API call using Node.js https + const response = await new Promise<{ statusCode: number; statusMessage: string; data: string }>( + (resolve, reject) => { + const req = https.request( + { + hostname: url.hostname, + port: url.port || 443, + path: path, + method: 'POST', + headers: headers, + }, + res => { + let data = '' + res.on('data', chunk => { + data += chunk + }) + res.on('end', () => { + resolve({ + statusCode: res.statusCode || 0, + statusMessage: res.statusMessage || '', + data: data, + }) + }) + } + ) + + req.on('error', error => { + reject(error) + }) + + req.write(requestBody) + req.end() + } + ) + + logging.log(`CreateJob ${region}: Response status: ${response.statusCode} ${response.statusMessage}`) + logging.log(`CreateJob ${region}: Raw response: ${response.data}`) + + if (response.statusCode < 200 || response.statusCode >= 300) { + logging.error(`CreateJob ${region}: API call failed: ${response.statusCode} ${response.statusMessage}`) + logging.error(`CreateJob ${region}: Error response: ${response.data}`) + return null + } + + const data = JSON.parse(response.data) + logging.log(`CreateJob ${region}: Parsed response: ${JSON.stringify(data)}`) + + // Extract job information + const jobId = data.jobId + const status = data.status + + if (jobId) { + logging.log(`CreateJob ${region}: SUCCESS - Job created with ID: ${jobId}, Status: ${status}`) + return { jobId, status } + } else { + logging.error(`CreateJob ${region}: No jobId in response`) + return null + } + } catch (error) { + logging.error(`Error in CreateJob for region: ${region}`) + logging.error(`CreateJob error: ${error instanceof Error ? error.message : 'Unknown error'}`) + logging.log(`CreateJob error details: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`) + return null + } +} + +/** + * Fetches profiles from ATX FES endpoints for Transform profiles + */ +async function fetchProfilesFromATXFES( + region: string, + endpoint: string, + logging: Logging, + credentialsProvider: any | null, + token: CancellationToken +): Promise { + try { + logging.log(`Starting ATX FES profile fetch from region: ${region}`) + + if (token.isCancellationRequested) { + logging.debug(`Cancellation requested during ATX FES profile fetch from region: ${region}`) + return [] + } + + // Check if we have bearer token credentials + if (!credentialsProvider || !credentialsProvider.hasCredentials('bearer')) { + logging.log(`ATX FES ${region}: No bearer token credentials available, skipping`) + return [] + } + + // Get bearer token from credentials provider + const credentials = await credentialsProvider.getCredentials('bearer') + if (!credentials || !credentials.token) { + logging.log(`ATX FES ${region}: Failed to get bearer token, skipping`) + return [] + } + + const bearerToken = credentials.token + logging.log(`ATX FES ${region}: Got bearer token, making API call`) + + // Parse endpoint URL + const url = new URL(endpoint) + + // Prepare request body + const requestBody = JSON.stringify({}) + + // Prepare ATX FES headers + const headers = { + 'Content-Type': 'application/json; charset=UTF-8', + 'Content-Encoding': 'amz-1.0', + 'X-Amz-Target': 'com.amazon.elasticgumbyfrontendservice.ElasticGumbyFrontEndService.ListAvailableProfiles', + Authorization: `Bearer ${bearerToken}`, + 'Content-Length': Buffer.byteLength(requestBody).toString(), + } + + logging.log(`ATX FES ${region}: Making request to ${endpoint}`) + logging.log(`ATX FES ${region}: Request body: ${requestBody}`) + logging.log(`ATX FES ${region}: Full headers: ${JSON.stringify(headers, null, 2)}`) + logging.log(`ATX FES ${region}: Bearer token length: ${bearerToken.length}`) + logging.log(`ATX FES ${region}: Bearer token prefix: ${bearerToken.substring(0, 20)}...`) + + // Make the ATX FES API call using Node.js https + const response = await new Promise<{ statusCode: number; statusMessage: string; data: string; headers: any }>( + (resolve, reject) => { + const req = https.request( + { + hostname: url.hostname, + port: url.port || 443, + path: url.pathname + url.search, + method: 'POST', + headers: headers, + }, + res => { + let data = '' + res.on('data', chunk => { + data += chunk + }) + res.on('end', () => { + resolve({ + statusCode: res.statusCode || 0, + statusMessage: res.statusMessage || '', + data: data, + headers: res.headers, + }) + }) + } + ) + + req.on('error', error => { + reject(error) + }) + + req.write(requestBody) + req.end() + } + ) + + logging.log(`ATX FES ${region}: Response status: ${response.statusCode} ${response.statusMessage}`) + logging.log(`ATX FES ${region}: Response headers: ${JSON.stringify(response.headers)}`) + logging.log(`ATX FES ${region}: Raw response body: ${response.data}`) + + if (response.statusCode < 200 || response.statusCode >= 300) { + logging.error(`ATX FES ${region}: API call failed: ${response.statusCode} ${response.statusMessage}`) + logging.error(`ATX FES ${region}: Error response: ${response.data}`) + logging.error( + `ATX FES ${region}: Full error context - endpoint: ${endpoint}, bearerToken length: ${bearerToken.length}` + ) + throw new Error(`ATX FES API call failed: ${response.statusCode} ${response.statusMessage}`) + } + + const data = JSON.parse(response.data) + logging.log(`ATX FES ${region}: Raw response: ${JSON.stringify(data)}`) + + // Parse ATX FES response + const profiles = + data.profiles?.map((profile: any) => ({ + arn: profile.arn, + name: profile.profileName, + identityDetails: { region }, + applicationUrl: profile.applicationUrl, + })) || [] + + logging.log(`ATX FES ${region}: Parsed ${profiles.length} Transform profiles`) + profiles.forEach((profile: AmazonQDeveloperProfile) => { + logging.log( + ` ATX FES Profile: ${profile.name} (${profile.arn}) - applicationUrl: ${profile.applicationUrl || 'N/A'}` + ) + }) + + return profiles + } catch (error) { + logging.error(`Error fetching profiles from ATX FES region: ${region}`) + logging.error(`ATX FES error: ${error instanceof Error ? error.message : 'Unknown error'}`) + logging.log(`ATX FES error details: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`) + + // Don't throw - return empty array to allow other endpoints to succeed + return [] + } +} + +/** + * Renamed the original function to be more specific + */ +async function fetchProfilesFromRTS( service: CodeWhispererServiceToken, region: string, logging: Logging, @@ -128,13 +506,13 @@ async function fetchProfilesFromRegion( let numberOfPages = 0 try { - logging.log(`Starting profile fetch from region: ${region}`) + logging.log(`Starting RTS profile fetch from region: ${region}`) do { - logging.debug(`Fetching profiles from region: ${region} (page: ${numberOfPages + 1})`) + logging.debug(`Fetching RTS profiles from region: ${region} (page: ${numberOfPages + 1})`) if (token.isCancellationRequested) { - logging.debug(`Cancellation requested during profile fetch from region: ${region}`) + logging.debug(`Cancellation requested during RTS profile fetch from region: ${region}`) return allRegionalProfiles } @@ -142,78 +520,50 @@ async function fetchProfilesFromRegion( maxResults: MAX_Q_DEVELOPER_PROFILES_PER_PAGE, nextToken: nextToken, } - logging.debug(`Request params for region ${region}: ${JSON.stringify(requestParams)}`) + logging.debug(`RTS request params for region ${region}: ${JSON.stringify(requestParams)}`) const response = await service.listAvailableProfiles(requestParams) - logging.debug(`Raw response from ${region}: ${JSON.stringify(response)}`) + logging.debug(`RTS raw response from ${region}: ${JSON.stringify(response)}`) const profiles = response.profiles?.map(profile => ({ - arn: profile.arn, - name: profile.profileName, + arn: profile.arn || '', + name: profile.profileName || '', identityDetails: { region, }, - })) ?? [] + applicationUrl: (profile as any).applicationUrl, + })) || [] - logging.log(`Fetched ${profiles.length} profiles from ${region} (page: ${numberOfPages + 1})`) + logging.log(`RTS ${region}: Fetched ${profiles.length} profiles (page: ${numberOfPages + 1})`) if (profiles.length > 0) { - logging.log(`Profile names from ${region}: ${profiles.map(p => p.name).join(', ')}`) + logging.log(`RTS profile names from ${region}: ${profiles.map(p => p.name).join(', ')}`) + profiles.forEach(profile => { + const profileType = isAWSTransformProfile(profile.arn || '') ? 'Transform' : 'Q Developer' + logging.log( + ` RTS Profile: ${profile.name} (${profileType}), applicationUrl: ${profile.applicationUrl || 'N/A'}` + ) + }) } allRegionalProfiles.push(...profiles) nextToken = response.nextToken if (nextToken) { - logging.debug(`Next token received from ${region}: ${nextToken.substring(0, 10)}...`) + logging.debug(`Next token received from RTS ${region}: ${nextToken.substring(0, 10)}...`) } else { - logging.debug(`No next token received from ${region}, pagination complete`) + logging.debug(`No next token received from RTS ${region}, pagination complete`) } numberOfPages++ } while (nextToken !== undefined && numberOfPages < MAX_Q_DEVELOPER_PROFILE_PAGES) - logging.log(`Completed fetching profiles from ${region}, total profiles: ${allRegionalProfiles.length}`) + logging.log(`Completed RTS profile fetch from ${region}, total profiles: ${allRegionalProfiles.length}`) return allRegionalProfiles } catch (error) { - // Enhanced error logging with complete error object - logging.error(`Error fetching profiles from region: ${region}`) - logging.log(`Complete error object: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`) - + logging.error(`Error fetching RTS profiles from region: ${region}`) + logging.log(`RTS error object: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`) throw error } } - -const AWSQCapabilitiesKey = 'q' -const developerProfilesEnabledKey = 'developerProfiles' - -/** - * @returns true if AWSInitializationOptions has the Q developer profiles flag set explicitly to true - * - * @example - * The function expects to receive the following structure: - * ```ts - * { - * awsClientCapabilities?: { - * q?: { - * developerProfiles?: boolean - * } - * } - * } - * ``` - */ -export function signalsAWSQDeveloperProfilesEnabled(initializationOptions: AWSInitializationOptions): boolean { - const qCapabilities = initializationOptions.awsClientCapabilities?.[AWSQCapabilitiesKey] - - if ( - isObject(qCapabilities) && - !(qCapabilities instanceof Array) && - developerProfilesEnabledKey in qCapabilities && - isBool(qCapabilities[developerProfilesEnabledKey]) - ) { - return qCapabilities[developerProfilesEnabledKey] - } - - return false -} diff --git a/server/aws-lsp-codewhisperer/src/shared/codeWhispererService.ts b/server/aws-lsp-codewhisperer/src/shared/codeWhispererService.ts index af9bc78ac7..38dea39ecd 100644 --- a/server/aws-lsp-codewhisperer/src/shared/codeWhispererService.ts +++ b/server/aws-lsp-codewhisperer/src/shared/codeWhispererService.ts @@ -219,6 +219,7 @@ export abstract class CodeWhispererServiceBase { public shareCodeWhispererContentWithAWS = false public customizationArn?: string public profileArn?: string + public tenantUrl?: string abstract client: CodeWhispererSigv4Client | CodeWhispererTokenClient inflightRequests: Set = new Set() diff --git a/server/aws-lsp-codewhisperer/src/shared/constants.ts b/server/aws-lsp-codewhisperer/src/shared/constants.ts index 33f61a079f..901a5f52fc 100644 --- a/server/aws-lsp-codewhisperer/src/shared/constants.ts +++ b/server/aws-lsp-codewhisperer/src/shared/constants.ts @@ -9,9 +9,23 @@ export const DEFAULT_AWS_Q_REGION = 'us-east-1' export const AWS_Q_ENDPOINTS = new Map([ [DEFAULT_AWS_Q_REGION, DEFAULT_AWS_Q_ENDPOINT_URL], ['us-east-1', 'https://codewhisperer.us-east-1.amazonaws.com/'], + ['us-west-2', 'https://codewhisperer.us-west-2.amazonaws.com/'], ['eu-central-1', 'https://q.eu-central-1.amazonaws.com/'], ]) +// ATX FES endpoints for Transform profiles +// Only IAD (us-east-1) and FRA (eu-central-1) are supported by IDE +export const ATX_FES_ENDPOINTS = new Map([ + //['us-west-2', 'https://api.transform-gamma.us-west-2.on.aws/'], // PDX + ['us-east-1', 'https://api.transform-gamma.us-east-1.on.aws/'], // IAD + // Note: FRA (eu-central-1) endpoint may not be available yet in gamma stage + // ['eu-central-1', 'https://api.transform-gamma.eu-central-1.on.aws/'], // FRA +]) + +// ATX FES Configuration - Change these values to switch environments +export const DEFAULT_ATX_FES_REGION = 'us-east-1' +export const DEFAULT_ATX_FES_ENDPOINT = 'https://api.transform-gamma.us-east-1.on.aws' + export const AWS_Q_REGION_ENV_VAR = 'AWS_Q_REGION' export const AWS_Q_ENDPOINT_URL_ENV_VAR = 'AWS_Q_ENDPOINT_URL'