praxis-agent 0.28.0 → 0.30.0

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.
@@ -1,4 +1,5 @@
1
1
  import { isAbsolute, matchesGlob, relative, resolve, sep } from 'node:path';
2
+ import { PromptComposer, } from '../../core/prompt-composer.js';
2
3
  import { renderClaudeDynamicSystemContext, renderClaudeDynamicUserContext, } from './dynamic-context.js';
3
4
  function renderResources(title, resources) {
4
5
  const rendered = resources
@@ -40,22 +41,32 @@ export class ClaudeConditionalRuleResolver {
40
41
  }
41
42
  export class ClaudeContextAssembler {
42
43
  options;
44
+ composer = new PromptComposer();
45
+ snapshots = new Map();
43
46
  constructor(options) {
44
47
  this.options = options;
45
48
  }
46
49
  async assemble(options = {}) {
47
- const resources = await this.options.loadResources(options.cwd);
50
+ const snapshot = this.snapshot(options);
51
+ const mode = options.mode ??
52
+ (this.options.systemPrompt !== undefined
53
+ ? 'custom'
54
+ : this.options.bare
55
+ ? 'bare'
56
+ : 'default');
57
+ const resources = mode === 'bare'
58
+ ? { instructions: [], conditionalRules: [], memoryIndex: null }
59
+ : await this.loadResources(snapshot, options.cwd);
48
60
  const sections = [
49
61
  renderResources('Instructions', resources.instructions),
50
62
  renderResources('Auto-memory', resources.memoryIndex ? [limitMemoryIndex(resources.memoryIndex)] : []),
51
63
  ].filter((section) => section !== null);
52
- const messages = [];
53
- if (this.options.systemPrompt !== undefined) {
54
- messages.push({ role: 'system', content: this.options.systemPrompt });
55
- }
64
+ const sessionSections = [];
56
65
  if (sections.length > 0) {
57
- messages.push({
58
- role: 'system',
66
+ sessionSections.push({
67
+ id: 'shared-resources',
68
+ placement: 'system',
69
+ stability: 'session',
59
70
  content: `# Shared Claude context
60
71
 
61
72
  Instructions are ordered from broadest to most specific. Auto-memory is background context and does not override instructions.
@@ -63,36 +74,199 @@ Instructions are ordered from broadest to most specific. Auto-memory is backgrou
63
74
  ${sections.join('\n\n')}`,
64
75
  });
65
76
  }
66
- let firstUserMessageContext;
67
- if (this.options.systemPrompt === undefined &&
68
- this.options.loadDynamicContext !== undefined) {
69
- const dynamic = await this.options.loadDynamicContext(options.cwd);
77
+ const tailSections = [];
78
+ if (mode !== 'bare') {
79
+ sessionSections.push({
80
+ id: 'current-date',
81
+ placement: 'system',
82
+ stability: 'session',
83
+ content: `# Current date\n${this.currentDate(snapshot)}`,
84
+ });
85
+ }
86
+ if (mode !== 'bare') {
87
+ const guidance = await this.loadSessionGuidance(snapshot);
88
+ if (guidance?.trim()) {
89
+ sessionSections.push({
90
+ id: 'session-guidance',
91
+ placement: 'system',
92
+ stability: 'session',
93
+ content: guidance,
94
+ });
95
+ }
96
+ const mcpInstructions = await this.loadMcpInstructions(snapshot);
97
+ tailSections.push(...mcpInstructions
98
+ .filter(({ instructions }) => instructions.trim().length > 0)
99
+ .sort((left, right) => left.server.localeCompare(right.server))
100
+ .map(({ server, instructions }) => ({
101
+ id: `mcp-instructions:${server}`,
102
+ placement: 'system',
103
+ stability: 'volatile',
104
+ content: `# MCP server instructions: ${server}\n${instructions.trimEnd()}`,
105
+ })));
106
+ }
107
+ if (mode !== 'bare' && this.options.loadDynamicContext !== undefined) {
108
+ const dynamic = await this.loadDynamicContext(snapshot, options.cwd);
70
109
  if (this.options.excludeDynamicSystemPromptSections) {
71
110
  if (dynamic.memory) {
72
- messages.push({ role: 'system', content: dynamic.memory });
111
+ sessionSections.push({
112
+ id: 'memory-mechanics',
113
+ placement: 'system',
114
+ stability: 'session',
115
+ content: dynamic.memory,
116
+ });
73
117
  }
74
- firstUserMessageContext = renderClaudeDynamicUserContext({
75
- environment: dynamic.environment,
76
- ...(dynamic.gitStatus ? { gitStatus: dynamic.gitStatus } : {}),
118
+ tailSections.push({
119
+ id: 'relocated-runtime-context',
120
+ placement: 'first-user',
121
+ stability: 'session',
122
+ content: renderClaudeDynamicUserContext({
123
+ environment: dynamic.environment,
124
+ ...(dynamic.gitStatus ? { gitStatus: dynamic.gitStatus } : {}),
125
+ }),
77
126
  });
78
127
  }
79
128
  else {
80
- messages.push({
81
- role: 'system',
129
+ sessionSections.push({
130
+ id: 'runtime-context',
131
+ placement: 'system',
132
+ stability: 'session',
82
133
  content: renderClaudeDynamicSystemContext(dynamic),
83
134
  });
84
135
  }
85
136
  }
86
- if (this.options.appendSystemPrompt !== undefined) {
87
- messages.push({
88
- role: 'system',
89
- content: this.options.appendSystemPrompt,
90
- });
137
+ for (const section of options.additionalSections ?? []) {
138
+ const target = section.stability === 'volatile' || section.placement === 'first-user'
139
+ ? tailSections
140
+ : sessionSections;
141
+ target.push(section);
91
142
  }
143
+ const composition = this.composer.compose({
144
+ mode,
145
+ ...(options.baseSystemPrompt !== undefined
146
+ ? { baseSystemPrompt: options.baseSystemPrompt }
147
+ : this.options.systemPrompt !== undefined
148
+ ? { baseSystemPrompt: this.options.systemPrompt }
149
+ : {}),
150
+ ...(this.options.appendSystemPrompt !== undefined
151
+ ? { appendSystemPrompt: this.options.appendSystemPrompt }
152
+ : {}),
153
+ sessionSections,
154
+ tailSections,
155
+ });
92
156
  return {
93
- systemMessages: messages,
94
- ...(firstUserMessageContext ? { firstUserMessageContext } : {}),
157
+ systemMessages: composition.systemMessages,
158
+ promptSections: composition.sections,
159
+ stableSystemSectionCount: composition.stableSystemSectionCount,
160
+ ...(composition.firstUserMessageContext
161
+ ? { firstUserMessageContext: composition.firstUserMessageContext }
162
+ : {}),
95
163
  };
96
164
  }
165
+ invalidate(options) {
166
+ for (const [key, snapshot] of this.snapshots) {
167
+ if (options.lifecycleId !== undefined &&
168
+ snapshot.lifecycleId !== options.lifecycleId)
169
+ continue;
170
+ if (options.reason === 'resource-reload' ||
171
+ options.reason === 'compact') {
172
+ delete snapshot.resources;
173
+ continue;
174
+ }
175
+ if (options.reason === 'tool-pool') {
176
+ delete snapshot.mcpInstructions;
177
+ delete snapshot.sessionGuidance;
178
+ continue;
179
+ }
180
+ this.snapshots.delete(key);
181
+ }
182
+ }
183
+ snapshot(options) {
184
+ if (!options.lifecycleId)
185
+ return undefined;
186
+ const key = JSON.stringify([options.lifecycleId, options.cwd ?? null]);
187
+ let snapshot = this.snapshots.get(key);
188
+ if (!snapshot) {
189
+ snapshot = {
190
+ lifecycleId: options.lifecycleId,
191
+ ...(options.cwd ? { cwd: options.cwd } : {}),
192
+ };
193
+ this.snapshots.set(key, snapshot);
194
+ }
195
+ return snapshot;
196
+ }
197
+ loadResources(snapshot, cwd) {
198
+ if (!snapshot)
199
+ return this.options.loadResources(cwd);
200
+ if (!snapshot.resources) {
201
+ const pending = this.options.loadResources(cwd);
202
+ snapshot.resources = pending;
203
+ void pending.catch(() => {
204
+ if (snapshot.resources === pending)
205
+ delete snapshot.resources;
206
+ });
207
+ }
208
+ return snapshot.resources;
209
+ }
210
+ loadDynamicContext(snapshot, cwd) {
211
+ const load = this.options.loadDynamicContext;
212
+ if (!load)
213
+ throw new Error('Dynamic context loader is unavailable');
214
+ if (!snapshot)
215
+ return load(cwd);
216
+ if (!snapshot.dynamic) {
217
+ const pending = load(cwd);
218
+ snapshot.dynamic = pending;
219
+ void pending.catch(() => {
220
+ if (snapshot.dynamic === pending)
221
+ delete snapshot.dynamic;
222
+ });
223
+ }
224
+ return snapshot.dynamic;
225
+ }
226
+ loadMcpInstructions(snapshot) {
227
+ const load = this.options.loadMcpInstructions;
228
+ if (!load)
229
+ return Promise.resolve([]);
230
+ if (!snapshot)
231
+ return load();
232
+ if (!snapshot.mcpInstructions) {
233
+ const pending = load();
234
+ snapshot.mcpInstructions = pending;
235
+ void pending.catch(() => {
236
+ if (snapshot.mcpInstructions === pending)
237
+ delete snapshot.mcpInstructions;
238
+ });
239
+ }
240
+ return snapshot.mcpInstructions;
241
+ }
242
+ loadSessionGuidance(snapshot) {
243
+ const load = this.options.loadSessionGuidance;
244
+ if (!load)
245
+ return Promise.resolve(undefined);
246
+ if (!snapshot)
247
+ return load();
248
+ if (!snapshot.sessionGuidance) {
249
+ const pending = load();
250
+ snapshot.sessionGuidance = pending;
251
+ void pending.catch(() => {
252
+ if (snapshot.sessionGuidance === pending)
253
+ delete snapshot.sessionGuidance;
254
+ });
255
+ }
256
+ return snapshot.sessionGuidance;
257
+ }
258
+ currentDate(snapshot) {
259
+ if (snapshot?.currentDate)
260
+ return snapshot.currentDate;
261
+ const now = (this.options.now ?? (() => new Date()))();
262
+ const date = [
263
+ String(now.getFullYear()).padStart(4, '0'),
264
+ String(now.getMonth() + 1).padStart(2, '0'),
265
+ String(now.getDate()).padStart(2, '0'),
266
+ ].join('-');
267
+ if (snapshot)
268
+ snapshot.currentDate = date;
269
+ return date;
270
+ }
97
271
  }
98
272
  //# sourceMappingURL=context.js.map
@@ -1,16 +1,29 @@
1
1
  import type { ModelMessage } from './runtime.js';
2
+ import type { PromptCompositionMode, PromptSection } from './prompt-composer.js';
2
3
  export type SystemContextMessage = Extract<ModelMessage, {
3
4
  role: 'system';
4
5
  }>;
5
6
  export interface AssembledContext {
6
7
  systemMessages: readonly SystemContextMessage[];
7
8
  firstUserMessageContext?: string;
9
+ promptSections?: readonly PromptSection[];
10
+ stableSystemSectionCount?: number;
8
11
  }
9
12
  export interface ContextAssemblyOptions {
10
13
  cwd?: string;
14
+ lifecycleId?: string;
15
+ mode?: PromptCompositionMode;
16
+ baseSystemPrompt?: string;
17
+ additionalSections?: readonly PromptSection[];
18
+ }
19
+ export type ContextInvalidationReason = 'clear' | 'compact' | 'fork' | 'resource-reload' | 'restore' | 'tool-pool' | 'cwd' | 'worktree';
20
+ export interface ContextInvalidationOptions {
21
+ lifecycleId?: string;
22
+ reason: ContextInvalidationReason;
11
23
  }
12
24
  export interface ContextAssembler {
13
25
  assemble(options?: ContextAssemblyOptions): Promise<AssembledContext>;
26
+ invalidate?(options: ContextInvalidationOptions): void;
14
27
  }
15
28
  export declare function injectFirstUserMessageContext(messages: readonly ModelMessage[], context: string | undefined): ModelMessage[];
16
29
  //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1,27 @@
1
+ import type { SystemContextMessage } from './context.js';
2
+ export type PromptSectionStability = 'static' | 'session' | 'volatile';
3
+ export type PromptSectionPlacement = 'system' | 'first-user';
4
+ export type PromptCompositionMode = 'default' | 'custom' | 'bare' | 'agent' | 'subagent';
5
+ export interface PromptSection {
6
+ id: string;
7
+ content: string;
8
+ stability: PromptSectionStability;
9
+ placement: PromptSectionPlacement;
10
+ }
11
+ export interface PromptComposition {
12
+ sections: readonly PromptSection[];
13
+ systemMessages: readonly SystemContextMessage[];
14
+ firstUserMessageContext?: string;
15
+ stableSystemSectionCount: number;
16
+ }
17
+ export interface PromptCompositionOptions {
18
+ mode: PromptCompositionMode;
19
+ baseSystemPrompt?: string;
20
+ appendSystemPrompt?: string;
21
+ sessionSections?: readonly PromptSection[];
22
+ tailSections?: readonly PromptSection[];
23
+ }
24
+ export declare class PromptComposer {
25
+ compose(options: PromptCompositionOptions): PromptComposition;
26
+ }
27
+ //# sourceMappingURL=prompt-composer.d.ts.map
@@ -0,0 +1,99 @@
1
+ const PRODUCT_POLICY = `# Praxis
2
+
3
+ You are Praxis, a local CLI coding agent. Work until the user's request is genuinely handled or a concrete blocker requires their input.
4
+
5
+ - Inspect relevant context before acting and use available tools deliberately.
6
+ - Preserve user data, existing changes, and project-specific instructions.
7
+ - Prefer focused, verifiable changes and report important outcomes clearly.
8
+ - Use a scratch or work area when the runtime provides one; keep temporary artifacts out of the user's project.
9
+ - Summarize or clear bulky intermediate results once their useful facts have been retained.
10
+ - Treat the context window as bounded: preserve decisions and evidence before reducing detail.
11
+ - Keep responses concise and use the user's language unless they request otherwise.`;
12
+ function baseSection(mode, content) {
13
+ if (mode === 'bare')
14
+ return undefined;
15
+ if (mode === 'default') {
16
+ return {
17
+ id: 'product-policy',
18
+ content: PRODUCT_POLICY,
19
+ placement: 'system',
20
+ stability: 'static',
21
+ };
22
+ }
23
+ if (content === undefined || content.trim().length === 0) {
24
+ throw new Error(`${mode} prompt mode requires a base system prompt`);
25
+ }
26
+ return {
27
+ id: mode === 'custom'
28
+ ? 'custom-system'
29
+ : mode === 'agent'
30
+ ? 'agent-policy'
31
+ : 'subagent-policy',
32
+ content,
33
+ placement: 'system',
34
+ stability: 'session',
35
+ };
36
+ }
37
+ function normalizedSections(sections, group) {
38
+ return sections
39
+ .filter((section) => section.content.trim().length > 0)
40
+ .map((section) => {
41
+ if (!section.id.trim())
42
+ throw new Error('Prompt section id is required');
43
+ if (group === 'sessionSections' && section.stability === 'volatile') {
44
+ throw new Error('sessionSections cannot contain volatile prompt sections');
45
+ }
46
+ if (group === 'tailSections' &&
47
+ section.placement === 'system' &&
48
+ section.stability !== 'volatile') {
49
+ throw new Error('tailSections system entries must be volatile prompt sections');
50
+ }
51
+ return { ...section };
52
+ });
53
+ }
54
+ export class PromptComposer {
55
+ compose(options) {
56
+ const base = baseSection(options.mode, options.baseSystemPrompt);
57
+ const sessionSections = normalizedSections(options.sessionSections ?? [], 'sessionSections');
58
+ const append = options.appendSystemPrompt?.trim()
59
+ ? {
60
+ id: 'append-system',
61
+ content: options.appendSystemPrompt,
62
+ placement: 'system',
63
+ stability: 'session',
64
+ }
65
+ : undefined;
66
+ const tailSections = normalizedSections(options.tailSections ?? [], 'tailSections');
67
+ const sections = [
68
+ ...(base ? [base] : []),
69
+ ...sessionSections,
70
+ ...(append ? [append] : []),
71
+ ...tailSections,
72
+ ];
73
+ const identities = new Set();
74
+ for (const section of sections) {
75
+ if (identities.has(section.id)) {
76
+ throw new Error(`Duplicate prompt section ${section.id}`);
77
+ }
78
+ identities.add(section.id);
79
+ }
80
+ const systemSections = sections.filter((section) => section.placement === 'system');
81
+ const firstVolatileSystemIndex = systemSections.findIndex((section) => section.stability === 'volatile');
82
+ const firstUserMessageContext = sections
83
+ .filter((section) => section.placement === 'first-user')
84
+ .map((section) => section.content)
85
+ .join('\n\n');
86
+ return {
87
+ sections,
88
+ systemMessages: systemSections.map((section) => ({
89
+ role: 'system',
90
+ content: section.content,
91
+ })),
92
+ stableSystemSectionCount: firstVolatileSystemIndex < 0
93
+ ? systemSections.length
94
+ : firstVolatileSystemIndex,
95
+ ...(firstUserMessageContext ? { firstUserMessageContext } : {}),
96
+ };
97
+ }
98
+ }
99
+ //# sourceMappingURL=prompt-composer.js.map
@@ -119,6 +119,8 @@ export type ModelStreamEvent = {
119
119
  };
120
120
  export interface ModelRequest {
121
121
  messages: readonly ModelMessage[];
122
+ /** Number of leading system messages that form the stable prompt prefix. */
123
+ stableSystemMessageCount?: number;
122
124
  tools?: readonly ModelToolDefinition[];
123
125
  webSearch?: ModelWebSearch;
124
126
  signal?: AbortSignal;
@@ -450,6 +452,8 @@ export interface AgentRuntimeOptions {
450
452
  }
451
453
  export interface AgentRunRequest {
452
454
  messages: readonly ModelMessage[];
455
+ /** Number of leading system messages that form the stable prompt prefix. */
456
+ stableSystemMessageCount?: number;
453
457
  cwd?: string;
454
458
  toolResultDirectory?: string;
455
459
  observer?: AgentRunObserver;
@@ -342,6 +342,11 @@ export class AgentRuntime {
342
342
  this.emit({ type: 'state', state: 'awaiting-model' });
343
343
  const providerRequest = {
344
344
  messages: prepareProviderMessages(messages, this.provider.capabilities.images === true, this.provider.capabilities.documents === true),
345
+ ...(request.stableSystemMessageCount === undefined
346
+ ? {}
347
+ : {
348
+ stableSystemMessageCount: request.stableSystemMessageCount,
349
+ }),
345
350
  };
346
351
  if (definitions.length > 0)
347
352
  providerRequest.tools = definitions;
@@ -35,6 +35,7 @@ export interface ClaudeMcpRuntime {
35
35
  authenticate(name: string): Promise<void>;
36
36
  reload(): Promise<void>;
37
37
  tools(name: string): Promise<readonly ClaudeMcpToolInspection[]>;
38
+ instructions?(): readonly ClaudeMcpServerInstruction[];
38
39
  connectAgent?(options: {
39
40
  specs: readonly unknown[];
40
41
  base: ToolRegistry;
@@ -47,6 +48,10 @@ export interface ClaudeMcpRuntime {
47
48
  /** Release MCP transports and child processes owned by this runtime. */
48
49
  close?(): Promise<void>;
49
50
  }
51
+ export interface ClaudeMcpServerInstruction {
52
+ server: string;
53
+ instructions: string;
54
+ }
50
55
  export interface ClaudeMcpConfigurationStatus {
51
56
  name: string;
52
57
  path: string;
@@ -67,6 +72,7 @@ export interface ClaudeMcpToolRegistryOptions {
67
72
  signal?: AbortSignal;
68
73
  eventSink?: RuntimeEventSink;
69
74
  onPromptsChanged?: (prompts: readonly ClaudeMcpPromptDefinition[]) => void;
75
+ onInstructionsChanged?: (instructions: readonly ClaudeMcpServerInstruction[]) => void;
70
76
  authenticateServer?: (name: string) => Promise<void>;
71
77
  reloadResources?: () => Promise<readonly ClaudeJsonResource[]>;
72
78
  onElicitation?: (request: {
@@ -93,6 +99,7 @@ export declare class ClaudeMcpToolRegistry implements ToolRegistry, ClaudeMcpRun
93
99
  private readonly serverClients;
94
100
  private readonly reconnectableServers;
95
101
  private readonly serverCapabilities;
102
+ private readonly serverInstructions;
96
103
  private readonly reconnectingServers;
97
104
  private readonly promptOperations;
98
105
  private promptResultDirectoryPromise;
@@ -104,6 +111,7 @@ export declare class ClaudeMcpToolRegistry implements ToolRegistry, ClaudeMcpRun
104
111
  definitions(): readonly ModelToolDefinition[];
105
112
  schedulingPolicy(call: ModelToolCall): import("../core/runtime.js").ToolSchedulingPolicy;
106
113
  serverStatuses(): readonly ClaudeMcpServerStatus[];
114
+ instructions(): readonly ClaudeMcpServerInstruction[];
107
115
  inspect(): Promise<readonly ClaudeMcpServerStatus[]>;
108
116
  reconnect(name: string): Promise<void>;
109
117
  authenticate(name: string): Promise<void>;
@@ -137,6 +145,7 @@ export declare class ClaudeMcpToolRegistry implements ToolRegistry, ClaudeMcpRun
137
145
  private ensurePromptConnected;
138
146
  private reconnectServer;
139
147
  private publishPrompts;
148
+ private publishInstructions;
140
149
  private assertOpenGeneration;
141
150
  private trackPromptOperation;
142
151
  private promptResultDirectory;
@@ -680,6 +680,7 @@ export class ClaudeMcpToolRegistry {
680
680
  serverClients = new Map();
681
681
  reconnectableServers = new Map();
682
682
  serverCapabilities = new Map();
683
+ serverInstructions = new Map();
683
684
  reconnectingServers = new Map();
684
685
  promptOperations = new Set();
685
686
  promptResultDirectoryPromise;
@@ -705,10 +706,11 @@ export class ClaudeMcpToolRegistry {
705
706
  definitions() {
706
707
  return [
707
708
  ...this.options.base.definitions(),
709
+ ...(this.resourceServers.size > 0 ? MCP_RESOURCE_TOOL_DEFINITIONS : []),
708
710
  ...[...this.connectedTools.entries()]
709
711
  .filter(([name]) => !this.reservedTools.has(name))
712
+ .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
710
713
  .map(([, tool]) => tool.definition),
711
- ...(this.resourceServers.size > 0 ? MCP_RESOURCE_TOOL_DEFINITIONS : []),
712
714
  ];
713
715
  }
714
716
  schedulingPolicy(call) {
@@ -732,6 +734,11 @@ export class ClaudeMcpToolRegistry {
732
734
  status,
733
735
  }));
734
736
  }
737
+ instructions() {
738
+ return [...this.serverInstructions]
739
+ .map(([server, instructions]) => ({ server, instructions }))
740
+ .sort((left, right) => left.server.localeCompare(right.server));
741
+ }
735
742
  async inspect() {
736
743
  return this.runtimeStatuses();
737
744
  }
@@ -763,6 +770,7 @@ export class ClaudeMcpToolRegistry {
763
770
  this.statuses.clear();
764
771
  this.reconnectableServers.clear();
765
772
  this.serverCapabilities.clear();
773
+ this.serverInstructions.clear();
766
774
  const ambientSensitiveValues = sensitiveEnvironmentValues(process.env);
767
775
  const warn = (message) => this.options.onWarning?.(redactSensitiveText(message, ambientSensitiveValues));
768
776
  await this.configure(this.options.reloadResources
@@ -962,6 +970,7 @@ export class ClaudeMcpToolRegistry {
962
970
  await Promise.allSettled([...this.clients].map((client) => client.close()));
963
971
  this.clients.clear();
964
972
  this.serverClients.clear();
973
+ this.serverInstructions.clear();
965
974
  const directory = await this.promptResultDirectoryPromise?.catch(() => undefined);
966
975
  if (directory)
967
976
  await rm(directory, { recursive: true, force: true });
@@ -1052,7 +1061,9 @@ export class ClaudeMcpToolRegistry {
1052
1061
  if (this.closed || this.serverClients.get(serverName) !== client)
1053
1062
  return;
1054
1063
  this.serverCapabilities.delete(serverName);
1064
+ this.serverInstructions.delete(serverName);
1055
1065
  this.statuses.set(serverName, { name: serverName, status: 'failed' });
1066
+ this.publishInstructions();
1056
1067
  };
1057
1068
  client.setRequestHandler(ElicitRequestSchema, async (request) => {
1058
1069
  if (!this.options.onElicitation)
@@ -1131,6 +1142,13 @@ export class ClaudeMcpToolRegistry {
1131
1142
  ...(capabilities?.resources ? ['resources'] : []),
1132
1143
  ...(capabilities?.prompts ? ['prompts'] : []),
1133
1144
  ]);
1145
+ const serverInstruction = client.getInstructions()?.trim();
1146
+ if (serverInstruction) {
1147
+ this.serverInstructions.set(serverName, redactSensitiveText(serverInstruction, sensitiveValues));
1148
+ }
1149
+ else {
1150
+ this.serverInstructions.delete(serverName);
1151
+ }
1134
1152
  for (const [name, tool] of connectedTools)
1135
1153
  this.connectedTools.set(name, tool);
1136
1154
  if (capabilities?.resources) {
@@ -1157,6 +1175,7 @@ export class ClaudeMcpToolRegistry {
1157
1175
  status: 'connected',
1158
1176
  statusDetail: 'connected',
1159
1177
  });
1178
+ this.publishInstructions();
1160
1179
  }
1161
1180
  catch (error) {
1162
1181
  await client.close().catch(() => undefined);
@@ -1383,6 +1402,7 @@ export class ClaudeMcpToolRegistry {
1383
1402
  this.resourceServers.delete(serverName);
1384
1403
  this.promptServers.delete(serverName);
1385
1404
  this.serverCapabilities.delete(serverName);
1405
+ this.serverInstructions.delete(serverName);
1386
1406
  await this.connectServer(serverName, reconnectable.config, reconnectable.sensitiveValues, expectedGeneration);
1387
1407
  if (this.statuses.get(serverName)?.status !== 'connected') {
1388
1408
  throw new Error(`MCP server ${serverName} could not reconnect`);
@@ -1393,6 +1413,10 @@ export class ClaudeMcpToolRegistry {
1393
1413
  if (!this.closed)
1394
1414
  this.options.onPromptsChanged?.(this.prompts());
1395
1415
  }
1416
+ publishInstructions() {
1417
+ if (!this.closed)
1418
+ this.options.onInstructionsChanged?.(this.instructions());
1419
+ }
1396
1420
  assertOpenGeneration(expectedGeneration) {
1397
1421
  if (this.closed || expectedGeneration !== this.generation) {
1398
1422
  throw new Error('MCP registry is closed');
@@ -1,4 +1,5 @@
1
1
  import { type ModelProvider, type ModelRequest, type ModelStreamEvent, type ModelThinkingConfig } from '../core/runtime.js';
2
+ import { type AnthropicPromptCachePolicy } from './anthropic-prompt-cache.js';
2
3
  export interface AnthropicCompatibleProviderOptions {
3
4
  baseUrl: string;
4
5
  apiKey: string;
@@ -6,6 +7,7 @@ export interface AnthropicCompatibleProviderOptions {
6
7
  maxOutputTokens?: number;
7
8
  anthropicVersion?: string;
8
9
  webSearch?: boolean;
10
+ promptCaching?: AnthropicPromptCachePolicy;
9
11
  contextWindowTokens?: number;
10
12
  thinking?: ModelThinkingConfig;
11
13
  maxStreamBufferBytes?: number;
@@ -29,6 +31,7 @@ export declare class AnthropicCompatibleProvider implements ModelProvider {
29
31
  private readonly maxToolMetadataBytes;
30
32
  private readonly maxErrorBodyBytes;
31
33
  private readonly thinking;
34
+ private readonly promptCaching;
32
35
  constructor(options: AnthropicCompatibleProviderOptions);
33
36
  complete(request: ModelRequest): AsyncIterable<ModelStreamEvent>;
34
37
  }