Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -1911,9 +1911,11 @@ const session = await client.createSession({
});
```

Available section IDs: `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `runtime_instructions`, `last_instructions`.
Available section IDs: `preamble`, `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `runtime_instructions`, `last_instructions`.

Each override supports four actions: `replace`, `remove`, `append`, and `prepend`. Unknown section IDs are handled gracefully—content is appended to additional instructions and a warning is emitted; `remove` on unknown sections is silently ignored.
`identity` and `tool_instructions` are section *groups*: they target a collection of related sub-sections as a unit. Use `preamble` to target just the identity preamble without affecting its sibling sub-sections.

Each override supports five actions: `replace`, `remove`, `append`, `prepend`, and `preserve`. The `preserve` action is a no-op that opts an individually-addressable section out of a group-level `remove` (for example, keep `tone` when removing the `identity` group). Unknown section IDs are handled gracefully—content is appended to additional instructions and a warning is emitted; `remove` on unknown sections is silently ignored.
Comment thread
MackinnonBuck marked this conversation as resolved.
Outdated

See the language-specific SDK READMEs for examples in [TypeScript](../nodejs/README.md), [Python](../python/README.md), [Go](../go/README.md), [Rust](../rust/README.md), [Java](../java/README.md), and [C#](../dotnet/README.md).

Expand Down
4 changes: 2 additions & 2 deletions dotnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -676,9 +676,9 @@ var session = await client.CreateSessionAsync(new SessionConfig
});
```

Available section IDs are defined as static properties on the `SystemMessageSection` struct: `Identity`, `Tone`, `ToolEfficiency`, `EnvironmentContext`, `CodeChangeRules`, `Guidelines`, `Safety`, `ToolInstructions`, `CustomInstructions`, `RuntimeInstructions`, `LastInstructions`.
Available section IDs are defined as static properties on the `SystemMessageSection` struct: `Preamble`, `Identity`, `Tone`, `ToolEfficiency`, `EnvironmentContext`, `CodeChangeRules`, `Guidelines`, `Safety`, `ToolInstructions`, `CustomInstructions`, `RuntimeInstructions`, `LastInstructions`. `Identity` and `ToolInstructions` are section groups that target a collection of related sub-sections as a unit; use `Preamble` to target just the identity preamble.

Each section override supports four actions: `Replace`, `Remove`, `Append`, and `Prepend`. Unknown section IDs are handled gracefully: content is appended to additional instructions, and `Remove` overrides are silently ignored.
Each section override supports five actions: `Replace`, `Remove`, `Append`, `Prepend`, and `Preserve` (a no-op that opts an individually-addressable section out of a group-level `Remove`). Unknown section IDs are handled gracefully: content is appended to additional instructions, and `Remove` overrides are silently ignored.

#### Replace Mode

Expand Down
9 changes: 9 additions & 0 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1828,6 +1828,13 @@ public enum SectionOverrideAction
/// <summary>Prepend content before the existing section.</summary>
[JsonStringEnumMemberName("prepend")]
Prepend,
/// <summary>
/// No-op marker that opts an individually-addressable section out of a group-level
/// remove (e.g. keep <see cref="SystemMessageSection.Tone"/> when removing the
/// <see cref="SystemMessageSection.Identity"/> group).
/// </summary>
[JsonStringEnumMemberName("preserve")]
Preserve,
/// <summary>Transform the section content via a callback.</summary>
[JsonStringEnumMemberName("transform")]
Transform
Expand Down Expand Up @@ -1866,6 +1873,8 @@ public sealed class SectionOverride
public readonly struct SystemMessageSection : IEquatable<SystemMessageSection>
{
/// <summary>Agent identity preamble and mode statement.</summary>
public static SystemMessageSection Preamble { get; } = new("preamble");
/// <summary>Section group covering the identity preamble and its sibling sub-sections (tone, tool efficiency, etc.).</summary>
public static SystemMessageSection Identity { get; } = new("identity");
/// <summary>Response style, conciseness rules, output formatting preferences.</summary>
public static SystemMessageSection Tone { get; } = new("tone");
Expand Down
30 changes: 30 additions & 0 deletions dotnet/test/E2E/SystemMessageSectionsE2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,34 @@ public async Task Should_Use_Replaced_Identity_Section_In_Response()
content.Contains("botanica") || content.Contains("garden") || content.Contains("plant"),
$"Expected response to reflect the replaced identity section, but got: {response.Data.Content}");
}

[Fact]
public async Task Should_Use_Replaced_Preamble_Section_In_Response()
{
var session = await CreateSessionAsync(new SessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll,
SystemMessage = new SystemMessageConfig
{
Mode = SystemMessageMode.Customize,
Sections = new Dictionary<SystemMessageSection, SectionOverride>
{
[SystemMessageSection.Preamble] = new SectionOverride
{
Action = SectionOverrideAction.Replace,
Content = "You are a helpful gardening assistant called Botanica. You only answer questions about plants and gardening."
}
}
}
});

await session.SendAsync(new MessageOptions { Prompt = "Who are you?" });
var response = await TestHelper.GetFinalAssistantMessageAsync(session);

Assert.NotNull(response);
var content = response.Data.Content.ToLowerInvariant();
Assert.True(
content.Contains("botanica") || content.Contains("garden") || content.Contains("plant"),
$"Expected response to reflect the replaced preamble section, but got: {response.Data.Content}");
}
}
7 changes: 5 additions & 2 deletions go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,14 +238,17 @@ session, err := client.CreateSession(ctx, &copilot.SessionConfig{
})
```

Available section constants: `SectionIdentity`, `SectionTone`, `SectionToolEfficiency`, `SectionEnvironmentContext`, `SectionCodeChangeRules`, `SectionGuidelines`, `SectionSafety`, `SectionToolInstructions`, `SectionCustomInstructions`, `SectionRuntimeInstructions`, `SectionLastInstructions`.
Available section constants: `SectionPreamble`, `SectionIdentity`, `SectionTone`, `SectionToolEfficiency`, `SectionEnvironmentContext`, `SectionCodeChangeRules`, `SectionGuidelines`, `SectionSafety`, `SectionToolInstructions`, `SectionCustomInstructions`, `SectionRuntimeInstructions`, `SectionLastInstructions`.

Each section override supports four actions:
`SectionIdentity` and `SectionToolInstructions` are section _groups_ that target a collection of related sub-sections as a unit. Use `SectionPreamble` to target just the identity preamble without affecting its sibling sub-sections.

Each section override supports five actions:

- **`replace`** — Replace the section content entirely
- **`remove`** — Remove the section from the prompt
- **`append`** — Add content after the existing section
- **`prepend`** — Add content before the existing section
- **`preserve`** — No-op that opts an individually-addressable section out of a group-level `remove`

Unknown section IDs are handled gracefully: content from `replace`/`append`/`prepend` overrides is appended to additional instructions, and `remove` overrides are silently ignored.

Expand Down
39 changes: 39 additions & 0 deletions go/internal/e2e/system_message_sections_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,43 @@ func TestSystemMessageSectionsE2E(t *testing.T) {
t.Errorf("Expected response to reflect the replaced identity section, but got: %s", ad.Content)
}
})

t.Run("should_use_replaced_preamble_section_in_response", func(t *testing.T) {
ctx.ConfigureForTest(t)

session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
SystemMessage: &copilot.SystemMessageConfig{
Mode: "customize",
Sections: map[string]copilot.SectionOverride{
copilot.SectionPreamble: {
Action: copilot.SectionActionReplace,
Content: "You are a helpful gardening assistant called Botanica. You only answer questions about plants and gardening.",
},
},
},
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
}

response, err := session.SendAndWait(t.Context(), copilot.MessageOptions{
Prompt: "Who are you?",
})
if err != nil {
t.Fatalf("Failed to send message: %v", err)
}
if response == nil {
t.Fatal("Expected a response from the assistant")
}

ad, ok := response.Data.(*copilot.AssistantMessageData)
if !ok {
t.Fatalf("Expected AssistantMessageData, got %T", response.Data)
}
content := strings.ToLower(ad.Content)
if !strings.Contains(content, "botanica") && !strings.Contains(content, "garden") && !strings.Contains(content, "plant") {
t.Errorf("Expected response to reflect the replaced preamble section, but got: %s", ad.Content)
}
})
}
9 changes: 8 additions & 1 deletion go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,10 @@ func Int(v int) *int {

// Known system message section identifiers for the "customize" mode.
const (
// SectionIdentity is the agent identity preamble and mode statement.
// SectionPreamble is the agent identity preamble and mode statement.
SectionPreamble = "preamble"
// SectionIdentity is the section group covering the identity preamble and its
// sibling sub-sections (tone, tool efficiency, etc.).
SectionIdentity = "identity"
// SectionTone covers response style, conciseness rules, and output formatting preferences.
SectionTone = "tone"
Expand Down Expand Up @@ -248,6 +251,10 @@ const (
SectionActionAppend SectionOverrideAction = "append"
// SectionActionPrepend prepends to existing section content.
SectionActionPrepend SectionOverrideAction = "prepend"
// SectionActionPreserve is a no-op marker that opts an individually-addressable
// section out of a group-level "remove" (e.g. keep "tone" when removing the
// "identity" group).
SectionActionPreserve SectionOverrideAction = "preserve"
)

// SectionTransformFn is a callback that receives the current content of a system message section
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ public enum SectionOverrideAction {
/** Prepend content before the existing section. */
PREPEND("prepend"),

/**
* No-op marker that opts an individually-addressable section out of a
* group-level {@link #REMOVE} (e.g. keep {@link SystemMessageSections#TONE}
* when removing the {@link SystemMessageSections#IDENTITY} group).
*/
PRESERVE("preserve"),

/**
* Transform the section content via a callback.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
public abstract sealed class SystemMessageSections permits SystemPromptSections {

/** Agent identity preamble and mode statement. */
public static final String PREAMBLE = "preamble";

/**
* Section group covering the identity preamble and its sibling sub-sections
* (tone, tool efficiency, etc.).
*/
public static final String IDENTITY = "identity";

/** Response style, conciseness rules, output formatting preferences. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ void transformOnIdentitySectionReceivesNonEmptyContent() throws Exception {
*/
@Test
void deprecatedSystemPromptSectionsMatchesSystemMessageSections() {
assertEquals(SystemMessageSections.PREAMBLE, SystemPromptSections.PREAMBLE);
assertEquals(SystemMessageSections.IDENTITY, SystemPromptSections.IDENTITY);
assertEquals(SystemMessageSections.TONE, SystemPromptSections.TONE);
assertEquals(SystemMessageSections.TOOL_EFFICIENCY, SystemPromptSections.TOOL_EFFICIENCY);
Expand All @@ -143,7 +144,7 @@ void allConstantsInheritedByDeprecatedClass() throws Exception {
&& Modifier.isFinal(f.getModifiers()) && f.getType() == String.class)
.map(Field::getName).collect(Collectors.toSet());

assertEquals(11, parentConstants.size(), "Expected 11 section constants in SystemMessageSections");
assertEquals(12, parentConstants.size(), "Expected 12 section constants in SystemMessageSections");

for (String constantName : parentConstants) {
Field parentField = SystemMessageSections.class.getDeclaredField(constantName);
Expand Down Expand Up @@ -189,4 +190,41 @@ void shouldUseReplacedIdentitySectionInResponse() throws Exception {
}
}
}

/**
* Verifies that replacing the {@link SystemMessageSections#PREAMBLE} section
* via {@link SectionOverrideAction#REPLACE} causes the assistant to adopt the
* custom identity in its response without affecting sibling sections.
*
* @see Snapshot:
* system_message_sections/should_use_replaced_preamble_section_in_response
*/
@Test
void shouldUseReplacedPreambleSectionInResponse() throws Exception {
ctx.configureForTest("system_message_sections", "should_use_replaced_preamble_section_in_response");

var systemMessage = new SystemMessageConfig().setMode(SystemMessageMode.CUSTOMIZE)
.setSections(Map.of(SystemMessageSections.PREAMBLE,
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 preamble section, but got: "
+ response.getData().content());
} finally {
session.close();
}
}
}
}
7 changes: 5 additions & 2 deletions nodejs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -618,14 +618,17 @@ const session = await client.createSession({
});
```

Available section IDs: `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `runtime_instructions`, `last_instructions`. Use the `SYSTEM_MESSAGE_SECTIONS` constant for descriptions of each section.
Available section IDs: `preamble`, `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `runtime_instructions`, `last_instructions`. Use the `SYSTEM_MESSAGE_SECTIONS` constant for descriptions of each section.

Each section override supports four actions:
`identity` and `tool_instructions` are section _groups_ that target a collection of related sub-sections as a unit. Use `preamble` to target just the identity preamble without affecting its sibling sub-sections.

Each section override supports five actions:

- **`replace`** — Replace the section content entirely
- **`remove`** — Remove the section from the prompt
- **`append`** — Add content after the existing section
- **`prepend`** — Add content before the existing section
- **`preserve`** — No-op that opts an individually-addressable section out of a group-level `remove`

Unknown section IDs are handled gracefully: content from `replace`/`append`/`prepend` overrides is appended to additional instructions, and `remove` overrides are silently ignored.

Expand Down
10 changes: 9 additions & 1 deletion nodejs/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,7 @@ export interface ToolCallResponsePayload {
* Each section corresponds to a distinct part of the system prompt.
*/
export type SystemMessageSection =
| "preamble"
| "identity"
| "tone"
| "tool_efficiency"
Expand All @@ -816,7 +817,11 @@ export type SystemMessageSection =

/** Section metadata for documentation and tooling. */
export const SYSTEM_MESSAGE_SECTIONS: Record<SystemMessageSection, { description: string }> = {
identity: { description: "Agent identity preamble and mode statement" },
preamble: { description: "Agent identity preamble and mode statement" },
identity: {
description:
"Section group covering the identity preamble and its sibling sub-sections (tone, tool efficiency, etc.)",
},
tone: { description: "Response style, conciseness rules, output formatting preferences" },
tool_efficiency: { description: "Tool usage patterns, parallel calling, batching guidelines" },
environment_context: { description: "CWD, OS, git root, directory listing, available tools" },
Expand Down Expand Up @@ -847,13 +852,16 @@ export type SectionTransformFn = (currentContent: string) => string | Promise<st
* - `"remove"`: Remove the section
* - `"append"`: Append to existing section content
* - `"prepend"`: Prepend to existing section content
* - `"preserve"`: No-op marker that opts an individually-addressable section out of a
* group-level `"remove"` (e.g. keep `tone` when removing the `identity` group)
* - `function`: Transform callback — receives current section content, returns new content
*/
export type SectionOverrideAction =
| "replace"
| "remove"
| "append"
| "prepend"
| "preserve"
| SectionTransformFn;

/**
Expand Down
27 changes: 27 additions & 0 deletions nodejs/test/e2e/system_message_sections.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,31 @@ describe("System message sections", async () => {

await session.disconnect();
});

it("should_use_replaced_preamble_section_in_response", async () => {
const session = await client.createSession({
onPermissionRequest: approveAll,
systemMessage: {
mode: "customize",
sections: {
preamble: {
action: "replace",
content:
"You are a helpful gardening assistant called Botanica. You only answer questions about plants and gardening.",
},
},
},
});

const response = await session.sendAndWait({ prompt: "Who are you?" });

expect(response).not.toBeNull();
const content = response!.data.content.toLowerCase();
expect(
content.includes("botanica") || content.includes("garden") || content.includes("plant"),
`Expected response to reflect the replaced preamble section, but got: ${response!.data.content}`
).toBe(true);

await session.disconnect();
});
});
Loading
Loading