@promptbook/cli 0.114.0-35 → 0.114.0-38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/agents-server/package.json +1 -1
- package/apps/agents-server/scripts/build-agents-server.js +4 -0
- package/apps/agents-server/scripts/build-e2e.js +1 -1
- package/apps/agents-server/src/app/swagger/SwaggerPageClient.tsx +35 -19
- package/apps/agents-server/src/types/swagger-ui-dist.d.ts +5 -0
- package/esm/index.es.js +363 -98
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +7 -0
- package/esm/scripts/run-codex-prompts/main/resolvePromptRunner.d.ts +5 -8
- package/esm/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -4
- package/esm/scripts/run-codex-prompts/prompts/buildPromptRunTraceContent.d.ts +5 -17
- package/esm/scripts/run-codex-prompts/prompts/buildPromptStatusDetails.d.ts +3 -15
- package/esm/scripts/run-codex-prompts/prompts/formatRunnerSignature.d.ts +14 -1
- package/esm/scripts/run-codex-prompts/prompts/markPromptFailed.d.ts +2 -9
- package/esm/src/book-2.0/agent-source/createAgentModelRequirements.deduplication.test.d.ts +1 -0
- package/esm/src/book-2.0/agent-source/deduplicateSystemMessage.d.ts +14 -0
- package/esm/src/book-2.0/agent-source/deduplicateSystemMessage.test.d.ts +1 -0
- package/esm/src/cli/$initializePromptbookCliProgram.d.ts +8 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +2 -3
- package/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.ts +3 -2
- package/src/book-2.0/agent-source/deduplicateSystemMessage.ts +327 -0
- package/src/cli/$initializePromptbookCliProgram.ts +87 -0
- package/src/cli/promptbookCli.ts +2 -87
- package/src/other/templates/getTemplatesPipelineCollection.ts +787 -841
- package/src/version.ts +2 -2
- package/src/versions.txt +3 -0
- package/umd/index.umd.js +363 -98
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +7 -0
- package/umd/scripts/run-codex-prompts/main/resolvePromptRunner.d.ts +5 -8
- package/umd/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -4
- package/umd/scripts/run-codex-prompts/prompts/buildPromptRunTraceContent.d.ts +5 -17
- package/umd/scripts/run-codex-prompts/prompts/buildPromptStatusDetails.d.ts +3 -15
- package/umd/scripts/run-codex-prompts/prompts/formatRunnerSignature.d.ts +14 -1
- package/umd/scripts/run-codex-prompts/prompts/markPromptFailed.d.ts +2 -9
- package/umd/src/book-2.0/agent-source/createAgentModelRequirements.deduplication.test.d.ts +1 -0
- package/umd/src/book-2.0/agent-source/deduplicateSystemMessage.d.ts +14 -0
- package/umd/src/book-2.0/agent-source/deduplicateSystemMessage.test.d.ts +1 -0
- package/umd/src/cli/$initializePromptbookCliProgram.d.ts +8 -0
- package/umd/src/version.d.ts +1 -1
|
@@ -7,6 +7,13 @@ export type ResolvedCoderAgentBook = {
|
|
|
7
7
|
* Raw source of the agent `.book` file.
|
|
8
8
|
*/
|
|
9
9
|
readonly agentSource: string_book;
|
|
10
|
+
/**
|
|
11
|
+
* Human-readable name of the agent, as it is reported in prompt status lines and run traces.
|
|
12
|
+
*
|
|
13
|
+
* Comes from the `META FULLNAME` of the Book, or from the plain title on its first line, so it is the
|
|
14
|
+
* display name of the agent and not its normalized identifier.
|
|
15
|
+
*/
|
|
16
|
+
readonly agentName: string;
|
|
10
17
|
/**
|
|
11
18
|
* Equivalent references by which a prompt may target this agent.
|
|
12
19
|
*
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import type { RunOptions } from '../cli/RunOptions';
|
|
2
|
+
import type { PromptRunnerMetadata } from '../common/PromptRunnerMetadata';
|
|
2
3
|
import type { PromptRunner } from '../runners/types/PromptRunner';
|
|
3
|
-
/**
|
|
4
|
-
* Runner metadata used in prompt status lines.
|
|
5
|
-
*/
|
|
6
|
-
type RunnerMetadata = {
|
|
7
|
-
runnerName: string;
|
|
8
|
-
modelName?: string;
|
|
9
|
-
};
|
|
10
4
|
/**
|
|
11
5
|
* Subset of `RunOptions` which decides which prompt runner is created and how it is labeled.
|
|
12
6
|
*
|
|
@@ -20,7 +14,10 @@ export type PromptRunnerSelectionOptions = Pick<RunOptions, 'agentName' | 'model
|
|
|
20
14
|
type PromptRunnerResolution = {
|
|
21
15
|
runner: PromptRunner;
|
|
22
16
|
actualRunnerModel?: string;
|
|
23
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Harness half of the run metadata; the optional Book agent is added by the caller which resolves it.
|
|
19
|
+
*/
|
|
20
|
+
runnerMetadata: PromptRunnerMetadata;
|
|
24
21
|
};
|
|
25
22
|
/**
|
|
26
23
|
* Resolves the configured prompt runner together with status-line metadata.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RunOptions } from '../cli/RunOptions';
|
|
2
2
|
import type { CliProgressDisplay } from '../common/cliProgressDisplay';
|
|
3
3
|
import type { WaitForCoderRunPauseCheckpoint } from '../common/CoderRunPauseCheckpoint';
|
|
4
|
+
import type { PromptRunnerMetadata } from '../common/PromptRunnerMetadata';
|
|
4
5
|
import type { PromptSelection } from '../prompts/types/PromptSelection';
|
|
5
6
|
import type { PromptRunner } from '../runners/types/PromptRunner';
|
|
6
7
|
import type { CoderRunUiHandle } from '../ui/renderCoderRunUi';
|
|
@@ -10,10 +11,7 @@ import type { CoderRunUiHandle } from '../ui/renderCoderRunUi';
|
|
|
10
11
|
export type RunPromptRoundOptions = {
|
|
11
12
|
options: RunOptions;
|
|
12
13
|
runner: PromptRunner;
|
|
13
|
-
runnerMetadata:
|
|
14
|
-
runnerName: string;
|
|
15
|
-
modelName?: string;
|
|
16
|
-
};
|
|
14
|
+
runnerMetadata: PromptRunnerMetadata;
|
|
17
15
|
nextPrompt: PromptSelection;
|
|
18
16
|
promptLabel: string;
|
|
19
17
|
resolvedCoderContext?: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import moment from 'moment';
|
|
2
2
|
import { type CodexLoginMethod } from '../../../src/book-3.0/codexLoginMethod';
|
|
3
|
-
import type { ThinkingLevel } from '../../../src/cli/cli-commands/coder/ThinkingLevel';
|
|
4
3
|
import type { CoderRunStep } from '../common/CoderRunStep';
|
|
4
|
+
import { type FormatRunnerSignatureOptions } from './formatRunnerSignature';
|
|
5
5
|
import type { PromptFile } from './types/PromptFile';
|
|
6
6
|
import type { PromptSection } from './types/PromptSection';
|
|
7
7
|
/**
|
|
@@ -29,21 +29,9 @@ export type PromptRunTraceOutcome = {
|
|
|
29
29
|
/**
|
|
30
30
|
* Everything one run trace says about the prompt round it describes.
|
|
31
31
|
*/
|
|
32
|
-
export type BuildPromptRunTraceContentOptions = {
|
|
32
|
+
export type BuildPromptRunTraceContentOptions = FormatRunnerSignatureOptions & {
|
|
33
33
|
readonly file: PromptFile;
|
|
34
34
|
readonly section: PromptSection;
|
|
35
|
-
/**
|
|
36
|
-
* Harness which ran the prompt.
|
|
37
|
-
*/
|
|
38
|
-
readonly runnerName?: string;
|
|
39
|
-
/**
|
|
40
|
-
* Model the harness ran the prompt with.
|
|
41
|
-
*/
|
|
42
|
-
readonly modelName?: string;
|
|
43
|
-
/**
|
|
44
|
-
* Reasoning effort the harness ran the prompt with.
|
|
45
|
-
*/
|
|
46
|
-
readonly thinkingLevel?: ThinkingLevel;
|
|
47
35
|
/**
|
|
48
36
|
* Verification command the round ran after each coding attempt, when one is configured.
|
|
49
37
|
*/
|
|
@@ -70,8 +58,8 @@ export type BuildPromptRunTraceContentOptions = {
|
|
|
70
58
|
/**
|
|
71
59
|
* Renders the markdown run trace of one finished or failed prompt round.
|
|
72
60
|
*
|
|
73
|
-
* The trace pairs the metadata of the round - which harness, model and thinking level ran it, how long
|
|
74
|
-
* its steps took and what it cost - with the untouched runtime log, so a finished round can still be
|
|
75
|
-
* after its temporary artifacts are cleaned up.
|
|
61
|
+
* The trace pairs the metadata of the round - which agent, harness, model and thinking level ran it, how long
|
|
62
|
+
* each of its steps took and what it cost - with the untouched runtime log, so a finished round can still be
|
|
63
|
+
* analyzed after its temporary artifacts are cleaned up.
|
|
76
64
|
*/
|
|
77
65
|
export declare function buildPromptRunTraceContent(options: BuildPromptRunTraceContentOptions): string;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type CodexLoginMethod } from '../../../src/book-3.0/codexLoginMethod';
|
|
2
|
-
import type { ThinkingLevel } from '../../../src/cli/cli-commands/coder/ThinkingLevel';
|
|
3
2
|
import type { CoderRunStep, CoderRunStepKind } from '../common/CoderRunStep';
|
|
3
|
+
import { type FormatRunnerSignatureOptions } from './formatRunnerSignature';
|
|
4
4
|
import { type PromptRunnerAttribution } from './promptRunnerAttribution';
|
|
5
5
|
/**
|
|
6
6
|
* Everything one prompt status line says after its checklist marker.
|
|
7
7
|
*/
|
|
8
|
-
export type BuildPromptStatusDetailsOptions = {
|
|
8
|
+
export type BuildPromptStatusDetailsOptions = FormatRunnerSignatureOptions & {
|
|
9
9
|
/**
|
|
10
10
|
* Steps of the prompt round which have already finished, each with its own price and duration.
|
|
11
11
|
*/
|
|
@@ -14,14 +14,6 @@ export type BuildPromptStatusDetailsOptions = {
|
|
|
14
14
|
* Step which has already started but has not finished yet, reported as `Implementation in progress`.
|
|
15
15
|
*/
|
|
16
16
|
readonly inProgressStepKind?: CoderRunStepKind;
|
|
17
|
-
/**
|
|
18
|
-
* Harness which runs the prompt.
|
|
19
|
-
*/
|
|
20
|
-
readonly runnerName: string | undefined;
|
|
21
|
-
/**
|
|
22
|
-
* Model the harness runs the prompt with.
|
|
23
|
-
*/
|
|
24
|
-
readonly modelName: string | undefined;
|
|
25
17
|
/**
|
|
26
18
|
* Chronological runner report read from a prompt left in the middle of its implementation.
|
|
27
19
|
*
|
|
@@ -37,15 +29,11 @@ export type BuildPromptStatusDetailsOptions = {
|
|
|
37
29
|
* Authentication method the harness reported, when it is already known.
|
|
38
30
|
*/
|
|
39
31
|
readonly loginMethod?: CodexLoginMethod;
|
|
40
|
-
/**
|
|
41
|
-
* Reasoning effort the harness runs the prompt with.
|
|
42
|
-
*/
|
|
43
|
-
readonly thinkingLevel?: ThinkingLevel;
|
|
44
32
|
};
|
|
45
33
|
/**
|
|
46
34
|
* Builds the shared body of a prompt status line, used by both the in-progress `[^]` and the done `[x]` status.
|
|
47
35
|
*
|
|
48
36
|
* Produces details such as
|
|
49
|
-
* ``by OpenAI Codex `gpt-5.6-luna` thinking `max` (ChatGPT account) - Implementation ~$0.2036 10 minutes``.
|
|
37
|
+
* ``by Developer on OpenAI Codex `gpt-5.6-luna` thinking `max` (ChatGPT account) - Implementation ~$0.2036 10 minutes``.
|
|
50
38
|
*/
|
|
51
39
|
export declare function buildPromptStatusDetails(options: BuildPromptStatusDetailsOptions): string;
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import type { ThinkingLevel } from '../../../src/cli/cli-commands/coder/ThinkingLevel';
|
|
2
|
+
import type { PromptRunnerMetadata } from '../common/PromptRunnerMetadata';
|
|
3
|
+
/**
|
|
4
|
+
* Everything one runner signature can name, as far as it is known when the signature is formatted.
|
|
5
|
+
*/
|
|
6
|
+
export type FormatRunnerSignatureOptions = Partial<PromptRunnerMetadata> & {
|
|
7
|
+
/**
|
|
8
|
+
* Reasoning effort the harness runs the prompt with.
|
|
9
|
+
*/
|
|
10
|
+
readonly thinkingLevel?: ThinkingLevel;
|
|
11
|
+
};
|
|
2
12
|
/**
|
|
3
13
|
* Formats runner details for prompt status lines.
|
|
14
|
+
*
|
|
15
|
+
* Produces `` OpenAI Codex `gpt-5.6-luna` thinking `max` `` for a plain run and
|
|
16
|
+
* `` Developer on OpenAI Codex `gpt-5.6-luna` thinking `max` `` for a run personalized with `--agent`.
|
|
4
17
|
*/
|
|
5
|
-
export declare function formatRunnerSignature(
|
|
18
|
+
export declare function formatRunnerSignature(options: FormatRunnerSignatureOptions): string;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import moment from 'moment';
|
|
2
|
+
import { type FormatRunnerSignatureOptions } from './formatRunnerSignature';
|
|
2
3
|
import { type PromptRunnerAttribution } from './promptRunnerAttribution';
|
|
3
4
|
import type { PromptFile } from './types/PromptFile';
|
|
4
5
|
import type { PromptSection } from './types/PromptSection';
|
|
5
6
|
/**
|
|
6
7
|
* Input for marking one prompt section as failed.
|
|
7
8
|
*/
|
|
8
|
-
export type MarkPromptFailedOptions = {
|
|
9
|
+
export type MarkPromptFailedOptions = FormatRunnerSignatureOptions & {
|
|
9
10
|
/**
|
|
10
11
|
* Prompt file the marked section belongs to.
|
|
11
12
|
*/
|
|
@@ -14,14 +15,6 @@ export type MarkPromptFailedOptions = {
|
|
|
14
15
|
* Section which could not be implemented.
|
|
15
16
|
*/
|
|
16
17
|
readonly section: PromptSection;
|
|
17
|
-
/**
|
|
18
|
-
* Harness which ran the prompt.
|
|
19
|
-
*/
|
|
20
|
-
readonly runnerName: string | undefined;
|
|
21
|
-
/**
|
|
22
|
-
* Model the harness ran the prompt with.
|
|
23
|
-
*/
|
|
24
|
-
readonly modelName: string | undefined;
|
|
25
18
|
/**
|
|
26
19
|
* Chronological runner report read from a prompt left in the middle of its implementation.
|
|
27
20
|
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes duplicated instructions from a generated system message
|
|
3
|
+
*
|
|
4
|
+
* Commitments are applied one by one, so a commitment used multiple times in one book repeats its whole
|
|
5
|
+
* section, including the shared guidance preamble. For example two `WRITING RULES` commitments emit the
|
|
6
|
+
* `## Writing rules` heading and its guidance paragraph twice. This function merges all sections sharing
|
|
7
|
+
* one heading into the position of their first occurrence and keeps every identical block only once.
|
|
8
|
+
*
|
|
9
|
+
* @param systemMessage The assembled system message which may contain repeated sections and blocks
|
|
10
|
+
* @returns The system message where each section heading and each identical block appears exactly once
|
|
11
|
+
*
|
|
12
|
+
* @private internal utility of `createAgentModelRequirementsWithCommitments`
|
|
13
|
+
*/
|
|
14
|
+
export declare function deduplicateSystemMessage(systemMessage: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Registers the CLI commands on a fresh Commander program without parsing arguments or running an action.
|
|
4
|
+
* Tests can configure output and exit handling before registration, so subcommands inherit those settings.
|
|
5
|
+
*
|
|
6
|
+
* @private internal utility of `promptbookCli`
|
|
7
|
+
*/
|
|
8
|
+
export declare function $initializePromptbookCliProgram(program: Command): void;
|
package/esm/src/version.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare const BOOK_LANGUAGE_VERSION: string_semantic_version;
|
|
|
15
15
|
export declare const PROMPTBOOK_ENGINE_VERSION: string_promptbook_version;
|
|
16
16
|
/**
|
|
17
17
|
* Represents the version string of the Promptbook engine.
|
|
18
|
-
* It follows semantic versioning (e.g., `0.114.0-
|
|
18
|
+
* It follows semantic versioning (e.g., `0.114.0-37`).
|
|
19
19
|
*
|
|
20
20
|
* @generated
|
|
21
21
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promptbook/cli",
|
|
3
|
-
"version": "0.114.0-
|
|
3
|
+
"version": "0.114.0-38",
|
|
4
4
|
"description": "Promptbook: Create persistent AI agents that turn your company's scattered knowledge into action",
|
|
5
5
|
"private": false,
|
|
6
6
|
"sideEffects": false,
|
|
@@ -151,7 +151,6 @@
|
|
|
151
151
|
"@sentry/nextjs": "^10.57.0",
|
|
152
152
|
"@types/formidable": "3.4.7",
|
|
153
153
|
"@types/mailparser": "3.4.6",
|
|
154
|
-
"@types/swagger-ui-react": "5.18.0",
|
|
155
154
|
"@types/turndown": "5.0.6",
|
|
156
155
|
"@vercel/blob": "1.1.1",
|
|
157
156
|
"@xmldom/xmldom": "^0.9.10",
|
|
@@ -175,7 +174,7 @@
|
|
|
175
174
|
"node-gzip": "1.1.2",
|
|
176
175
|
"nodemailer": "^7.0.13",
|
|
177
176
|
"qrcode": "1.5.4",
|
|
178
|
-
"swagger-ui-
|
|
177
|
+
"swagger-ui-dist": "5.32.15",
|
|
179
178
|
"turndown": "7.2.4",
|
|
180
179
|
"type-fest": "4.5.0",
|
|
181
180
|
"web-push": "^3.6.7",
|
|
@@ -6,6 +6,7 @@ import { applyCommitmentsToAgentModelRequirements } from './createAgentModelRequ
|
|
|
6
6
|
import { augmentAgentModelRequirementsFromSource } from './createAgentModelRequirementsWithCommitments/augmentAgentModelRequirementsFromSource';
|
|
7
7
|
import { filterCommitmentsForAgentModelRequirements } from './createAgentModelRequirementsWithCommitments/filterCommitmentsForAgentModelRequirements';
|
|
8
8
|
import { materializeInlineKnowledgeSources } from './createAgentModelRequirementsWithCommitments/materializeInlineKnowledgeSources';
|
|
9
|
+
import { deduplicateSystemMessage } from './deduplicateSystemMessage';
|
|
9
10
|
import { parseAgentSourceWithCommitments } from './parseAgentSourceWithCommitments';
|
|
10
11
|
import { removeCommentsFromSystemMessage } from './removeCommentsFromSystemMessage';
|
|
11
12
|
import type { string_book } from './string_book';
|
|
@@ -73,13 +74,13 @@ function createInitialAgentModelRequirements(
|
|
|
73
74
|
* Performs the final system-message cleanup pass after all other augmentation steps are complete.
|
|
74
75
|
*
|
|
75
76
|
* @param requirements - Fully built requirements before final cleanup.
|
|
76
|
-
* @returns Requirements with comment lines removed
|
|
77
|
+
* @returns Requirements with comment lines removed and repeated instructions deduplicated in the final system message.
|
|
77
78
|
*
|
|
78
79
|
* @private internal utility of `createAgentModelRequirementsWithCommitments`
|
|
79
80
|
*/
|
|
80
81
|
function finalizeRequirements(requirements: AgentModelRequirements): AgentModelRequirements {
|
|
81
82
|
return {
|
|
82
83
|
...requirements,
|
|
83
|
-
systemMessage: removeCommentsFromSystemMessage(requirements.systemMessage),
|
|
84
|
+
systemMessage: deduplicateSystemMessage(removeCommentsFromSystemMessage(requirements.systemMessage)),
|
|
84
85
|
};
|
|
85
86
|
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern matching a markdown section heading (`## Title`) that separates system-message sections
|
|
3
|
+
*
|
|
4
|
+
* @private internal constant of `deduplicateSystemMessage`
|
|
5
|
+
*/
|
|
6
|
+
const SYSTEM_MESSAGE_SECTION_HEADING_PATTERN = /^##(?!#)\s*(.+?)\s*$/;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Pattern matching a fenced code block delimiter which suspends section and block splitting
|
|
10
|
+
*
|
|
11
|
+
* @private internal constant of `deduplicateSystemMessage`
|
|
12
|
+
*/
|
|
13
|
+
const CODE_FENCE_PATTERN = /^\s*(?:```|~~~)/;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Pattern matching a markdown list item which keeps merged bullet lists visually compact
|
|
17
|
+
*
|
|
18
|
+
* @private internal constant of `deduplicateSystemMessage`
|
|
19
|
+
*/
|
|
20
|
+
const LIST_ITEM_PATTERN = /^\s*(?:[-*+]|\d+[.)])\s/;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* One block of a system-message region, which is a group of lines surrounded by blank lines
|
|
24
|
+
*
|
|
25
|
+
* @private internal type of `deduplicateSystemMessage`
|
|
26
|
+
*/
|
|
27
|
+
type SystemMessageBlock = {
|
|
28
|
+
/**
|
|
29
|
+
* Exact text of the block
|
|
30
|
+
*/
|
|
31
|
+
readonly text: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether the block opens a section occurrence merged into an earlier section with the same heading
|
|
35
|
+
*/
|
|
36
|
+
readonly isOpeningMergedSection: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* One region of the system message, which is either the intro before the first heading or one `## Title` section
|
|
41
|
+
*
|
|
42
|
+
* @private internal type of `deduplicateSystemMessage`
|
|
43
|
+
*/
|
|
44
|
+
type SystemMessageRegion = {
|
|
45
|
+
/**
|
|
46
|
+
* Case-insensitive key used to merge regions sharing one heading, `null` for the intro before the first heading
|
|
47
|
+
*/
|
|
48
|
+
readonly titleKey: string | null;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Original heading line kept so the rendered heading preserves its exact original text
|
|
52
|
+
*/
|
|
53
|
+
readonly headingLine: string | null;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Whether the original region separated its heading from the body by a blank line
|
|
57
|
+
*/
|
|
58
|
+
readonly isBodySeparatedByBlankLine: boolean;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Body blocks of the region in their original order
|
|
62
|
+
*/
|
|
63
|
+
readonly blocks: ReadonlyArray<SystemMessageBlock>;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Removes duplicated instructions from a generated system message
|
|
68
|
+
*
|
|
69
|
+
* Commitments are applied one by one, so a commitment used multiple times in one book repeats its whole
|
|
70
|
+
* section, including the shared guidance preamble. For example two `WRITING RULES` commitments emit the
|
|
71
|
+
* `## Writing rules` heading and its guidance paragraph twice. This function merges all sections sharing
|
|
72
|
+
* one heading into the position of their first occurrence and keeps every identical block only once.
|
|
73
|
+
*
|
|
74
|
+
* @param systemMessage The assembled system message which may contain repeated sections and blocks
|
|
75
|
+
* @returns The system message where each section heading and each identical block appears exactly once
|
|
76
|
+
*
|
|
77
|
+
* @private internal utility of `createAgentModelRequirementsWithCommitments`
|
|
78
|
+
*/
|
|
79
|
+
export function deduplicateSystemMessage(systemMessage: string): string {
|
|
80
|
+
if (!systemMessage.trim()) {
|
|
81
|
+
return systemMessage;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return splitSystemMessageIntoRegions(systemMessage)
|
|
85
|
+
.reduce(mergeRegionSharingHeading, [])
|
|
86
|
+
.map(removeDuplicateBlocksFromRegion)
|
|
87
|
+
.map(renderRegion)
|
|
88
|
+
.filter((renderedRegion) => renderedRegion !== '')
|
|
89
|
+
.join('\n\n');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Splits the system message into the intro region and one region per `## Title` section
|
|
94
|
+
*
|
|
95
|
+
* @param systemMessage The system message to split
|
|
96
|
+
* @returns Regions in their original order, without empty ones
|
|
97
|
+
*
|
|
98
|
+
* @private internal utility of `deduplicateSystemMessage`
|
|
99
|
+
*/
|
|
100
|
+
function splitSystemMessageIntoRegions(systemMessage: string): ReadonlyArray<SystemMessageRegion> {
|
|
101
|
+
const regions: SystemMessageRegion[] = [];
|
|
102
|
+
let headingLine: string | null = null;
|
|
103
|
+
let bodyLines: string[] = [];
|
|
104
|
+
let isInsideCodeFence = false;
|
|
105
|
+
|
|
106
|
+
for (const line of systemMessage.split(/\r?\n/)) {
|
|
107
|
+
if (CODE_FENCE_PATTERN.test(line)) {
|
|
108
|
+
isInsideCodeFence = !isInsideCodeFence;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (isInsideCodeFence || !SYSTEM_MESSAGE_SECTION_HEADING_PATTERN.test(line)) {
|
|
112
|
+
bodyLines.push(line);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
regions.push(createRegion(headingLine, bodyLines));
|
|
117
|
+
headingLine = line;
|
|
118
|
+
bodyLines = [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
regions.push(createRegion(headingLine, bodyLines));
|
|
122
|
+
|
|
123
|
+
return regions.filter((region) => region.headingLine !== null || region.blocks.length > 0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Creates one region from its heading line and its raw body lines
|
|
128
|
+
*
|
|
129
|
+
* @param headingLine The original `## Title` line, or `null` for the intro before the first heading
|
|
130
|
+
* @param bodyLines The raw lines following the heading line
|
|
131
|
+
* @returns The region with its body already split into blocks
|
|
132
|
+
*
|
|
133
|
+
* @private internal utility of `splitSystemMessageIntoRegions`
|
|
134
|
+
*/
|
|
135
|
+
function createRegion(headingLine: string | null, bodyLines: ReadonlyArray<string>): SystemMessageRegion {
|
|
136
|
+
return {
|
|
137
|
+
titleKey: headingLine === null ? null : normalizeSectionTitle(headingLine),
|
|
138
|
+
headingLine,
|
|
139
|
+
isBodySeparatedByBlankLine: bodyLines[0] !== undefined && bodyLines[0].trim() === '',
|
|
140
|
+
blocks: splitBodyIntoBlocks(bodyLines),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Normalizes a heading line into a key usable for matching sections which belong together
|
|
146
|
+
*
|
|
147
|
+
* @param headingLine The original `## Title` line
|
|
148
|
+
* @returns Lowercased title without the markdown heading prefix
|
|
149
|
+
*
|
|
150
|
+
* @private internal utility of `createRegion`
|
|
151
|
+
*/
|
|
152
|
+
function normalizeSectionTitle(headingLine: string): string {
|
|
153
|
+
return (SYSTEM_MESSAGE_SECTION_HEADING_PATTERN.exec(headingLine)?.[1] ?? headingLine).toLowerCase();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Splits raw body lines into blocks separated by blank lines while keeping fenced code blocks intact
|
|
158
|
+
*
|
|
159
|
+
* @param bodyLines The raw lines of one region body
|
|
160
|
+
* @returns Non-empty blocks in their original order
|
|
161
|
+
*
|
|
162
|
+
* @private internal utility of `createRegion`
|
|
163
|
+
*/
|
|
164
|
+
function splitBodyIntoBlocks(bodyLines: ReadonlyArray<string>): ReadonlyArray<SystemMessageBlock> {
|
|
165
|
+
const blockTexts: string[] = [];
|
|
166
|
+
let blockLines: string[] = [];
|
|
167
|
+
let isInsideCodeFence = false;
|
|
168
|
+
|
|
169
|
+
for (const line of bodyLines) {
|
|
170
|
+
if (CODE_FENCE_PATTERN.test(line)) {
|
|
171
|
+
isInsideCodeFence = !isInsideCodeFence;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!isInsideCodeFence && line.trim() === '') {
|
|
175
|
+
blockTexts.push(blockLines.join('\n'));
|
|
176
|
+
blockLines = [];
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
blockLines.push(line);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
blockTexts.push(blockLines.join('\n'));
|
|
184
|
+
|
|
185
|
+
return blockTexts
|
|
186
|
+
.filter((blockText) => blockText.trim() !== '')
|
|
187
|
+
.map((blockText) => ({ text: blockText, isOpeningMergedSection: false }));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Appends one region to the already merged regions, merging it into an earlier region with the same heading
|
|
192
|
+
*
|
|
193
|
+
* @param mergedRegions Regions merged so far, used as the reducer accumulator
|
|
194
|
+
* @param region The next region in original order
|
|
195
|
+
* @returns The accumulator with the region either appended or merged into its earlier twin
|
|
196
|
+
*
|
|
197
|
+
* @private internal utility of `deduplicateSystemMessage`
|
|
198
|
+
*/
|
|
199
|
+
function mergeRegionSharingHeading(
|
|
200
|
+
mergedRegions: ReadonlyArray<SystemMessageRegion>,
|
|
201
|
+
region: SystemMessageRegion,
|
|
202
|
+
): ReadonlyArray<SystemMessageRegion> {
|
|
203
|
+
const existingRegionIndex = mergedRegions.findIndex(
|
|
204
|
+
(mergedRegion) => region.titleKey !== null && mergedRegion.titleKey === region.titleKey,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (existingRegionIndex === -1) {
|
|
208
|
+
return [...mergedRegions, region];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return mergedRegions.map((mergedRegion, index) =>
|
|
212
|
+
index !== existingRegionIndex
|
|
213
|
+
? mergedRegion
|
|
214
|
+
: {
|
|
215
|
+
...mergedRegion,
|
|
216
|
+
blocks: [...mergedRegion.blocks, ...markFirstBlockAsOpeningMergedSection(region.blocks)],
|
|
217
|
+
},
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Marks the first block of a merged section occurrence so the merge seam can be rendered as one list
|
|
223
|
+
*
|
|
224
|
+
* @param blocks The blocks of the section occurrence being merged into an earlier section
|
|
225
|
+
* @returns The same blocks with the first one marked as opening a merged section
|
|
226
|
+
*
|
|
227
|
+
* @private internal utility of `mergeRegionSharingHeading`
|
|
228
|
+
*/
|
|
229
|
+
function markFirstBlockAsOpeningMergedSection(
|
|
230
|
+
blocks: ReadonlyArray<SystemMessageBlock>,
|
|
231
|
+
): ReadonlyArray<SystemMessageBlock> {
|
|
232
|
+
return blocks.map((block, index) => (index === 0 ? { ...block, isOpeningMergedSection: true } : block));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Keeps only the first occurrence of every identical block inside one region
|
|
237
|
+
*
|
|
238
|
+
* @param region The region whose blocks may repeat
|
|
239
|
+
* @returns The region without repeated blocks
|
|
240
|
+
*
|
|
241
|
+
* @private internal utility of `deduplicateSystemMessage`
|
|
242
|
+
*/
|
|
243
|
+
function removeDuplicateBlocksFromRegion(region: SystemMessageRegion): SystemMessageRegion {
|
|
244
|
+
const alreadyRenderedTexts = new Set<string>();
|
|
245
|
+
const uniqueBlocks: SystemMessageBlock[] = [];
|
|
246
|
+
let isMergeSeamPending = false;
|
|
247
|
+
|
|
248
|
+
for (const block of region.blocks) {
|
|
249
|
+
if (alreadyRenderedTexts.has(block.text)) {
|
|
250
|
+
// Note: A removed block must still hand its merge seam over to the next block which survives
|
|
251
|
+
isMergeSeamPending = isMergeSeamPending || block.isOpeningMergedSection;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
alreadyRenderedTexts.add(block.text);
|
|
256
|
+
uniqueBlocks.push({
|
|
257
|
+
text: block.text,
|
|
258
|
+
isOpeningMergedSection: block.isOpeningMergedSection || isMergeSeamPending,
|
|
259
|
+
});
|
|
260
|
+
isMergeSeamPending = false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return { ...region, blocks: uniqueBlocks };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Renders one region back into its markdown representation
|
|
268
|
+
*
|
|
269
|
+
* @param region The region to render
|
|
270
|
+
* @returns The heading line together with its joined blocks
|
|
271
|
+
*
|
|
272
|
+
* @private internal utility of `deduplicateSystemMessage`
|
|
273
|
+
*/
|
|
274
|
+
function renderRegion(region: SystemMessageRegion): string {
|
|
275
|
+
const body = joinBlocks(region.blocks);
|
|
276
|
+
|
|
277
|
+
if (region.headingLine === null) {
|
|
278
|
+
return body;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (body === '') {
|
|
282
|
+
return region.headingLine;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return `${region.headingLine}${region.isBodySeparatedByBlankLine ? '\n\n' : '\n'}${body}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Joins blocks of one region back together
|
|
290
|
+
*
|
|
291
|
+
* @param blocks The blocks to join in their original order
|
|
292
|
+
* @returns The joined body of one region
|
|
293
|
+
*
|
|
294
|
+
* @private internal utility of `renderRegion`
|
|
295
|
+
*/
|
|
296
|
+
function joinBlocks(blocks: ReadonlyArray<SystemMessageBlock>): string {
|
|
297
|
+
return blocks
|
|
298
|
+
.map((block, index) =>
|
|
299
|
+
index === 0 ? block.text : `${createBlockSeparator(blocks[index - 1]!, block)}${block.text}`,
|
|
300
|
+
)
|
|
301
|
+
.join('');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Chooses the separator between two neighboring blocks
|
|
306
|
+
*
|
|
307
|
+
* Blocks are normally separated by a blank line, exactly as they were written. Only where two sections were
|
|
308
|
+
* merged together their lists are joined into one compact list instead of two separate ones.
|
|
309
|
+
*
|
|
310
|
+
* @param previousBlock The block rendered before the separator
|
|
311
|
+
* @param nextBlock The block rendered after the separator
|
|
312
|
+
* @returns Either a single or a double newline
|
|
313
|
+
*
|
|
314
|
+
* @private internal utility of `joinBlocks`
|
|
315
|
+
*/
|
|
316
|
+
function createBlockSeparator(previousBlock: SystemMessageBlock, nextBlock: SystemMessageBlock): string {
|
|
317
|
+
if (!nextBlock.isOpeningMergedSection) {
|
|
318
|
+
return '\n\n';
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const previousBlockLines = previousBlock.text.split('\n');
|
|
322
|
+
const isListContinuing =
|
|
323
|
+
LIST_ITEM_PATTERN.test(previousBlockLines[previousBlockLines.length - 1]!) &&
|
|
324
|
+
LIST_ITEM_PATTERN.test(nextBlock.text.split('\n')[0]!);
|
|
325
|
+
|
|
326
|
+
return isListContinuing ? '\n' : '\n\n';
|
|
327
|
+
}
|