diff --git a/java/src/main/java/com/github/copilot/CopilotSession.java b/java/src/main/java/com/github/copilot/CopilotSession.java index fa080c925..adfeac013 100644 --- a/java/src/main/java/com/github/copilot/CopilotSession.java +++ b/java/src/main/java/com/github/copilot/CopilotSession.java @@ -1818,7 +1818,7 @@ public CompletableFuture abort() { * @return a future that completes when the model switch is acknowledged * @throws IllegalStateException * if this session has been terminated - * @since 1.2.0 + * @since 1.0.0 */ public CompletableFuture setModel(String model, String reasoningEffort) { ensureNotTerminated(); @@ -1958,7 +1958,7 @@ public CompletableFuture setModel(String model) { * @return a future that completes when the message is logged * @throws IllegalStateException * if this session has been terminated - * @since 1.2.0 + * @since 1.0.0 */ public CompletableFuture log(String message, String level, Boolean ephemeral, String url) { ensureNotTerminated(); diff --git a/java/src/main/java/com/github/copilot/SystemMessageMode.java b/java/src/main/java/com/github/copilot/SystemMessageMode.java index d693535f9..4e90dca36 100644 --- a/java/src/main/java/com/github/copilot/SystemMessageMode.java +++ b/java/src/main/java/com/github/copilot/SystemMessageMode.java @@ -42,7 +42,7 @@ public enum SystemMessageMode { * default system prompt. An optional {@code content} string is appended after * all sections when provided. * - * @since 1.2.0 + * @since 1.0.0 */ CUSTOMIZE("customize"); diff --git a/java/src/main/java/com/github/copilot/rpc/BlobAttachment.java b/java/src/main/java/com/github/copilot/rpc/BlobAttachment.java index fe15293c6..ea800f110 100644 --- a/java/src/main/java/com/github/copilot/rpc/BlobAttachment.java +++ b/java/src/main/java/com/github/copilot/rpc/BlobAttachment.java @@ -23,7 +23,7 @@ * } * * @see MessageOptions#setAttachments(java.util.List) - * @since 1.2.0 + * @since 1.0.0 */ @JsonInclude(JsonInclude.Include.NON_NULL) public final class BlobAttachment implements MessageAttachment { diff --git a/java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java b/java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java index 941467059..515d1488b 100644 --- a/java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java +++ b/java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java @@ -512,7 +512,7 @@ public CopilotClientOptions setRemote(boolean remote) { * Gets the OpenTelemetry configuration for the CLI server. * * @return the telemetry config, or {@code null} - * @since 1.2.0 + * @since 1.0.0 */ public TelemetryConfig getTelemetry() { return telemetry; @@ -527,7 +527,7 @@ public TelemetryConfig getTelemetry() { * @param telemetry * the telemetry configuration * @return this options instance for method chaining - * @since 1.2.0 + * @since 1.0.0 */ public CopilotClientOptions setTelemetry(TelemetryConfig telemetry) { this.telemetry = Objects.requireNonNull(telemetry, "telemetry must not be null"); diff --git a/java/src/main/java/com/github/copilot/rpc/MessageAttachment.java b/java/src/main/java/com/github/copilot/rpc/MessageAttachment.java index e28c5da2e..9b2af3ee0 100644 --- a/java/src/main/java/com/github/copilot/rpc/MessageAttachment.java +++ b/java/src/main/java/com/github/copilot/rpc/MessageAttachment.java @@ -16,7 +16,7 @@ * @see Attachment * @see BlobAttachment * @see MessageOptions#setAttachments(java.util.List) - * @since 1.2.0 + * @since 1.0.0 */ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonSubTypes({@JsonSubTypes.Type(value = Attachment.class, name = "file"), diff --git a/java/src/main/java/com/github/copilot/rpc/SectionOverride.java b/java/src/main/java/com/github/copilot/rpc/SectionOverride.java index 0b4dd05ce..1c4a39b57 100644 --- a/java/src/main/java/com/github/copilot/rpc/SectionOverride.java +++ b/java/src/main/java/com/github/copilot/rpc/SectionOverride.java @@ -12,34 +12,34 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * Override operation for a single system prompt section in + * Override operation for a single system message section in * {@link SystemMessageMode#CUSTOMIZE} mode. *

* Each {@code SectionOverride} describes how one named section of the default - * system prompt should be modified. The section name keys come from - * {@link SystemPromptSections}. + * system message should be modified. The section name keys come from + * {@link SystemMessageSections}. * *

Static override example

* *
{@code
  * var config = new SystemMessageConfig().setMode(SystemMessageMode.CUSTOMIZE).setSections(Map.of(
- * 		SystemPromptSections.TONE,
+ * 		SystemMessageSections.TONE,
  * 		new SectionOverride().setAction(SectionOverrideAction.REPLACE).setContent("Be concise and formal."),
- * 		SystemPromptSections.CODE_CHANGE_RULES, new SectionOverride().setAction(SectionOverrideAction.REMOVE)));
+ * 		SystemMessageSections.CODE_CHANGE_RULES, new SectionOverride().setAction(SectionOverrideAction.REMOVE)));
  * }
* *

Transform callback example

* *
{@code
  * var config = new SystemMessageConfig().setMode(SystemMessageMode.CUSTOMIZE)
- * 		.setSections(Map.of(SystemPromptSections.IDENTITY, new SectionOverride().setTransform(
+ * 		.setSections(Map.of(SystemMessageSections.IDENTITY, new SectionOverride().setTransform(
  * 				content -> CompletableFuture.completedFuture(content + "\nAlways end replies with DONE."))));
  * }
* * @see SystemMessageConfig * @see SectionOverrideAction - * @see SystemPromptSections - * @since 1.2.0 + * @see SystemMessageSections + * @since 1.0.0 */ @JsonInclude(JsonInclude.Include.NON_NULL) public class SectionOverride { diff --git a/java/src/main/java/com/github/copilot/rpc/SectionOverrideAction.java b/java/src/main/java/com/github/copilot/rpc/SectionOverrideAction.java index f3569009b..bee462d58 100644 --- a/java/src/main/java/com/github/copilot/rpc/SectionOverrideAction.java +++ b/java/src/main/java/com/github/copilot/rpc/SectionOverrideAction.java @@ -12,7 +12,7 @@ * * @see SectionOverride * @see SystemMessageConfig - * @since 1.2.0 + * @since 1.0.0 */ public enum SectionOverrideAction { diff --git a/java/src/main/java/com/github/copilot/rpc/SystemMessageConfig.java b/java/src/main/java/com/github/copilot/rpc/SystemMessageConfig.java index 973168f4d..c5e89acc1 100644 --- a/java/src/main/java/com/github/copilot/rpc/SystemMessageConfig.java +++ b/java/src/main/java/com/github/copilot/rpc/SystemMessageConfig.java @@ -35,10 +35,10 @@ *
{@code
  * var config = new SystemMessageConfig().setMode(SystemMessageMode.CUSTOMIZE)
  * 		.setSections(
- * 				Map.of(SystemPromptSections.TONE,
+ * 				Map.of(SystemMessageSections.TONE,
  * 						new SectionOverride().setAction(SectionOverrideAction.REPLACE)
  * 								.setContent("Be concise and formal."),
- * 						SystemPromptSections.CODE_CHANGE_RULES,
+ * 						SystemMessageSections.CODE_CHANGE_RULES,
  * 						new SectionOverride().setAction(SectionOverrideAction.REMOVE)))
  * 		.setContent("Additional instructions appended after all sections.");
  * }
@@ -46,7 +46,7 @@ * @see SessionConfig#setSystemMessage(SystemMessageConfig) * @see SystemMessageMode * @see SectionOverride - * @see SystemPromptSections + * @see SystemMessageSections * @since 1.0.0 */ @JsonInclude(JsonInclude.Include.NON_NULL) @@ -122,7 +122,7 @@ public Map getSections() { /** * Sets section-level overrides for {@link SystemMessageMode#CUSTOMIZE} mode. *

- * Keys are section identifiers from {@link SystemPromptSections}. Each value + * Keys are section identifiers from {@link SystemMessageSections}. Each value * describes how that section should be modified. Sections with a * {@link SectionOverride#getTransform() transform} callback are handled locally * by the SDK via a {@code systemMessage.transform} RPC call; the rest are sent @@ -131,7 +131,7 @@ public Map getSections() { * @param sections * a map of section identifier to override operation * @return this config for method chaining - * @since 1.2.0 + * @since 1.0.0 */ public SystemMessageConfig setSections(Map sections) { this.sections = sections; diff --git a/java/src/main/java/com/github/copilot/rpc/SystemMessageSections.java b/java/src/main/java/com/github/copilot/rpc/SystemMessageSections.java new file mode 100644 index 000000000..a896be70d --- /dev/null +++ b/java/src/main/java/com/github/copilot/rpc/SystemMessageSections.java @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +/** + * Well-known system message section identifiers for use with + * {@link SystemMessageMode#CUSTOMIZE} mode. + *

+ * Each constant names a section of the default Copilot system message. Pass + * these as keys in the {@code sections} map of {@link SystemMessageConfig} to + * override individual sections. + * + *

Example

+ * + *
{@code
+ * var config = new SystemMessageConfig().setMode(SystemMessageMode.CUSTOMIZE).setSections(Map.of(
+ * 		SystemMessageSections.TONE,
+ * 		new SectionOverride().setAction(SectionOverrideAction.REPLACE).setContent("Always be concise."),
+ * 		SystemMessageSections.CODE_CHANGE_RULES, new SectionOverride().setAction(SectionOverrideAction.REMOVE)));
+ * }
+ * + * @see SystemMessageConfig + * @see SectionOverride + * @since 1.0.2 + */ +public abstract sealed class SystemMessageSections permits SystemPromptSections { + + /** Agent identity preamble and mode statement. */ + public static final String IDENTITY = "identity"; + + /** Response style, conciseness rules, output formatting preferences. */ + public static final String TONE = "tone"; + + /** Tool usage patterns, parallel calling, batching guidelines. */ + public static final String TOOL_EFFICIENCY = "tool_efficiency"; + + /** CWD, OS, git root, directory listing, available tools. */ + public static final String ENVIRONMENT_CONTEXT = "environment_context"; + + /** Coding rules, linting/testing, ecosystem tools, style. */ + public static final String CODE_CHANGE_RULES = "code_change_rules"; + + /** Tips, behavioral best practices, behavioral guidelines. */ + public static final String GUIDELINES = "guidelines"; + + /** Environment limitations, prohibited actions, security policies. */ + public static final String SAFETY = "safety"; + + /** Per-tool usage instructions. */ + public static final String TOOL_INSTRUCTIONS = "tool_instructions"; + + /** Repository and organization custom instructions. */ + public static final String CUSTOM_INSTRUCTIONS = "custom_instructions"; + + /** + * Runtime-provided context and instructions (e.g. system notifications, + * memories, workspace context, mode-specific instructions, content-exclusion + * policy). + * + * @since 1.3.0 + */ + public static final String RUNTIME_INSTRUCTIONS = "runtime_instructions"; + + /** + * End-of-prompt instructions: parallel tool calling, persistence, task + * completion. + */ + public static final String LAST_INSTRUCTIONS = "last_instructions"; + + /** Package-private constructor for the sealed hierarchy. */ + SystemMessageSections() { + } +} diff --git a/java/src/main/java/com/github/copilot/rpc/SystemPromptSections.java b/java/src/main/java/com/github/copilot/rpc/SystemPromptSections.java index 0aaf0113e..10941926c 100644 --- a/java/src/main/java/com/github/copilot/rpc/SystemPromptSections.java +++ b/java/src/main/java/com/github/copilot/rpc/SystemPromptSections.java @@ -5,71 +5,19 @@ package com.github.copilot.rpc; /** - * Well-known system prompt section identifiers for use with - * {@link SystemMessageMode#CUSTOMIZE} mode. + * Deprecated: use {@link SystemMessageSections} instead. *

- * Each constant names a section of the default Copilot system prompt. Pass - * these as keys in the {@code sections} map of {@link SystemMessageConfig} to - * override individual sections. + * This class is retained for backward compatibility. All constants are + * inherited from {@link SystemMessageSections}. * - *

Example

- * - *
{@code
- * var config = new SystemMessageConfig().setMode(SystemMessageMode.CUSTOMIZE).setSections(Map.of(
- * 		SystemPromptSections.TONE,
- * 		new SectionOverride().setAction(SectionOverrideAction.REPLACE).setContent("Always be concise."),
- * 		SystemPromptSections.CODE_CHANGE_RULES, new SectionOverride().setAction(SectionOverrideAction.REMOVE)));
- * }
- * - * @see SystemMessageConfig - * @see SectionOverride - * @since 1.2.0 + * @deprecated Use {@link SystemMessageSections} — this class will be removed in + * a future major version. + * @see SystemMessageSections + * @since 1.0.2 */ -public final class SystemPromptSections { - - /** Agent identity preamble and mode statement. */ - public static final String IDENTITY = "identity"; - - /** Response style, conciseness rules, output formatting preferences. */ - public static final String TONE = "tone"; - - /** Tool usage patterns, parallel calling, batching guidelines. */ - public static final String TOOL_EFFICIENCY = "tool_efficiency"; - - /** CWD, OS, git root, directory listing, available tools. */ - public static final String ENVIRONMENT_CONTEXT = "environment_context"; - - /** Coding rules, linting/testing, ecosystem tools, style. */ - public static final String CODE_CHANGE_RULES = "code_change_rules"; - - /** Tips, behavioral best practices, behavioral guidelines. */ - public static final String GUIDELINES = "guidelines"; - - /** Environment limitations, prohibited actions, security policies. */ - public static final String SAFETY = "safety"; - - /** Per-tool usage instructions. */ - public static final String TOOL_INSTRUCTIONS = "tool_instructions"; - - /** Repository and organization custom instructions. */ - public static final String CUSTOM_INSTRUCTIONS = "custom_instructions"; - - /** - * Runtime-provided context and instructions (e.g. system notifications, - * memories, workspace context, mode-specific instructions, content-exclusion - * policy). - * - * @since 1.3.0 - */ - public static final String RUNTIME_INSTRUCTIONS = "runtime_instructions"; - - /** - * End-of-prompt instructions: parallel tool calling, persistence, task - * completion. - */ - public static final String LAST_INSTRUCTIONS = "last_instructions"; +@Deprecated(since = "1.0.2", forRemoval = true) +public final class SystemPromptSections extends SystemMessageSections { private SystemPromptSections() { - // utility class } } diff --git a/java/src/main/java/com/github/copilot/rpc/TelemetryConfig.java b/java/src/main/java/com/github/copilot/rpc/TelemetryConfig.java index 593908ff8..a8a0f664a 100644 --- a/java/src/main/java/com/github/copilot/rpc/TelemetryConfig.java +++ b/java/src/main/java/com/github/copilot/rpc/TelemetryConfig.java @@ -24,7 +24,7 @@ * } * * @see CopilotClientOptions#setTelemetry(TelemetryConfig) - * @since 1.2.0 + * @since 1.0.0 */ @JsonInclude(JsonInclude.Include.NON_NULL) public class TelemetryConfig { diff --git a/java/src/main/java/com/github/copilot/rpc/ToolDefer.java b/java/src/main/java/com/github/copilot/rpc/ToolDefer.java index bbcae850c..1955f02ec 100644 --- a/java/src/main/java/com/github/copilot/rpc/ToolDefer.java +++ b/java/src/main/java/com/github/copilot/rpc/ToolDefer.java @@ -17,7 +17,7 @@ * decide when unset. * * @see ToolDefinition - * @since 1.2.0 + * @since 1.0.0 */ public enum ToolDefer { diff --git a/java/src/main/java/com/github/copilot/rpc/ToolDefinition.java b/java/src/main/java/com/github/copilot/rpc/ToolDefinition.java index d8cebe325..23b7fe30d 100644 --- a/java/src/main/java/com/github/copilot/rpc/ToolDefinition.java +++ b/java/src/main/java/com/github/copilot/rpc/ToolDefinition.java @@ -131,7 +131,7 @@ public static ToolDefinition createOverride(String name, String description, Map * @param handler * the handler function to execute when invoked * @return a new tool definition with permission skipping enabled - * @since 1.2.0 + * @since 1.0.0 */ public static ToolDefinition createSkipPermission(String name, String description, Map schema, ToolHandler handler) { @@ -157,7 +157,7 @@ public static ToolDefinition createSkipPermission(String name, String descriptio * @param defer * the deferral mode for the tool * @return a new tool definition with the deferral mode set - * @since 1.2.0 + * @since 1.0.0 */ public static ToolDefinition createWithDefer(String name, String description, Map schema, ToolHandler handler, ToolDefer defer) { diff --git a/java/src/test/java/com/github/copilot/SystemMessageSectionsIT.java b/java/src/test/java/com/github/copilot/SystemMessageSectionsIT.java new file mode 100644 index 000000000..bdab3ede5 --- /dev/null +++ b/java/src/test/java/com/github/copilot/SystemMessageSectionsIT.java @@ -0,0 +1,192 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.github.copilot.generated.AssistantMessageEvent; +import com.github.copilot.rpc.MessageOptions; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.SectionOverride; +import com.github.copilot.rpc.SectionOverrideAction; +import com.github.copilot.rpc.SessionConfig; +import com.github.copilot.rpc.SystemMessageConfig; +import com.github.copilot.rpc.SystemMessageSections; +import com.github.copilot.rpc.SystemPromptSections; + +/** + * Failsafe integration test that validates {@link SystemMessageSections} + * constants work correctly with the Copilot CLI via the replay proxy, and that + * the deprecated {@link SystemPromptSections} inherits all constants. + * + * @see Snapshot: + * system_message_transform/should_invoke_transform_callbacks_with_section_content + */ +@SuppressWarnings("deprecation") +class SystemMessageSectionsIT { + + private static E2ETestContext ctx; + + @BeforeAll + static void setup() throws Exception { + ctx = E2ETestContext.create(); + } + + @AfterAll + static void teardown() throws Exception { + if (ctx != null) { + ctx.close(); + } + } + + /** + * Verifies that transform callbacks on {@link SystemMessageSections#IDENTITY} + * and {@link SystemMessageSections#TONE} are invoked by the runtime with + * non-empty section content via the replay proxy. + * + * @see Snapshot: + * system_message_transform/should_invoke_transform_callbacks_with_section_content + */ + @Test + void transformOnIdentitySectionReceivesNonEmptyContent() throws Exception { + ctx.configureForTest("system_message_transform", "should_invoke_transform_callbacks_with_section_content"); + + ConcurrentHashMap capturedContent = new ConcurrentHashMap<>(); + + var systemMessage = new SystemMessageConfig().setMode(SystemMessageMode.CUSTOMIZE) + .setSections(Map.of(SystemMessageSections.IDENTITY, new SectionOverride().setTransform(content -> { + capturedContent.put("identity", content); + return CompletableFuture.completedFuture(content); + }), SystemMessageSections.TONE, new SectionOverride().setTransform(content -> { + capturedContent.put("tone", content); + return CompletableFuture.completedFuture(content); + }))); + + try (CopilotClient client = ctx.createClient()) { + // Create the file the snapshot expects the CLI view tool to read + Path testFile = ctx.getWorkDir().resolve("test.txt"); + Files.writeString(testFile, "Hello transform!"); + + CopilotSession session = client.createSession(new SessionConfig().setSystemMessage(systemMessage) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(30, TimeUnit.SECONDS); + + try { + AssistantMessageEvent response = session + .sendAndWait(new MessageOptions() + .setPrompt("Read the contents of test.txt and tell me what it says"), 60_000) + .get(90, TimeUnit.SECONDS); + + assertNotNull(response, "Expected a response from the assistant"); + + String identityContent = capturedContent.get("identity"); + assertNotNull(identityContent, "Expected identity transform callback to be invoked by the runtime"); + assertTrue(!identityContent.isBlank(), + "Expected identity section content to be non-empty but was blank"); + + String toneContent = capturedContent.get("tone"); + assertNotNull(toneContent, "Expected tone transform callback to be invoked by the runtime"); + assertTrue(!toneContent.isBlank(), "Expected tone section content to be non-empty but was blank"); + } finally { + session.close(); + } + } + } + + /** + * Verifies that the deprecated {@link SystemPromptSections} constants resolve + * to the same values as {@link SystemMessageSections}. + */ + @Test + void deprecatedSystemPromptSectionsMatchesSystemMessageSections() { + assertEquals(SystemMessageSections.IDENTITY, SystemPromptSections.IDENTITY); + assertEquals(SystemMessageSections.TONE, SystemPromptSections.TONE); + assertEquals(SystemMessageSections.TOOL_EFFICIENCY, SystemPromptSections.TOOL_EFFICIENCY); + assertEquals(SystemMessageSections.ENVIRONMENT_CONTEXT, SystemPromptSections.ENVIRONMENT_CONTEXT); + assertEquals(SystemMessageSections.CODE_CHANGE_RULES, SystemPromptSections.CODE_CHANGE_RULES); + assertEquals(SystemMessageSections.GUIDELINES, SystemPromptSections.GUIDELINES); + assertEquals(SystemMessageSections.SAFETY, SystemPromptSections.SAFETY); + assertEquals(SystemMessageSections.TOOL_INSTRUCTIONS, SystemPromptSections.TOOL_INSTRUCTIONS); + assertEquals(SystemMessageSections.CUSTOM_INSTRUCTIONS, SystemPromptSections.CUSTOM_INSTRUCTIONS); + assertEquals(SystemMessageSections.RUNTIME_INSTRUCTIONS, SystemPromptSections.RUNTIME_INSTRUCTIONS); + assertEquals(SystemMessageSections.LAST_INSTRUCTIONS, SystemPromptSections.LAST_INSTRUCTIONS); + } + + /** + * Verifies sealed hierarchy and exhaustive constant inheritance. + */ + @Test + void allConstantsInheritedByDeprecatedClass() throws Exception { + assertEquals(SystemMessageSections.class, SystemPromptSections.class.getSuperclass()); + + Set parentConstants = Arrays.stream(SystemMessageSections.class.getDeclaredFields()) + .filter(f -> Modifier.isPublic(f.getModifiers()) && Modifier.isStatic(f.getModifiers()) + && Modifier.isFinal(f.getModifiers()) && f.getType() == String.class) + .map(Field::getName).collect(Collectors.toSet()); + + assertEquals(11, parentConstants.size(), "Expected 11 section constants in SystemMessageSections"); + + for (String constantName : parentConstants) { + Field parentField = SystemMessageSections.class.getDeclaredField(constantName); + Field childField = SystemPromptSections.class.getField(constantName); + assertEquals(parentField.get(null), childField.get(null), + "Constant " + constantName + " should have same value in both classes"); + } + } + + /** + * Verifies that replacing the {@link SystemMessageSections#IDENTITY} section + * via {@link SectionOverrideAction#REPLACE} causes the assistant to adopt the + * custom identity in its response. + * + * @see Snapshot: + * system_message_sections/should_use_replaced_identity_section_in_response + */ + @Test + void shouldUseReplacedIdentitySectionInResponse() throws Exception { + ctx.configureForTest("system_message_sections", "should_use_replaced_identity_section_in_response"); + + var systemMessage = new SystemMessageConfig().setMode(SystemMessageMode.CUSTOMIZE) + .setSections(Map.of(SystemMessageSections.IDENTITY, + new SectionOverride().setAction(SectionOverrideAction.REPLACE) + .setContent("You are a helpful gardening assistant called Botanica. " + + "You only answer questions about plants and gardening."))); + + try (CopilotClient client = ctx.createClient()) { + CopilotSession session = client.createSession(new SessionConfig().setSystemMessage(systemMessage) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(30, TimeUnit.SECONDS); + + try { + AssistantMessageEvent response = session + .sendAndWait(new MessageOptions().setPrompt("Who are you?"), 60_000).get(90, TimeUnit.SECONDS); + + assertNotNull(response, "Expected a response from the assistant"); + String content = response.getData().content().toLowerCase(); + assertTrue(content.contains("botanica") || content.contains("garden") || content.contains("plant"), + "Expected response to reflect the replaced identity section, but got: " + + response.getData().content()); + } finally { + session.close(); + } + } + } +} diff --git a/test/snapshots/system_message_sections/should_use_replaced_identity_section_in_response.yaml b/test/snapshots/system_message_sections/should_use_replaced_identity_section_in_response.yaml new file mode 100644 index 000000000..fcc84423d --- /dev/null +++ b/test/snapshots/system_message_sections/should_use_replaced_identity_section_in_response.yaml @@ -0,0 +1,15 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Who are you? + - role: assistant + content: >- + I'm Botanica, your helpful gardening assistant! I'm here to help you + with all things related to plants and gardening. Whether you have + questions about plant care, garden design, soil preparation, pest + management, or anything else in the world of gardening, I'm happy to + help. What would you like to know about plants or gardening today?