@myagentroam/agent 0.9.171 → 0.9.172
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/agent-core/extensions/compaction.d.ts +16 -0
- package/dist/agent-core/extensions/compaction.js +42 -1
- package/dist/agent-core/model/anthropic-messages.js +40 -6
- package/dist/agent-core/model/attempt-diagnostics.d.ts +3 -0
- package/dist/agent-core/model/attempt-diagnostics.js +9 -0
- package/dist/agent-core/model/contracts.d.ts +9 -1
- package/dist/agent-core/model/openai-responses.js +4 -11
- package/dist/agent-core/prompts/resources.d.ts +0 -1
- package/dist/agent-core/prompts/resources.js +4 -14
- package/dist/agent-core/runtime/compaction/history.d.ts +3 -2
- package/dist/agent-core/runtime/compaction/history.js +42 -12
- package/dist/agent-core/runtime/compaction/operation.d.ts +5 -0
- package/dist/agent-core/runtime/compaction/operation.js +22 -2
- package/dist/agent-core/runtime/context/projection.js +1 -0
- package/dist/agent-core/runtime/context/request-budget.js +1 -1
- package/dist/agent-core/runtime/context/restore.d.ts +2 -2
- package/dist/agent-core/runtime/context/restore.js +24 -8
- package/dist/agent-core/runtime/context/world-state.d.ts +2 -5
- package/dist/agent-core/runtime/context/world-state.js +14 -14
- package/dist/agent-core/sdk/session.js +82 -49
- package/dist/agent-core/session/context-gc-replacements.js +14 -3
- package/dist/agent-core/session/jsonl-store.d.ts +9 -4
- package/dist/agent-core/session/jsonl-store.js +48 -1
- package/dist/agent-core/tools/code-mode/observations.js +19 -1
- package/dist/agent-core/tools/local/exec.d.ts +3 -1
- package/dist/agent-core/tools/local/exec.js +21 -0
- package/dist/agent-core/tools/local/registry.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/shared/model-text.d.ts +2 -1
- package/dist/shared/model-text.js +1 -0
- package/package.json +1 -1
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import type { ModelMessage, ModelUsage } from '../model/contracts.js';
|
|
2
|
+
export interface CoreCompactDiagnostic {
|
|
3
|
+
readonly modelId: string;
|
|
4
|
+
readonly providerModelId?: string;
|
|
5
|
+
readonly reasoningEffort: string;
|
|
6
|
+
readonly state: 'STARTED' | 'SUCCEEDED' | 'DECLINED' | 'FAILED';
|
|
7
|
+
readonly inputTokensEstimateBefore: number;
|
|
8
|
+
readonly inputTokensEstimateAfter: number;
|
|
9
|
+
readonly modelRequests: number;
|
|
10
|
+
readonly elapsedMs: number;
|
|
11
|
+
readonly reason?: 'INPUT_BUDGET_EXHAUSTED' | 'MODEL_INCOMPLETE' | 'MODEL_FAILED';
|
|
12
|
+
readonly errorCode?: string;
|
|
13
|
+
}
|
|
2
14
|
export interface CoreCompactMessage {
|
|
3
15
|
readonly role: 'user' | 'assistant' | 'tool' | 'tool_call';
|
|
4
16
|
readonly content: string;
|
|
@@ -14,6 +26,8 @@ export interface CoreCompactContext {
|
|
|
14
26
|
readonly summaryTokenBudget: number;
|
|
15
27
|
/** Return the Core compaction contract selected for this operation's main model snapshot. */
|
|
16
28
|
readonly getPrompt: () => string;
|
|
29
|
+
/** Private, bounded telemetry only; reporting never changes the generation result. */
|
|
30
|
+
readonly reportDiagnostic?: (diagnostic: CoreCompactDiagnostic) => Promise<void>;
|
|
17
31
|
}
|
|
18
32
|
export interface CoreCompactResult {
|
|
19
33
|
readonly summary: string;
|
|
@@ -21,5 +35,7 @@ export interface CoreCompactResult {
|
|
|
21
35
|
}
|
|
22
36
|
/** Copy only neutral evidence; a hook never receives provider payloads or history ownership. */
|
|
23
37
|
export declare function compactExtensionMessages(messages: readonly ModelMessage[]): readonly CoreCompactMessage[];
|
|
38
|
+
/** Whitelist scalar facts; never copy arbitrary plugin payloads into the session log. */
|
|
39
|
+
export declare function compactExtensionDiagnostic(value: unknown): CoreCompactDiagnostic | undefined;
|
|
24
40
|
/** Invalid plugin results decline generation rather than altering the checkpoint contract. */
|
|
25
41
|
export declare function compactExtensionResult(value: unknown, budget: number): CoreCompactResult | undefined;
|
|
@@ -2,7 +2,8 @@ import { countModelTokens } from '../runtime/token-budget.js';
|
|
|
2
2
|
/** Copy only neutral evidence; a hook never receives provider payloads or history ownership. */
|
|
3
3
|
export function compactExtensionMessages(messages) {
|
|
4
4
|
return freeze(messages
|
|
5
|
-
.filter((message) => message.role !== 'provider' &&
|
|
5
|
+
.filter((message) => message.role !== 'provider' &&
|
|
6
|
+
!['environment', 'private', 'compact_control'].includes(message.contextKind ?? ''))
|
|
6
7
|
.map((message) => ({
|
|
7
8
|
role: message.role,
|
|
8
9
|
content: message.content,
|
|
@@ -23,6 +24,46 @@ const usageKeys = new Set([
|
|
|
23
24
|
'hostedSearches',
|
|
24
25
|
'hostedFetches'
|
|
25
26
|
]);
|
|
27
|
+
/** Whitelist scalar facts; never copy arbitrary plugin payloads into the session log. */
|
|
28
|
+
export function compactExtensionDiagnostic(value) {
|
|
29
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
30
|
+
return undefined;
|
|
31
|
+
const input = value;
|
|
32
|
+
const label = (value, limit) => typeof value === 'string' &&
|
|
33
|
+
value.length > 0 &&
|
|
34
|
+
value.length <= limit &&
|
|
35
|
+
!/[\r\n]/u.test(value);
|
|
36
|
+
if (!label(input.modelId, 128) ||
|
|
37
|
+
!label(input.reasoningEffort, 32) ||
|
|
38
|
+
typeof input.state !== 'string' ||
|
|
39
|
+
!['STARTED', 'SUCCEEDED', 'DECLINED', 'FAILED'].includes(input.state))
|
|
40
|
+
return undefined;
|
|
41
|
+
for (const key of [
|
|
42
|
+
'inputTokensEstimateBefore',
|
|
43
|
+
'inputTokensEstimateAfter',
|
|
44
|
+
'modelRequests',
|
|
45
|
+
'elapsedMs'
|
|
46
|
+
])
|
|
47
|
+
if (typeof input[key] !== 'number' || !Number.isSafeInteger(input[key]) || input[key] < 0)
|
|
48
|
+
return undefined;
|
|
49
|
+
return {
|
|
50
|
+
modelId: input.modelId,
|
|
51
|
+
reasoningEffort: input.reasoningEffort,
|
|
52
|
+
state: input.state,
|
|
53
|
+
inputTokensEstimateBefore: input.inputTokensEstimateBefore,
|
|
54
|
+
inputTokensEstimateAfter: input.inputTokensEstimateAfter,
|
|
55
|
+
modelRequests: input.modelRequests,
|
|
56
|
+
elapsedMs: input.elapsedMs,
|
|
57
|
+
...(label(input.providerModelId, 256) ? { providerModelId: input.providerModelId } : {}),
|
|
58
|
+
...(typeof input.reason === 'string' &&
|
|
59
|
+
['INPUT_BUDGET_EXHAUSTED', 'MODEL_INCOMPLETE', 'MODEL_FAILED'].includes(input.reason)
|
|
60
|
+
? { reason: input.reason }
|
|
61
|
+
: {}),
|
|
62
|
+
...(typeof input.errorCode === 'string' && /^[A-Z][A-Z0-9_]{0,127}$/u.test(input.errorCode)
|
|
63
|
+
? { errorCode: input.errorCode }
|
|
64
|
+
: {})
|
|
65
|
+
};
|
|
66
|
+
}
|
|
26
67
|
/** Invalid plugin results decline generation rather than altering the checkpoint contract. */
|
|
27
68
|
export function compactExtensionResult(value, budget) {
|
|
28
69
|
if (value === null ||
|
|
@@ -3,9 +3,10 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { MarAgentError } from '../error.js';
|
|
4
4
|
import { resolveModelDefaultReasoningEffort } from './configuration.js';
|
|
5
5
|
import { isRetryableUpstreamOverloadCode, number, parseSse, record, text, observeModelStreamEvent, modelStreamEventObservedAt } from './sse.js';
|
|
6
|
-
import { fetchModelResponse, modelRetryMaxAttempts, isRetryableModelRequestError, isRetryableModelTransportError, waitForModelRetry } from './http.js';
|
|
6
|
+
import { fetchModelResponse, modelRetryMaxAttempts, isRetryableModelRequestError, isRetryableModelTransportError, modelRetryDelay, waitForModelRetry } from './http.js';
|
|
7
7
|
import { anthropicThinkingBlock, assertAnthropicThinkingBudget } from './anthropic-thinking.js';
|
|
8
8
|
import { resolveModelTextBlocks } from './text-blocks.js';
|
|
9
|
+
import { reportModelAttempt } from './attempt-diagnostics.js';
|
|
9
10
|
export class AnthropicMessagesAdapter {
|
|
10
11
|
configuration;
|
|
11
12
|
constructor(configuration) {
|
|
@@ -16,13 +17,25 @@ export class AnthropicMessagesAdapter {
|
|
|
16
17
|
const maxAttempts = modelRetryMaxAttempts(retryState);
|
|
17
18
|
let resetOutputTiming = false;
|
|
18
19
|
while (retryState.attempts < maxAttempts) {
|
|
20
|
+
signal.throwIfAborted();
|
|
19
21
|
if (resetOutputTiming)
|
|
20
22
|
yield { type: 'output.reset' };
|
|
21
23
|
resetOutputTiming = false;
|
|
22
24
|
const attempt = retryState.attempts++;
|
|
25
|
+
const startedAt = Date.now();
|
|
26
|
+
const attemptDiagnostic = {
|
|
27
|
+
protocol: 'ANTHROPIC_MESSAGES',
|
|
28
|
+
transport: 'HTTP',
|
|
29
|
+
attempt: attempt + 1,
|
|
30
|
+
maxAttempts,
|
|
31
|
+
continuation: false,
|
|
32
|
+
credentialReason: 'REQUEST'
|
|
33
|
+
};
|
|
23
34
|
// Buffer non-semantic events until a semantic event makes this attempt non-retryable.
|
|
24
35
|
const prelude = [];
|
|
25
36
|
let sawSemanticEvent = false;
|
|
37
|
+
let firstSemanticEventType;
|
|
38
|
+
await reportModelAttempt(request, { ...attemptDiagnostic, state: 'STARTED' });
|
|
26
39
|
try {
|
|
27
40
|
for await (const event of this.startAttempt(request, signal)) {
|
|
28
41
|
if (event.type === 'output.delta') {
|
|
@@ -36,21 +49,42 @@ export class AnthropicMessagesAdapter {
|
|
|
36
49
|
}
|
|
37
50
|
if (!sawSemanticEvent) {
|
|
38
51
|
sawSemanticEvent = true;
|
|
52
|
+
firstSemanticEventType = event.type;
|
|
39
53
|
for (const pending of prelude)
|
|
40
54
|
yield pending;
|
|
41
55
|
prelude.length = 0;
|
|
42
56
|
}
|
|
43
57
|
yield event;
|
|
44
58
|
}
|
|
59
|
+
await reportModelAttempt(request, {
|
|
60
|
+
...attemptDiagnostic,
|
|
61
|
+
state: 'SUCCEEDED',
|
|
62
|
+
semanticEventObserved: sawSemanticEvent,
|
|
63
|
+
...(firstSemanticEventType === undefined ? {} : { firstSemanticEventType }),
|
|
64
|
+
durationMs: Math.max(0, Date.now() - startedAt)
|
|
65
|
+
});
|
|
45
66
|
return;
|
|
46
67
|
}
|
|
47
68
|
catch (error) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
const retryable = isRetryableModelRequestError(error) || isAnthropicRecoverableStreamError(error);
|
|
70
|
+
const willRetry = !signal.aborted && !sawSemanticEvent && retryable && attempt + 1 < maxAttempts;
|
|
71
|
+
const delayMs = willRetry ? modelRetryDelay(retryAfter(error), attempt) : undefined;
|
|
72
|
+
await reportModelAttempt(request, {
|
|
73
|
+
...attemptDiagnostic,
|
|
74
|
+
state: 'FAILED',
|
|
75
|
+
semanticEventObserved: sawSemanticEvent,
|
|
76
|
+
...(firstSemanticEventType === undefined ? {} : { firstSemanticEventType }),
|
|
77
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
78
|
+
errorCode: error instanceof MarAgentError ? error.code : 'MAR_AGENT_MODEL_ATTEMPT_FAILED',
|
|
79
|
+
retryable,
|
|
80
|
+
willRetry,
|
|
81
|
+
...(delayMs === undefined
|
|
82
|
+
? {}
|
|
83
|
+
: { retryDelayMs: delayMs, retryReason: 'STREAM' })
|
|
84
|
+
});
|
|
85
|
+
if (!willRetry)
|
|
52
86
|
throw error;
|
|
53
|
-
await waitForModelRetry(retryAfter(error), attempt, signal);
|
|
87
|
+
await waitForModelRetry(retryAfter(error), attempt, signal, delayMs);
|
|
54
88
|
}
|
|
55
89
|
}
|
|
56
90
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ModelAttemptDiagnostic, ModelRequest } from './contracts.js';
|
|
2
|
+
/** One observer boundary for all adapters; telemetry cannot change request outcomes. */
|
|
3
|
+
export declare function reportModelAttempt(request: ModelRequest, diagnostic: ModelAttemptDiagnostic): Promise<void>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** One observer boundary for all adapters; telemetry cannot change request outcomes. */
|
|
2
|
+
export async function reportModelAttempt(request, diagnostic) {
|
|
3
|
+
try {
|
|
4
|
+
await request.onAttemptDiagnostic?.(diagnostic);
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
// Diagnostics must never change the model request result.
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -5,6 +5,12 @@ export interface ModelTextBlock {
|
|
|
5
5
|
readonly text: string;
|
|
6
6
|
readonly cache?: true;
|
|
7
7
|
}
|
|
8
|
+
export interface ModelInputMetadata {
|
|
9
|
+
source: 'user' | 'parent_agent' | 'extension';
|
|
10
|
+
recordId?: string;
|
|
11
|
+
extensionId?: string;
|
|
12
|
+
truncated?: true;
|
|
13
|
+
}
|
|
8
14
|
export interface ModelMessage {
|
|
9
15
|
/** Model-facing tool-result status; independent of the actual execution outcome. */
|
|
10
16
|
isError?: boolean;
|
|
@@ -12,7 +18,9 @@ export interface ModelMessage {
|
|
|
12
18
|
role: 'user' | 'assistant' | 'tool' | 'tool_call' | 'provider';
|
|
13
19
|
content: string;
|
|
14
20
|
textBlocks?: readonly ModelTextBlock[];
|
|
15
|
-
contextKind?: 'environment' | 'private';
|
|
21
|
+
contextKind?: 'environment' | 'private' | 'contextual' | 'summary' | 'compact_control';
|
|
22
|
+
/** Input provenance and local truncation state; never serialized to a Provider. */
|
|
23
|
+
metadata?: ModelInputMetadata;
|
|
16
24
|
callId?: string;
|
|
17
25
|
name?: string;
|
|
18
26
|
arguments?: unknown;
|
|
@@ -8,6 +8,7 @@ import { isRetryableUpstreamOverloadCode, number, parseSse, record, text, observ
|
|
|
8
8
|
import { connectSocks5Target, fetchModelResponse, isRetryableModelHttpStatus, isRetryableModelTransportError, modelRetryMaxAttempts, isRetryableModelRequestError, modelRetryDelay, waitForModelRetry } from './http.js';
|
|
9
9
|
import { acquireModelCredential, appendModelCredentialHeaders } from './runtime-credential.js';
|
|
10
10
|
import { resolveModelTextBlocks } from './text-blocks.js';
|
|
11
|
+
import { reportModelAttempt } from './attempt-diagnostics.js';
|
|
11
12
|
const WEBSOCKET_IDLE_TIMEOUT_MS = 300_000;
|
|
12
13
|
const WEBSOCKET_SEND_FRAME_BYTES = 1024 * 1024;
|
|
13
14
|
export const OPENAI_RESPONSES_EARLY_EOF_REASON = 'OPENAI_RESPONSES_EARLY_EOF';
|
|
@@ -92,7 +93,7 @@ export class OpenAiResponsesAdapter {
|
|
|
92
93
|
let sawSemanticEvent = false;
|
|
93
94
|
let firstSemanticEventType;
|
|
94
95
|
turn.beginAttempt();
|
|
95
|
-
await
|
|
96
|
+
await reportModelAttempt(request, {
|
|
96
97
|
protocol: 'OPENAI_RESPONSES',
|
|
97
98
|
transport: attemptTransport,
|
|
98
99
|
state: 'STARTED',
|
|
@@ -121,7 +122,7 @@ export class OpenAiResponsesAdapter {
|
|
|
121
122
|
}
|
|
122
123
|
yield event;
|
|
123
124
|
}
|
|
124
|
-
await
|
|
125
|
+
await reportModelAttempt(request, {
|
|
125
126
|
protocol: 'OPENAI_RESPONSES',
|
|
126
127
|
transport: attemptTransport,
|
|
127
128
|
state: 'SUCCEEDED',
|
|
@@ -137,7 +138,7 @@ export class OpenAiResponsesAdapter {
|
|
|
137
138
|
return;
|
|
138
139
|
}
|
|
139
140
|
catch (error) {
|
|
140
|
-
const reportFailure = async (willRetry, options = {}) =>
|
|
141
|
+
const reportFailure = async (willRetry, options = {}) => reportModelAttempt(request, {
|
|
141
142
|
protocol: 'OPENAI_RESPONSES',
|
|
142
143
|
transport: attemptTransport,
|
|
143
144
|
state: 'FAILED',
|
|
@@ -1437,14 +1438,6 @@ function retryAfter(error) {
|
|
|
1437
1438
|
const value = error.details?.retryAfter;
|
|
1438
1439
|
return typeof value === 'string' ? value : null;
|
|
1439
1440
|
}
|
|
1440
|
-
async function reportOpenAiResponsesAttempt(request, diagnostic) {
|
|
1441
|
-
try {
|
|
1442
|
-
await request.onAttemptDiagnostic?.(diagnostic);
|
|
1443
|
-
}
|
|
1444
|
-
catch {
|
|
1445
|
-
// Diagnostics must never change the model request result.
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
1441
|
function openAiResponsesAttemptError(error) {
|
|
1449
1442
|
const diagnostic = describeMarAgentError(error, 'MAR_AGENT_MODEL_ATTEMPT_FAILED');
|
|
1450
1443
|
const causeCode = deepestCauseCode(error);
|
|
@@ -14,4 +14,3 @@ export interface RetainedSessionResources {
|
|
|
14
14
|
}[];
|
|
15
15
|
}
|
|
16
16
|
export declare function retainedSessionResourcesSnapshot(input: RetainedSessionResources): string | undefined;
|
|
17
|
-
export declare function retainedSessionResourcesUpdate(snapshot: string | undefined, previousSnapshot: string | undefined, previousMayContainResources?: boolean): string | undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { truncateModelText } from '../runtime/token-budget.js';
|
|
1
2
|
export function retainedSessionResourcesSnapshot(input) {
|
|
2
3
|
if (input.processes.length === 0 && input.subagents.length === 0 && !input.codeCells?.length)
|
|
3
4
|
return undefined;
|
|
@@ -9,7 +10,7 @@ export function retainedSessionResourcesSnapshot(input) {
|
|
|
9
10
|
...(input.codeCells?.length
|
|
10
11
|
? [
|
|
11
12
|
'Code Exec cells: use direct code_exec_wait with the retained cellId for ongoing work or needed output; cancelling a wait does not terminate the cell.',
|
|
12
|
-
...input.codeCells.map((cell) => `- cellId: ${cell.cellId}; status: ${cell.status}
|
|
13
|
+
...input.codeCells.map((cell) => `- cellId: ${cell.cellId}; status: ${cell.status}`)
|
|
13
14
|
]
|
|
14
15
|
: []),
|
|
15
16
|
processes.length === 0
|
|
@@ -25,26 +26,15 @@ export function retainedSessionResourcesSnapshot(input) {
|
|
|
25
26
|
? ''
|
|
26
27
|
: [
|
|
27
28
|
'Managed processes:',
|
|
28
|
-
...processes.map((process) => `- processId: ${process.processId}; status: ${process.status}; cwd: ${process.cwd}; command: ${process.command}`)
|
|
29
|
+
...processes.map((process) => `- processId: ${process.processId}; status: ${process.status}; cwd: ${truncateModelText(process.cwd, 64, 'resource cwd').content}; command: ${truncateModelText(process.command, 128, 'resource command').content}`)
|
|
29
30
|
].join('\n'),
|
|
30
31
|
subagents.length === 0
|
|
31
32
|
? ''
|
|
32
33
|
: [
|
|
33
34
|
'Subagents:',
|
|
34
|
-
...subagents.map((task) => `- agentId: ${task.agentId}; status: ${task.status}; description: ${task.description}`)
|
|
35
|
+
...subagents.map((task) => `- agentId: ${task.agentId}; status: ${task.status}; description: ${truncateModelText(task.description, 96, 'resource description').content}`)
|
|
35
36
|
].join('\n')
|
|
36
37
|
]
|
|
37
38
|
.filter(Boolean)
|
|
38
39
|
.join('\n');
|
|
39
40
|
}
|
|
40
|
-
export function retainedSessionResourcesUpdate(snapshot, previousSnapshot, previousMayContainResources = false) {
|
|
41
|
-
if (!previousMayContainResources && snapshot === previousSnapshot)
|
|
42
|
-
return undefined;
|
|
43
|
-
if (!snapshot)
|
|
44
|
-
return previousSnapshot || previousMayContainResources
|
|
45
|
-
? 'The previously provided retained Session resources no longer apply. No retained processes, code cells or subagents remain. Historical resource IDs are not live handles; do not poll or wait on them. Use already recorded results or saved artifacts to recover completed work before starting anything again.'
|
|
46
|
-
: undefined;
|
|
47
|
-
return previousSnapshot || previousMayContainResources
|
|
48
|
-
? `This replaces all previously provided retained Session resources.\n\n${snapshot}`
|
|
49
|
-
: snapshot;
|
|
50
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ModelMessage } from '../../model/contracts.js';
|
|
2
|
-
import type { CompactHistoryMessage } from '../../session/jsonl-store.js';
|
|
2
|
+
import type { CompactHistoryMessage, CompactUserInput } from '../../session/jsonl-store.js';
|
|
3
3
|
export declare const DEFAULT_COMPACTION_FOCUS = "Create a durable continuation state for the next model invocation.";
|
|
4
4
|
export declare function buildNativeCompactionMessages(messages: readonly ModelMessage[], input?: {
|
|
5
5
|
focus?: string;
|
|
@@ -11,6 +11,7 @@ export declare function buildCompactionInput(messages: readonly ModelMessage[],
|
|
|
11
11
|
maxTokens: number;
|
|
12
12
|
}): string;
|
|
13
13
|
export declare function retainCompactionUserMessages(messages: readonly string[], maxTokens: number): string[];
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function retainCompactionUserMessages(messages: readonly CompactUserInput[], maxTokens: number): CompactUserInput[];
|
|
15
|
+
export declare function buildCompactedHistory(summary: string, userMessages: readonly (string | CompactUserInput)[], maxUserTokens: number, currentUserMessage?: string | CompactUserInput): CompactHistoryMessage[];
|
|
15
16
|
export declare function compactedHistoryMessages(history: readonly CompactHistoryMessage[]): ModelMessage[];
|
|
16
17
|
export declare function continuationMessage(summary: string): string;
|
|
@@ -16,15 +16,17 @@ export function buildNativeCompactionMessages(messages, input = {}) {
|
|
|
16
16
|
}),
|
|
17
17
|
{
|
|
18
18
|
role: 'user',
|
|
19
|
+
contextKind: 'compact_control',
|
|
19
20
|
content: focus ? `Compaction focus: ${focus}\n\n${contract}` : contract
|
|
20
21
|
}
|
|
21
22
|
];
|
|
22
23
|
}
|
|
23
24
|
export function buildCompactionMessages(evidence, highDensityCompaction = false) {
|
|
24
25
|
return [
|
|
25
|
-
{ role: 'user', content: evidence },
|
|
26
|
+
{ role: 'user', content: evidence, contextKind: 'contextual' },
|
|
26
27
|
{
|
|
27
28
|
role: 'user',
|
|
29
|
+
contextKind: 'compact_control',
|
|
28
30
|
content: highDensityCompaction ? highDensityCompactionPrompt() : compactionPrompt()
|
|
29
31
|
}
|
|
30
32
|
];
|
|
@@ -37,7 +39,7 @@ export function buildCompactionInput(messages, input) {
|
|
|
37
39
|
!(message.role === 'provider' && message.provider === 'ANTHROPIC_MESSAGES'))
|
|
38
40
|
.map((message, index) => ({
|
|
39
41
|
index,
|
|
40
|
-
user: message.role === 'user',
|
|
42
|
+
user: message.role === 'user' && message.contextKind === undefined,
|
|
41
43
|
text: renderMessage(message)
|
|
42
44
|
}));
|
|
43
45
|
const header = `Compaction focus: ${focus}\n\nConversation evidence (chronological):\n`;
|
|
@@ -104,15 +106,26 @@ export function retainCompactionUserMessages(messages, maxTokens) {
|
|
|
104
106
|
const selected = [];
|
|
105
107
|
for (let index = messages.length - 1; index >= 0 && remaining > 0; index--) {
|
|
106
108
|
const message = messages[index];
|
|
107
|
-
const
|
|
109
|
+
const content = typeof message === 'string' ? message : message.content;
|
|
110
|
+
const tokens = countModelTokens(content) + 8;
|
|
108
111
|
if (tokens <= remaining) {
|
|
109
112
|
selected.push(message);
|
|
110
113
|
remaining -= tokens;
|
|
111
114
|
}
|
|
112
115
|
else {
|
|
113
|
-
const truncated = truncateModelText(
|
|
116
|
+
const truncated = truncateModelText(content, Math.max(0, remaining - 8), 'user input');
|
|
114
117
|
if (truncated.content)
|
|
115
|
-
selected.push(
|
|
118
|
+
selected.push(typeof message === 'string'
|
|
119
|
+
? truncated.content
|
|
120
|
+
: {
|
|
121
|
+
...message,
|
|
122
|
+
content: truncated.content,
|
|
123
|
+
metadata: {
|
|
124
|
+
source: message.metadata?.source ?? 'user',
|
|
125
|
+
...message.metadata,
|
|
126
|
+
truncated: true
|
|
127
|
+
}
|
|
128
|
+
});
|
|
116
129
|
break;
|
|
117
130
|
}
|
|
118
131
|
}
|
|
@@ -120,19 +133,36 @@ export function retainCompactionUserMessages(messages, maxTokens) {
|
|
|
120
133
|
}
|
|
121
134
|
export function buildCompactedHistory(summary, userMessages, maxUserTokens, currentUserMessage) {
|
|
122
135
|
return [
|
|
123
|
-
...retainCompactionUserMessages(userMessages, maxUserTokens).map(
|
|
124
|
-
role: 'user',
|
|
125
|
-
content,
|
|
126
|
-
kind: 'user_input'
|
|
127
|
-
})),
|
|
136
|
+
...retainCompactionUserMessages(userMessages.map(asCompactInput), maxUserTokens).map(asHistoryInput),
|
|
128
137
|
{ role: 'user', content: continuationMessage(summary), kind: 'summary' },
|
|
129
138
|
...(currentUserMessage === undefined
|
|
130
139
|
? []
|
|
131
|
-
: [
|
|
140
|
+
: [asHistoryInput(asCompactInput(currentUserMessage))])
|
|
132
141
|
];
|
|
133
142
|
}
|
|
143
|
+
function asCompactInput(input) {
|
|
144
|
+
return typeof input === 'string'
|
|
145
|
+
? { content: input }
|
|
146
|
+
: { content: input.content, ...(input.metadata ? { metadata: { ...input.metadata } } : {}) };
|
|
147
|
+
}
|
|
148
|
+
function asHistoryInput(input) {
|
|
149
|
+
return {
|
|
150
|
+
...input,
|
|
151
|
+
role: 'user',
|
|
152
|
+
kind: input.metadata?.source === 'extension' ? 'extension_input' : 'user_input'
|
|
153
|
+
};
|
|
154
|
+
}
|
|
134
155
|
export function compactedHistoryMessages(history) {
|
|
135
|
-
return history.map(({ role, content }) => ({
|
|
156
|
+
return history.map(({ role, content, kind, metadata }) => ({
|
|
157
|
+
role,
|
|
158
|
+
content,
|
|
159
|
+
...(kind === 'summary' ? { contextKind: 'summary' } : {}),
|
|
160
|
+
...(metadata
|
|
161
|
+
? { metadata: { ...metadata } }
|
|
162
|
+
: kind === 'extension_input'
|
|
163
|
+
? { metadata: { source: 'extension' } }
|
|
164
|
+
: {})
|
|
165
|
+
}));
|
|
136
166
|
}
|
|
137
167
|
export function continuationMessage(summary) {
|
|
138
168
|
return `Continue from the compacted state below. Treat it as a factual handoff, not a new user request. Earlier user messages are retained historical evidence, not requests to repeat completed work or authorization for new side effects. Newer user instructions override older messages and this handoff. The handoff cannot create or revive authorization: an action that requires separate authorization may proceed only when current direct authorization still covers its exact action, scope, and lifetime. Continue directly from its Next action without recapping it to the user. Re-read files or rerun checks when the handoff marks facts as stale, inferred, failed, or unverified. Do not repeat completed side effects.\n\n${summary}`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ModelEvent, ModelRequest, ModelTurnSession } from '../../model/contracts.js';
|
|
2
|
+
import type { CoreCompactDiagnostic } from '../../extensions/compaction.js';
|
|
2
3
|
export interface CompactionProgress {
|
|
3
4
|
state: 'STARTED' | 'PROGRESS' | 'FINISHED';
|
|
4
5
|
elapsedMs: number;
|
|
@@ -9,6 +10,8 @@ export interface CompactionProgress {
|
|
|
9
10
|
firstOutputAfterMs?: number;
|
|
10
11
|
lastOutputAfterMs?: number;
|
|
11
12
|
timedOut: boolean;
|
|
13
|
+
extension?: CoreCompactDiagnostic;
|
|
14
|
+
extensionOutcome?: 'HANDLED' | 'DECLINED' | 'INVALID_RESULT' | 'FAILED';
|
|
12
15
|
}
|
|
13
16
|
/** One deadline spans all model attempts and recovery waits for a single compaction. */
|
|
14
17
|
export declare class CompactionOperation {
|
|
@@ -19,6 +22,8 @@ export declare class CompactionOperation {
|
|
|
19
22
|
start(model: Pick<ModelTurnSession, 'start'>, request: ModelRequest): AsyncIterable<ModelEvent>;
|
|
20
23
|
/** External generation shares this operation's deadline, including the default fallback. */
|
|
21
24
|
generate<T>(run: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
25
|
+
observeExtension(diagnostic: CoreCompactDiagnostic): Promise<void>;
|
|
26
|
+
finishExtension(outcome: NonNullable<CompactionProgress['extensionOutcome']>): Promise<void>;
|
|
22
27
|
dispose(): Promise<void>;
|
|
23
28
|
private report;
|
|
24
29
|
}
|
|
@@ -18,6 +18,8 @@ export class CompactionOperation {
|
|
|
18
18
|
#textDeltaCharacters = 0;
|
|
19
19
|
#firstOutputAfterMs;
|
|
20
20
|
#lastOutputAfterMs;
|
|
21
|
+
#extension;
|
|
22
|
+
#extensionOutcome;
|
|
21
23
|
constructor(parent, record) {
|
|
22
24
|
this.record = record;
|
|
23
25
|
this.signal = AbortSignal.any([parent, this.#controller.signal]);
|
|
@@ -81,6 +83,20 @@ export class CompactionOperation {
|
|
|
81
83
|
this.signal.removeEventListener('abort', abort);
|
|
82
84
|
}
|
|
83
85
|
}
|
|
86
|
+
async observeExtension(diagnostic) {
|
|
87
|
+
if (this.#closed || this.signal.aborted)
|
|
88
|
+
return;
|
|
89
|
+
if (diagnostic.modelRequests < (this.#extension?.modelRequests ?? 0))
|
|
90
|
+
return;
|
|
91
|
+
this.#extension = { ...diagnostic };
|
|
92
|
+
await this.report('PROGRESS');
|
|
93
|
+
}
|
|
94
|
+
async finishExtension(outcome) {
|
|
95
|
+
if (this.#closed)
|
|
96
|
+
return;
|
|
97
|
+
this.#extensionOutcome = outcome;
|
|
98
|
+
await this.report('PROGRESS');
|
|
99
|
+
}
|
|
84
100
|
async dispose() {
|
|
85
101
|
if (this.#closed)
|
|
86
102
|
return;
|
|
@@ -93,8 +109,12 @@ export class CompactionOperation {
|
|
|
93
109
|
const progress = {
|
|
94
110
|
state,
|
|
95
111
|
elapsedMs: Math.max(0, Date.now() - this.#startedAt),
|
|
96
|
-
inputTokensEstimate: this.#
|
|
97
|
-
|
|
112
|
+
inputTokensEstimate: this.#modelRequests
|
|
113
|
+
? this.#inputTokensEstimate
|
|
114
|
+
: (this.#extension?.inputTokensEstimateAfter ?? this.#inputTokensEstimate),
|
|
115
|
+
modelRequests: this.#modelRequests + (this.#extension?.modelRequests ?? 0),
|
|
116
|
+
...(this.#extension ? { extension: { ...this.#extension } } : {}),
|
|
117
|
+
...(this.#extensionOutcome ? { extensionOutcome: this.#extensionOutcome } : {}),
|
|
98
118
|
eventCounts: { ...this.#eventCounts },
|
|
99
119
|
textDeltaCharacters: this.#textDeltaCharacters,
|
|
100
120
|
...(this.#firstOutputAfterMs === undefined
|
|
@@ -4,7 +4,7 @@ import { observationModelText } from '../../tools/code-mode/observations.js';
|
|
|
4
4
|
export function estimatedRequestTokens(system, messages, tools, images) {
|
|
5
5
|
const transientImageCount = messages.reduce((total, message) => total + (message.images?.length ?? 0), images?.length ?? 0);
|
|
6
6
|
const textMessages = messages.map((message) => {
|
|
7
|
-
const { images: _images, observations: _observations, observationId: _observationId, ...textMessage } = message;
|
|
7
|
+
const { images: _images, observations: _observations, observationId: _observationId, metadata: _metadata, contextKind: _contextKind, ...textMessage } = message;
|
|
8
8
|
const content = message.role === 'tool' ? observationModelText(message) : undefined;
|
|
9
9
|
if (content !== undefined)
|
|
10
10
|
textMessage.content = content;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ModelMessage } from '../../model/contracts.js';
|
|
2
|
-
import { type JsonlSessionStore } from '../../session/jsonl-store.js';
|
|
2
|
+
import { type CompactUserInput, type JsonlSessionStore } from '../../session/jsonl-store.js';
|
|
3
3
|
import type { EnvironmentContextState } from './contracts.js';
|
|
4
4
|
import { type ContextProjection } from './projection.js';
|
|
5
5
|
export declare function restoreMessages(store: JsonlSessionStore, sessionId: string, records: readonly import('../../session/jsonl-store.js').SessionRecord[], options: {
|
|
@@ -13,7 +13,7 @@ export declare function restoreMessages(store: JsonlSessionStore, sessionId: str
|
|
|
13
13
|
result?: unknown;
|
|
14
14
|
}>;
|
|
15
15
|
environmentState: EnvironmentContextState | undefined;
|
|
16
|
-
userMessages:
|
|
16
|
+
userMessages: CompactUserInput[];
|
|
17
17
|
messages: ModelMessage[];
|
|
18
18
|
ignoredImages: number;
|
|
19
19
|
currentTurnUserIndex: number | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MarAgentError } from '../../error.js';
|
|
2
|
-
import { compactSummaryFromCheckpoint, replacementHistoryFromCheckpoint } from '../../session/jsonl-store.js';
|
|
2
|
+
import { compactSummaryFromCheckpoint, replacementHistoryFromCheckpoint, inputMetadataFromRecord } from '../../session/jsonl-store.js';
|
|
3
3
|
import { compactedHistoryMessages, continuationMessage } from '../compaction/history.js';
|
|
4
4
|
import { environmentContextUpdate, restoreEnvironmentState } from './environment.js';
|
|
5
5
|
import { buildContextProjection, restoreModelContext } from './projection.js';
|
|
@@ -26,7 +26,9 @@ export async function restoreMessages(store, sessionId, records, options) {
|
|
|
26
26
|
const compactSummary = compactSummaryFromCheckpoint(record);
|
|
27
27
|
if ((record.type === 'turn.user' || record.type === 'extension.input') &&
|
|
28
28
|
typeof payload.prompt === 'string') {
|
|
29
|
-
|
|
29
|
+
const content = typeof payload.modelText === 'string' ? payload.modelText : payload.prompt;
|
|
30
|
+
const metadata = inputMetadataFromRecord(record);
|
|
31
|
+
userMessages.push({ content, metadata });
|
|
30
32
|
let images = [];
|
|
31
33
|
if (options.includeImages)
|
|
32
34
|
try {
|
|
@@ -47,20 +49,21 @@ export async function restoreMessages(store, sessionId, records, options) {
|
|
|
47
49
|
currentTurnUserIndex = messages.length;
|
|
48
50
|
messages.push({
|
|
49
51
|
role: 'user',
|
|
50
|
-
content
|
|
52
|
+
content,
|
|
53
|
+
metadata,
|
|
51
54
|
...(images.length ? { images } : {})
|
|
52
55
|
});
|
|
53
56
|
const context = payload.modelContext ?? payload.context;
|
|
54
57
|
if (payload.contextRecords !== true && Array.isArray(context))
|
|
55
58
|
for (const text of context)
|
|
56
59
|
if (typeof text === 'string')
|
|
57
|
-
messages.push({ role: 'user', content: text });
|
|
60
|
+
messages.push({ role: 'user', content: text, contextKind: 'contextual' });
|
|
58
61
|
}
|
|
59
62
|
else if (record.type === 'input.context') {
|
|
60
63
|
if (Array.isArray(payload.modelContext))
|
|
61
64
|
for (const text of payload.modelContext)
|
|
62
65
|
if (typeof text === 'string')
|
|
63
|
-
messages.push({ role: 'user', content: text });
|
|
66
|
+
messages.push({ role: 'user', content: text, contextKind: 'contextual' });
|
|
64
67
|
}
|
|
65
68
|
else if ((record.type === 'message.completed' || record.type === 'plan.completed') &&
|
|
66
69
|
typeof payload.text === 'string')
|
|
@@ -72,11 +75,24 @@ export async function restoreMessages(store, sessionId, records, options) {
|
|
|
72
75
|
const history = replacementHistoryFromCheckpoint(record);
|
|
73
76
|
userMessages =
|
|
74
77
|
history
|
|
75
|
-
?.filter((message) => message.kind
|
|
76
|
-
.map((message) =>
|
|
78
|
+
?.filter((message) => message.kind !== 'summary')
|
|
79
|
+
.map((message) => ({
|
|
80
|
+
content: message.content,
|
|
81
|
+
...(message.metadata
|
|
82
|
+
? { metadata: { ...message.metadata } }
|
|
83
|
+
: message.kind === 'extension_input'
|
|
84
|
+
? { metadata: { source: 'extension' } }
|
|
85
|
+
: {})
|
|
86
|
+
})) ?? [];
|
|
77
87
|
messages.push(...(history
|
|
78
88
|
? compactedHistoryMessages(history)
|
|
79
|
-
: [
|
|
89
|
+
: [
|
|
90
|
+
{
|
|
91
|
+
role: 'user',
|
|
92
|
+
content: continuationMessage(compactSummary),
|
|
93
|
+
contextKind: 'summary'
|
|
94
|
+
}
|
|
95
|
+
]));
|
|
80
96
|
if (Array.isArray(payload.retainedModelContext))
|
|
81
97
|
for (const item of payload.retainedModelContext) {
|
|
82
98
|
if (!item || typeof item !== 'object' || item.role !== 'user')
|
|
@@ -19,17 +19,14 @@ export declare function insertContextualWorldState(messages: readonly ModelMessa
|
|
|
19
19
|
export declare function latestContextualWorldStateGeneration(records: readonly import('../../session/jsonl-store.js').SessionRecord[]): string | undefined;
|
|
20
20
|
export declare function parsePersistedContextualWorldState(payload: unknown): ContextualWorldState | undefined;
|
|
21
21
|
export declare function persistContextualWorldState(store: import('../../session/jsonl-store.js').JsonlSessionStore, sessionId: string, state: ContextualWorldState): Promise<void>;
|
|
22
|
-
export declare function compactHistoryMayContainRetainedResources(records: readonly import('../../session/jsonl-store.js').SessionRecord[]): boolean;
|
|
23
22
|
export declare function updateContextualWorldState(state: ContextualWorldState, index: number, current: {
|
|
24
23
|
instructionSnapshot: string | undefined;
|
|
25
24
|
instructionContent: string | undefined;
|
|
26
|
-
resourceSnapshot: string | undefined;
|
|
27
|
-
previousMayContainResources?: boolean;
|
|
28
25
|
}): ContextualWorldState;
|
|
29
26
|
export declare function resetContextualWorldState(generation: string | undefined, index: number, current: {
|
|
30
27
|
instructionSnapshot: string | undefined;
|
|
31
28
|
instructionContent: string | undefined;
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
/** Only a committed Compact may supply a new dynamic-resource snapshot. */
|
|
30
|
+
resourceSnapshot?: string | undefined;
|
|
34
31
|
}): ContextualWorldState;
|
|
35
32
|
export declare function contextualInstructionInsertIndex(messages: readonly ModelMessage[]): number;
|