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.
- package/dist/application/session-service.d.ts +1 -0
- package/dist/application/session-service.js +129 -13
- package/dist/application/subagent-service.js +7 -1
- package/dist/application/top-level-agent-manager.js +8 -0
- package/dist/cli-runtime.js +96 -33
- package/dist/compatibility/claude/context.d.ts +19 -4
- package/dist/compatibility/claude/context.js +198 -24
- package/dist/core/context.d.ts +13 -0
- package/dist/core/prompt-composer.d.ts +27 -0
- package/dist/core/prompt-composer.js +99 -0
- package/dist/core/runtime.d.ts +4 -0
- package/dist/core/runtime.js +5 -0
- package/dist/mcp/claude-mcp-tools.d.ts +9 -0
- package/dist/mcp/claude-mcp-tools.js +25 -1
- package/dist/providers/anthropic-compatible.d.ts +3 -0
- package/dist/providers/anthropic-compatible.js +78 -7
- package/dist/providers/anthropic-prompt-cache.d.ts +11 -0
- package/dist/providers/anthropic-prompt-cache.js +58 -0
- package/dist/providers/environment.js +12 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ModelProviderError, } from '../core/runtime.js';
|
|
2
2
|
import { transportFailureKind } from './provider-errors.js';
|
|
3
|
+
import { createAnthropicPromptCachePolicyResolver, } from './anthropic-prompt-cache.js';
|
|
3
4
|
function isRecord(value) {
|
|
4
5
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
6
|
}
|
|
@@ -451,6 +452,34 @@ function parseSseEvent(data, state, maxToolArgumentsBytes, maxToolCallsPerRespon
|
|
|
451
452
|
}
|
|
452
453
|
return [];
|
|
453
454
|
}
|
|
455
|
+
const ANTHROPIC_CACHE_LOOKBACK_BLOCKS = 20;
|
|
456
|
+
function cacheControl(ttl) {
|
|
457
|
+
return {
|
|
458
|
+
type: 'ephemeral',
|
|
459
|
+
...(ttl === '1h' ? { ttl } : {}),
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function markLatestCacheableMessageBlock(messages, control) {
|
|
463
|
+
let inspectedBlocks = 0;
|
|
464
|
+
for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex--) {
|
|
465
|
+
const content = messages[messageIndex]?.content;
|
|
466
|
+
if (!content)
|
|
467
|
+
continue;
|
|
468
|
+
for (let blockIndex = content.length - 1; blockIndex >= 0; blockIndex--) {
|
|
469
|
+
if (inspectedBlocks >= ANTHROPIC_CACHE_LOOKBACK_BLOCKS)
|
|
470
|
+
return;
|
|
471
|
+
inspectedBlocks += 1;
|
|
472
|
+
const block = content[blockIndex];
|
|
473
|
+
if (!block ||
|
|
474
|
+
block.type === 'thinking' ||
|
|
475
|
+
block.type === 'redacted_thinking') {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
content[blockIndex] = { ...block, cache_control: control };
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
454
483
|
function serializeMediaContent(message) {
|
|
455
484
|
const blocks = message.contentBlocks
|
|
456
485
|
? [...message.contentBlocks]
|
|
@@ -481,9 +510,10 @@ function serializeMediaContent(message) {
|
|
|
481
510
|
},
|
|
482
511
|
});
|
|
483
512
|
}
|
|
484
|
-
function serializeMessages(messages) {
|
|
513
|
+
function serializeMessages(messages, stableSystemMessageCount, promptCaching) {
|
|
485
514
|
const system = [];
|
|
486
515
|
const serialized = [];
|
|
516
|
+
let systemIndex = 0;
|
|
487
517
|
const append = (role, content) => {
|
|
488
518
|
if (content.length === 0)
|
|
489
519
|
return;
|
|
@@ -495,7 +525,15 @@ function serializeMessages(messages) {
|
|
|
495
525
|
};
|
|
496
526
|
for (const message of messages) {
|
|
497
527
|
if (message.role === 'system') {
|
|
498
|
-
|
|
528
|
+
const block = {
|
|
529
|
+
type: 'text',
|
|
530
|
+
text: `${systemIndex === 0 ? '' : '\n\n'}${message.content}`,
|
|
531
|
+
...(promptCaching && systemIndex + 1 === stableSystemMessageCount
|
|
532
|
+
? { cache_control: promptCaching }
|
|
533
|
+
: {}),
|
|
534
|
+
};
|
|
535
|
+
system.push(block);
|
|
536
|
+
systemIndex += 1;
|
|
499
537
|
continue;
|
|
500
538
|
}
|
|
501
539
|
if (message.role === 'user') {
|
|
@@ -553,7 +591,24 @@ function serializeMessages(messages) {
|
|
|
553
591
|
}
|
|
554
592
|
append('assistant', content);
|
|
555
593
|
}
|
|
556
|
-
|
|
594
|
+
if (promptCaching) {
|
|
595
|
+
markLatestCacheableMessageBlock(serialized, promptCaching);
|
|
596
|
+
}
|
|
597
|
+
return { system, messages: serialized };
|
|
598
|
+
}
|
|
599
|
+
function validateStableSystemPrefix(request) {
|
|
600
|
+
const count = request.stableSystemMessageCount;
|
|
601
|
+
if (count === undefined)
|
|
602
|
+
return;
|
|
603
|
+
const systemMessageCount = request.messages.filter((message) => message.role === 'system').length;
|
|
604
|
+
if (!Number.isSafeInteger(count) || count < 0 || count > systemMessageCount) {
|
|
605
|
+
throw new Error('Stable system message count must identify a valid system-message prefix');
|
|
606
|
+
}
|
|
607
|
+
if (request.messages
|
|
608
|
+
.slice(0, count)
|
|
609
|
+
.some((message) => message.role !== 'system')) {
|
|
610
|
+
throw new Error('Stable system message count must identify a valid system-message prefix');
|
|
611
|
+
}
|
|
557
612
|
}
|
|
558
613
|
export class AnthropicCompatibleProvider {
|
|
559
614
|
options;
|
|
@@ -569,6 +624,7 @@ export class AnthropicCompatibleProvider {
|
|
|
569
624
|
maxToolMetadataBytes;
|
|
570
625
|
maxErrorBodyBytes;
|
|
571
626
|
thinking;
|
|
627
|
+
promptCaching;
|
|
572
628
|
constructor(options) {
|
|
573
629
|
this.options = options;
|
|
574
630
|
if (options.contextWindowTokens !== undefined) {
|
|
@@ -601,6 +657,16 @@ export class AnthropicCompatibleProvider {
|
|
|
601
657
|
terminalReasons: true,
|
|
602
658
|
};
|
|
603
659
|
this.thinking = validateThinking(options.thinking);
|
|
660
|
+
const promptCaching = options.promptCaching === false
|
|
661
|
+
? undefined
|
|
662
|
+
: (options.promptCaching ??
|
|
663
|
+
createAnthropicPromptCachePolicyResolver({}, 'native')({
|
|
664
|
+
baseUrl: options.baseUrl,
|
|
665
|
+
model: options.model,
|
|
666
|
+
}));
|
|
667
|
+
this.promptCaching = promptCaching
|
|
668
|
+
? cacheControl(promptCaching.ttl)
|
|
669
|
+
: undefined;
|
|
604
670
|
this.anthropicVersion = options.anthropicVersion ?? '2023-06-01';
|
|
605
671
|
this.maxStreamBufferBytes = options.maxStreamBufferBytes ?? 1024 * 1024;
|
|
606
672
|
this.maxToolArgumentsBytes = options.maxToolArgumentsBytes ?? 1024 * 1024;
|
|
@@ -609,6 +675,7 @@ export class AnthropicCompatibleProvider {
|
|
|
609
675
|
this.maxErrorBodyBytes = options.maxErrorBodyBytes ?? 64 * 1024;
|
|
610
676
|
}
|
|
611
677
|
async *complete(request) {
|
|
678
|
+
validateStableSystemPrefix(request);
|
|
612
679
|
if (request.webSearch && !this.capabilities.webSearch) {
|
|
613
680
|
throw new Error('Provider does not support web search');
|
|
614
681
|
}
|
|
@@ -631,7 +698,8 @@ export class AnthropicCompatibleProvider {
|
|
|
631
698
|
? ['interleaved-thinking-2025-05-14']
|
|
632
699
|
: []),
|
|
633
700
|
].filter((beta, index, all) => all.indexOf(beta) === index);
|
|
634
|
-
const serialized = serializeMessages(request.messages);
|
|
701
|
+
const serialized = serializeMessages(request.messages, request.stableSystemMessageCount, this.promptCaching);
|
|
702
|
+
const requestTools = request.tools;
|
|
635
703
|
const requestInit = {
|
|
636
704
|
method: 'POST',
|
|
637
705
|
headers: {
|
|
@@ -649,7 +717,7 @@ export class AnthropicCompatibleProvider {
|
|
|
649
717
|
...(request.effort
|
|
650
718
|
? { output_config: { effort: request.effort } }
|
|
651
719
|
: {}),
|
|
652
|
-
...(serialized.system ? { system: serialized.system } : {}),
|
|
720
|
+
...(serialized.system.length ? { system: serialized.system } : {}),
|
|
653
721
|
...(request.webSearch
|
|
654
722
|
? {
|
|
655
723
|
tools: [
|
|
@@ -667,12 +735,15 @@ export class AnthropicCompatibleProvider {
|
|
|
667
735
|
],
|
|
668
736
|
tool_choice: { type: 'tool', name: 'web_search' },
|
|
669
737
|
}
|
|
670
|
-
:
|
|
738
|
+
: requestTools?.length
|
|
671
739
|
? {
|
|
672
|
-
tools:
|
|
740
|
+
tools: requestTools.map((tool, index) => ({
|
|
673
741
|
name: tool.name,
|
|
674
742
|
description: tool.description,
|
|
675
743
|
input_schema: tool.inputSchema,
|
|
744
|
+
...(this.promptCaching && index === requestTools.length - 1
|
|
745
|
+
? { cache_control: this.promptCaching }
|
|
746
|
+
: {}),
|
|
676
747
|
})),
|
|
677
748
|
}
|
|
678
749
|
: {}),
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type AnthropicPromptCachePolicy = false | {
|
|
2
|
+
ttl: '5m' | '1h';
|
|
3
|
+
};
|
|
4
|
+
export interface AnthropicPromptCacheTarget {
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
model: string;
|
|
7
|
+
}
|
|
8
|
+
/** Captures prompt-cache configuration once, then resolves a policy for each
|
|
9
|
+
* Anthropic model adapter created during the same session. */
|
|
10
|
+
export declare function createAnthropicPromptCachePolicyResolver(environment: NodeJS.ProcessEnv, dataPlane: 'native' | 'claude'): (target: AnthropicPromptCacheTarget) => AnthropicPromptCachePolicy;
|
|
11
|
+
//# sourceMappingURL=anthropic-prompt-cache.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
function officialAnthropicEndpoint(baseUrl) {
|
|
2
|
+
try {
|
|
3
|
+
return new URL(baseUrl).hostname === 'api.anthropic.com';
|
|
4
|
+
}
|
|
5
|
+
catch {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function modelFamily(model) {
|
|
10
|
+
const normalized = model.toLowerCase();
|
|
11
|
+
if (normalized.includes('haiku'))
|
|
12
|
+
return 'haiku';
|
|
13
|
+
if (normalized.includes('sonnet'))
|
|
14
|
+
return 'sonnet';
|
|
15
|
+
if (normalized.includes('opus'))
|
|
16
|
+
return 'opus';
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
/** Captures prompt-cache configuration once, then resolves a policy for each
|
|
20
|
+
* Anthropic model adapter created during the same session. */
|
|
21
|
+
export function createAnthropicPromptCachePolicyResolver(environment, dataPlane) {
|
|
22
|
+
const praxisEnabled = environment.PRAXIS_ANTHROPIC_PROMPT_CACHING;
|
|
23
|
+
if (praxisEnabled !== undefined &&
|
|
24
|
+
praxisEnabled !== 'true' &&
|
|
25
|
+
praxisEnabled !== 'false') {
|
|
26
|
+
throw new Error('PRAXIS_ANTHROPIC_PROMPT_CACHING must be true or false');
|
|
27
|
+
}
|
|
28
|
+
const praxisTtl = environment.PRAXIS_ANTHROPIC_PROMPT_CACHE_TTL;
|
|
29
|
+
if (praxisTtl !== undefined && praxisTtl !== '5m' && praxisTtl !== '1h') {
|
|
30
|
+
throw new Error('PRAXIS_ANTHROPIC_PROMPT_CACHE_TTL must be 5m or 1h');
|
|
31
|
+
}
|
|
32
|
+
if (praxisEnabled === 'false' && praxisTtl !== undefined) {
|
|
33
|
+
throw new Error('PRAXIS_ANTHROPIC_PROMPT_CACHE_TTL cannot be set when prompt caching is false');
|
|
34
|
+
}
|
|
35
|
+
const claudeCompatibility = dataPlane === 'claude';
|
|
36
|
+
const disableAll = environment.DISABLE_PROMPT_CACHING === '1';
|
|
37
|
+
const disabledFamilies = new Set(['haiku', 'sonnet', 'opus'].filter((family) => environment[`DISABLE_PROMPT_CACHING_${family.toUpperCase()}`] === '1'));
|
|
38
|
+
const forceFiveMinutes = environment.FORCE_PROMPT_CACHING_5M === '1';
|
|
39
|
+
const enableOneHour = environment.ENABLE_PROMPT_CACHING_1H === '1';
|
|
40
|
+
return ({ baseUrl, model }) => {
|
|
41
|
+
if (claudeCompatibility) {
|
|
42
|
+
const family = modelFamily(model);
|
|
43
|
+
if (disableAll || (family && disabledFamilies.has(family)))
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (praxisEnabled === 'false')
|
|
47
|
+
return false;
|
|
48
|
+
if (claudeCompatibility && forceFiveMinutes)
|
|
49
|
+
return { ttl: '5m' };
|
|
50
|
+
if (claudeCompatibility && enableOneHour)
|
|
51
|
+
return { ttl: '1h' };
|
|
52
|
+
if (praxisEnabled === 'true' || praxisTtl !== undefined) {
|
|
53
|
+
return { ttl: praxisTtl ?? '5m' };
|
|
54
|
+
}
|
|
55
|
+
return officialAnthropicEndpoint(baseUrl) ? { ttl: '5m' } : false;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=anthropic-prompt-cache.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createAnthropicPromptCachePolicyResolver } from './anthropic-prompt-cache.js';
|
|
1
2
|
export function parseProviderEnvironment(environment) {
|
|
2
3
|
const provider = environment.PRAXIS_PROVIDER ?? 'openai';
|
|
3
4
|
if (provider !== 'openai' && provider !== 'anthropic') {
|
|
@@ -29,6 +30,17 @@ export function parseProviderEnvironment(environment) {
|
|
|
29
30
|
if (provider === 'openai' && webSearch !== undefined) {
|
|
30
31
|
throw new Error('PRAXIS_ANTHROPIC_WEB_SEARCH requires PRAXIS_PROVIDER=anthropic');
|
|
31
32
|
}
|
|
33
|
+
for (const name of [
|
|
34
|
+
'PRAXIS_ANTHROPIC_PROMPT_CACHING',
|
|
35
|
+
'PRAXIS_ANTHROPIC_PROMPT_CACHE_TTL',
|
|
36
|
+
]) {
|
|
37
|
+
if (provider === 'openai' && environment[name] !== undefined) {
|
|
38
|
+
throw new Error(`${name} requires PRAXIS_PROVIDER=anthropic`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (provider === 'anthropic') {
|
|
42
|
+
createAnthropicPromptCachePolicyResolver(environment, 'native');
|
|
43
|
+
}
|
|
32
44
|
return {
|
|
33
45
|
provider,
|
|
34
46
|
baseUrl: environment.PRAXIS_BASE_URL ??
|