Skip to content

Commit 9366a5f

Browse files
committed
Add --field option to config command and standardize endpoint key
Introduce --field/-f to query single config fields from static or generated config. Standardize generated config to use "messagingEndpoint" (not "botMessagingEndpoint") and update all code/tests accordingly. Add TryGetConfigField helper and unit tests. Ensure backward compatibility by migrating legacy keys in MergeDynamicProperties.
1 parent 5b05e37 commit 9366a5f

6 files changed

Lines changed: 1092 additions & 879 deletions

File tree

src/Microsoft.Agents.A365.DevTools.Cli/Commands/ConfigCommand.cs

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,15 @@ private static Command CreateDisplaySubcommand(ILogger logger, string configDir)
223223
new[] { "--all", "-a" },
224224
description: "Display both static and generated configuration");
225225

226+
var fieldOption = new Option<string?>(
227+
new[] { "--field", "-f" },
228+
description: "Output the value of a single field (for example: --field messagingEndpoint)");
229+
226230
cmd.AddOption(generatedOption);
227231
cmd.AddOption(allOption);
232+
cmd.AddOption(fieldOption);
228233

229-
cmd.SetHandler(async (bool showGenerated, bool showAll) =>
234+
cmd.SetHandler(async (bool showGenerated, bool showAll, string? field) =>
230235
{
231236
try
232237
{
@@ -246,6 +251,22 @@ private static Command CreateDisplaySubcommand(ILogger logger, string configDir)
246251
bool displayStatic = !showGenerated || showAll;
247252
bool displayGenerated = showGenerated || showAll;
248253

254+
// --field: output a single value from the selected config and exit
255+
if (!string.IsNullOrWhiteSpace(field))
256+
{
257+
var value = TryGetConfigField(config, field, displayGenerated, displayStatic, logger, displayOptions);
258+
if (value != null)
259+
{
260+
Console.WriteLine(value);
261+
}
262+
else
263+
{
264+
Console.Error.WriteLine($"Field '{field}' not found in configuration.");
265+
Environment.Exit(1);
266+
}
267+
return;
268+
}
269+
249270
if (displayStatic)
250271
{
251272
if (showAll)
@@ -323,8 +344,58 @@ private static Command CreateDisplaySubcommand(ILogger logger, string configDir)
323344
{
324345
logger.LogError(ex, "Failed to display configuration: {Message}", ex.Message);
325346
}
326-
}, generatedOption, allOption);
347+
}, generatedOption, allOption, fieldOption);
327348

328349
return cmd;
329350
}
351+
352+
/// <summary>
353+
/// Looks up a single field by JSON key from config, searching generated config first
354+
/// (when checkGenerated is true) then static config (when checkStatic is true).
355+
/// Returns the string value, or raw JSON text for non-string values, or null if not found.
356+
/// </summary>
357+
internal static string? TryGetConfigField(
358+
Models.Agent365Config config,
359+
string field,
360+
bool checkGenerated,
361+
bool checkStatic,
362+
Microsoft.Extensions.Logging.ILogger logger,
363+
JsonSerializerOptions? serializerOptions = null)
364+
{
365+
var options = serializerOptions ?? new JsonSerializerOptions
366+
{
367+
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
368+
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
369+
};
370+
371+
if (checkGenerated)
372+
{
373+
var generatedConfig = config.GetGeneratedConfigForDisplay(logger);
374+
var generatedJson = JsonSerializer.Serialize(generatedConfig, options);
375+
using var generatedDoc = JsonDocument.Parse(generatedJson);
376+
if (generatedDoc.RootElement.TryGetProperty(field, out var generatedProp) &&
377+
generatedProp.ValueKind != JsonValueKind.Null)
378+
{
379+
return generatedProp.ValueKind == JsonValueKind.String
380+
? generatedProp.GetString()
381+
: generatedProp.GetRawText();
382+
}
383+
}
384+
385+
if (checkStatic)
386+
{
387+
var staticConfig = config.GetStaticConfig();
388+
var staticJson = JsonSerializer.Serialize(staticConfig, options);
389+
using var staticDoc = JsonDocument.Parse(staticJson);
390+
if (staticDoc.RootElement.TryGetProperty(field, out var staticProp) &&
391+
staticProp.ValueKind != JsonValueKind.Null)
392+
{
393+
return staticProp.ValueKind == JsonValueKind.String
394+
? staticProp.GetString()
395+
: staticProp.GetRawText();
396+
}
397+
}
398+
399+
return null;
400+
}
330401
}

src/Microsoft.Agents.A365.DevTools.Cli/Commands/SetupSubcommands/BlueprintSubcommand.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,18 @@ public static async Task<BlueprintCreationResult> CreateBlueprintImplementationA
455455
generatedConfig["resourceConsents"] = new JsonArray();
456456
}
457457

458+
// Always write messagingEndpoint to the generated config so it's available
459+
// for Developer Portal configuration regardless of whether endpoint registration ran.
460+
// NeedDeployment=true: derive from WebAppName; NeedDeployment=false: copy from static config.
461+
var derivedMessagingEndpoint = setupConfig.NeedDeployment && !string.IsNullOrWhiteSpace(setupConfig.WebAppName)
462+
? $"https://{setupConfig.WebAppName}.azurewebsites.net/api/messages"
463+
: setupConfig.MessagingEndpoint;
464+
if (!string.IsNullOrWhiteSpace(derivedMessagingEndpoint))
465+
{
466+
generatedConfig["messagingEndpoint"] = derivedMessagingEndpoint;
467+
setupConfig.BotMessagingEndpoint = derivedMessagingEndpoint;
468+
}
469+
458470
await File.WriteAllTextAsync(generatedConfigPath, generatedConfig.ToJsonString(new JsonSerializerOptions { WriteIndented = true }), cancellationToken);
459471

460472
// ========================================================================

src/Microsoft.Agents.A365.DevTools.Cli/Models/Agent365Config.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,9 +420,11 @@ public string BotName
420420
public string? BotMsaAppId { get; set; }
421421

422422
/// <summary>
423-
/// Messaging endpoint URL for the bot.
423+
/// Messaging endpoint URL for the agent (stored in generated config as "messagingEndpoint").
424+
/// [JsonIgnore] prevents a duplicate-key collision with the static MessagingEndpoint property.
424425
/// </summary>
425-
[JsonPropertyName("botMessagingEndpoint")]
426+
[JsonIgnore]
427+
[JsonPropertyName("messagingEndpoint")]
426428
public string? BotMessagingEndpoint { get; set; }
427429

428430
#endregion

0 commit comments

Comments
 (0)