@@ -18,22 +18,24 @@ public class TspClientUpdateTool : MCPTool
1818 private readonly ILogger < TspClientUpdateTool > logger ;
1919 private readonly IOutputHelper output ;
2020 private readonly IClientUpdateLanguageServiceResolver languageServiceResolver ;
21- private readonly Argument < string > specPathArg = new ( name : "spec-path" , description : "Path to the .tsp specification file" ) { Arity = ArgumentArity . ExactlyOne } ;
21+ private readonly ITspClientHelper tspClientHelper ;
22+ private readonly Argument < string > updateCommitSha = new ( name : "update-commit-sha" , description : "SHA of the commit to apply update changes for" ) { Arity = ArgumentArity . ExactlyOne } ;
2223 private readonly Option < string ? > newGenOpt = new ( [ "--new-gen" ] , ( ) => "./tmpgen" , "Directory for regenerated TypeSpec output (optional)" ) ;
2324
24- public TspClientUpdateTool ( ILogger < TspClientUpdateTool > logger , IOutputHelper output , IClientUpdateLanguageServiceResolver languageServiceResolver )
25+ public TspClientUpdateTool ( ILogger < TspClientUpdateTool > logger , IOutputHelper output , IClientUpdateLanguageServiceResolver languageServiceResolver , ITspClientHelper tspClientHelper )
2526 {
2627 this . logger = logger ;
2728 this . output = output ;
2829 this . languageServiceResolver = languageServiceResolver ;
30+ this . tspClientHelper = tspClientHelper ;
2931 CommandHierarchy = [ SharedCommandGroups . TypeSpec ] ;
3032 }
3133
3234 public override Command GetCommand ( )
3335 {
3436 var cmd = new Command ( "customized-update" ,
3537 description : "Update customized TypeSpec-generated client code. Runs the full pipeline by default: regenerate -> diff -> map -> propose -> apply" ) ;
36- cmd . AddArgument ( specPathArg ) ;
38+ cmd . AddArgument ( updateCommitSha ) ;
3739 cmd . AddOption ( SharedOptions . PackagePath ) ;
3840 cmd . AddOption ( newGenOpt ) ;
3941 cmd . SetHandler ( async ctx => await HandleUpdate ( ctx , ctx . GetCancellationToken ( ) ) ) ;
@@ -43,43 +45,31 @@ public override Command GetCommand()
4345 public override Task HandleCommand ( InvocationContext ctx , CancellationToken ct ) => Task . CompletedTask ;
4446
4547 [ McpServerTool ( Name = "azsdk_tsp_update" ) , Description ( "Update customized TypeSpec-generated client code" ) ]
46- public async Task < TspClientUpdateResponse > UpdateAsync ( string specPath , string packagePath , CancellationToken ct = default )
48+ public Task < TspClientUpdateResponse > UpdateAsync ( string commitSha , string packagePath , CancellationToken ct = default )
49+ => RunUpdateAsync ( commitSha , packagePath , newGenPath : null , ct ) ;
50+
51+ private async Task < TspClientUpdateResponse > RunUpdateAsync ( string commitSha , string packagePath , string ? newGenPath , CancellationToken ct )
4752 {
4853 try
4954 {
50- logger . LogInformation ( $ "Starting client update for package at: { packagePath } " ) ;
55+ logger . LogInformation ( "Starting client update for package at: {packagePath} (regenDir: {regenDir})" , packagePath , newGenPath ) ;
5156 if ( ! Directory . Exists ( packagePath ) )
5257 {
5358 SetFailure ( 1 ) ;
54- return new TspClientUpdateResponse
55- {
56- ErrorCode = "1" ,
57- ResponseError = $ "Package path does not exist: { packagePath } ",
58- Message = ""
59- } ;
59+ return new TspClientUpdateResponse { ErrorCode = "1" , ResponseError = $ "Package path does not exist: { packagePath } " } ;
6060 }
61- if ( string . IsNullOrWhiteSpace ( specPath ) )
61+ if ( string . IsNullOrWhiteSpace ( commitSha ) )
6262 {
6363 SetFailure ( 1 ) ;
64- return new TspClientUpdateResponse
65- {
66- ErrorCode = "1" ,
67- ResponseError = $ "Spec path is required.",
68- Message = ""
69- } ;
64+ return new TspClientUpdateResponse { ErrorCode = "1" , ResponseError = "Commit SHA is required." } ;
7065 }
7166 var resolved = await languageServiceResolver . ResolveAsync ( packagePath , ct ) ;
7267 if ( resolved == null )
7368 {
7469 SetFailure ( 1 ) ;
75- return new TspClientUpdateResponse
76- {
77- ErrorCode = "NoLanguageService" ,
78- ResponseError = "Could not resolve a client update language service." ,
79- Message = ""
80- } ;
70+ return new TspClientUpdateResponse { ErrorCode = "NoLanguageService" , ResponseError = "Could not resolve a client update language service." } ;
8171 }
82- return await UpdateCoreAsync ( specPath , packagePath , resolved , ct ) ;
72+ return await UpdateCoreAsync ( commitSha , packagePath , resolved , ct , newGenPath ) ;
8373 }
8474 catch ( Exception ex )
8575 {
@@ -88,12 +78,42 @@ public async Task<TspClientUpdateResponse> UpdateAsync(string specPath, string p
8878 }
8979 }
9080
91- private async Task < TspClientUpdateResponse > UpdateCoreAsync ( string specPath , string packagePath , IClientUpdateLanguageService languageService , CancellationToken ct )
81+ private async Task < TspClientUpdateResponse > UpdateCoreAsync ( string commitSha , string packagePath , IClientUpdateLanguageService languageService , CancellationToken ct , string ? newGenPath )
9282 {
93- var session = new ClientUpdateSessionState { SpecPath = specPath } ;
83+ var session = new ClientUpdateSessionState { SpecPath = commitSha } ;
84+
85+ // Determine output directory for new generation: use provided newGenPath (CLI option) or fallback.
86+ var regenDir = ResolveRegenDirectory ( packagePath , newGenPath ) ;
87+ if ( ! Directory . Exists ( regenDir ) )
88+ {
89+ Directory . CreateDirectory ( regenDir ) ;
90+ }
91+ session . NewGeneratedPath = regenDir ;
92+
93+ // Locate the existing tsp-location.yaml file within the provided packagePath and overwrite the commit: value with the new sha
94+ var tspLocationPath = Path . Combine ( packagePath , "tsp-location.yaml" ) ;
95+ if ( File . Exists ( tspLocationPath ) )
96+ {
97+ var tspLocationContent = await File . ReadAllTextAsync ( tspLocationPath , ct ) ;
98+ tspLocationContent = tspLocationContent . Replace ( "commit: " , $ "commit: { commitSha } ") ;
99+ await File . WriteAllTextAsync ( tspLocationPath , tspLocationContent , ct ) ;
100+ }
94101
95- // Regenerate (placeholder)
102+ // Invoke tsp-client update
103+ var regenResult = await tspClientHelper . UpdateGenerationAsync ( tspLocationPath , regenDir , isCli : false , ct ) ;
104+ if ( ! regenResult . IsSuccessful )
105+ {
106+ SetFailure ( 1 ) ;
107+ session . LastStage = UpdateStage . Failed ;
108+ return new TspClientUpdateResponse
109+ {
110+ Session = session ,
111+ ErrorCode = "RegenerateFailed" ,
112+ ResponseError = regenResult . ResponseError
113+ } ;
114+ }
96115 session . LastStage = UpdateStage . Regenerated ;
116+ // Now after regeneration, we have old generated at packagePath, new generation at regenDir to perform a diff
97117
98118 var apiChanges = await languageService . DiffAsync ( packagePath , session . NewGeneratedPath ) ;
99119 session . LastStage = UpdateStage . Diffed ;
@@ -154,12 +174,13 @@ private static async Task<TspClientUpdateResponse> ValidateWithAutoFixAsync(Clie
154174
155175 private async Task HandleUpdate ( InvocationContext ctx , CancellationToken ct )
156176 {
157- var spec = ctx . ParseResult . GetValueForArgument ( specPathArg ) ;
177+ var spec = ctx . ParseResult . GetValueForArgument ( updateCommitSha ) ;
158178 var packagePath = ctx . ParseResult . GetValueForOption ( SharedOptions . PackagePath ) ;
179+ var newGenPath = ctx . ParseResult . GetValueForOption ( newGenOpt ) ;
159180 try
160181 {
161- logger . LogInformation ( $ "Starting client update for package at: { packagePath } " ) ;
162- var resp = await UpdateAsync ( spec , packagePath , ct ) ;
182+ logger . LogInformation ( "Starting client update (CLI) for package at: {packagePath} with new-gen: {newGenPath}" , packagePath , newGenPath ) ;
183+ var resp = await RunUpdateAsync ( spec , packagePath , newGenPath , ct ) ;
163184 output . Output ( resp ) ;
164185 }
165186 catch ( Exception ex )
@@ -168,4 +189,18 @@ private async Task HandleUpdate(InvocationContext ctx, CancellationToken ct)
168189 output . OutputError ( new TspClientUpdateResponse { ResponseError = ex . Message , ErrorCode = "ClientUpdateFailed" } ) ;
169190 }
170191 }
192+
193+ private static string ResolveRegenDirectory ( string packagePath , string ? newGenPath )
194+ {
195+ if ( string . IsNullOrWhiteSpace ( newGenPath ) )
196+ {
197+ return Path . Combine ( packagePath , "_generated-new" ) ;
198+ }
199+ // If user supplied a relative path, place it under the package path for isolation.
200+ if ( ! Path . IsPathRooted ( newGenPath ) )
201+ {
202+ return Path . GetFullPath ( Path . Combine ( packagePath , newGenPath ) ) ;
203+ }
204+ return Path . GetFullPath ( newGenPath ) ;
205+ }
171206}
0 commit comments