@gajae-code/agent-core 0.1.1

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +482 -0
  2. package/README.md +473 -0
  3. package/dist/types/agent-loop.d.ts +55 -0
  4. package/dist/types/agent.d.ts +334 -0
  5. package/dist/types/append-only-context.d.ts +113 -0
  6. package/dist/types/compaction/branch-summarization.d.ts +94 -0
  7. package/dist/types/compaction/compaction.d.ts +166 -0
  8. package/dist/types/compaction/entries.d.ts +103 -0
  9. package/dist/types/compaction/errors.d.ts +26 -0
  10. package/dist/types/compaction/index.d.ts +11 -0
  11. package/dist/types/compaction/messages.d.ts +61 -0
  12. package/dist/types/compaction/openai.d.ts +58 -0
  13. package/dist/types/compaction/pruning.d.ts +18 -0
  14. package/dist/types/compaction/utils.d.ts +32 -0
  15. package/dist/types/compaction.d.ts +1 -0
  16. package/dist/types/harmony-leak.d.ts +99 -0
  17. package/dist/types/index.d.ts +10 -0
  18. package/dist/types/proxy.d.ts +84 -0
  19. package/dist/types/run-collector.d.ts +196 -0
  20. package/dist/types/telemetry.d.ts +588 -0
  21. package/dist/types/thinking.d.ts +17 -0
  22. package/dist/types/types.d.ts +407 -0
  23. package/package.json +75 -0
  24. package/src/agent-loop.ts +1279 -0
  25. package/src/agent.ts +1399 -0
  26. package/src/append-only-context.ts +297 -0
  27. package/src/compaction/branch-summarization.ts +339 -0
  28. package/src/compaction/compaction.ts +1065 -0
  29. package/src/compaction/entries.ts +133 -0
  30. package/src/compaction/errors.ts +31 -0
  31. package/src/compaction/index.ts +12 -0
  32. package/src/compaction/messages.ts +212 -0
  33. package/src/compaction/openai.ts +552 -0
  34. package/src/compaction/prompts/auto-handoff-threshold-focus.md +1 -0
  35. package/src/compaction/prompts/branch-summary-context.md +5 -0
  36. package/src/compaction/prompts/branch-summary-preamble.md +2 -0
  37. package/src/compaction/prompts/branch-summary.md +30 -0
  38. package/src/compaction/prompts/compaction-short-summary.md +9 -0
  39. package/src/compaction/prompts/compaction-summary-context.md +5 -0
  40. package/src/compaction/prompts/compaction-summary.md +38 -0
  41. package/src/compaction/prompts/compaction-turn-prefix.md +17 -0
  42. package/src/compaction/prompts/compaction-update-summary.md +45 -0
  43. package/src/compaction/prompts/file-operations.md +10 -0
  44. package/src/compaction/prompts/handoff-document.md +49 -0
  45. package/src/compaction/prompts/summarization-system.md +3 -0
  46. package/src/compaction/pruning.ts +92 -0
  47. package/src/compaction/utils.ts +185 -0
  48. package/src/compaction.ts +1 -0
  49. package/src/harmony-leak.ts +427 -0
  50. package/src/index.ts +19 -0
  51. package/src/proxy.ts +326 -0
  52. package/src/run-collector.ts +631 -0
  53. package/src/telemetry.ts +2018 -0
  54. package/src/thinking.ts +19 -0
  55. package/src/types.ts +467 -0
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Context compaction for long sessions.
3
+ *
4
+ * Pure functions for compaction logic. The session manager handles I/O,
5
+ * and after compaction the session is reloaded.
6
+ */
7
+ import { type MessageAttribution, type Model, type Usage } from "@gajae-code/ai";
8
+ import { type AgentTelemetry } from "../telemetry";
9
+ import type { AgentMessage, AgentTool } from "../types";
10
+ import type { SessionEntry } from "./entries";
11
+ import { type ConvertToLlm } from "./messages";
12
+ import { type FileOperations } from "./utils";
13
+ /** Details stored in CompactionEntry.details for file tracking */
14
+ export interface CompactionDetails {
15
+ readFiles: string[];
16
+ modifiedFiles: string[];
17
+ }
18
+ /** Result from compact() - SessionManager adds uuid/parentUuid when saving */
19
+ export interface CompactionResult<T = unknown> {
20
+ summary: string;
21
+ /** Short PR-style summary for display purposes. */
22
+ shortSummary?: string;
23
+ firstKeptEntryId: string;
24
+ tokensBefore: number;
25
+ /** Hook-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
26
+ details?: T;
27
+ /** Hook-provided data to persist alongside compaction entry. */
28
+ preserveData?: Record<string, unknown>;
29
+ }
30
+ export interface CompactionSettings {
31
+ enabled: boolean;
32
+ strategy?: "context-full" | "handoff" | "off";
33
+ thresholdPercent?: number;
34
+ thresholdTokens?: number;
35
+ reserveTokens: number;
36
+ keepRecentTokens: number;
37
+ autoContinue?: boolean;
38
+ remoteEnabled?: boolean;
39
+ remoteEndpoint?: string;
40
+ }
41
+ export declare const DEFAULT_COMPACTION_SETTINGS: CompactionSettings;
42
+ /**
43
+ * Calculate total context tokens from usage.
44
+ * Uses the native totalTokens field when available, falls back to computing from components.
45
+ */
46
+ export declare function calculateContextTokens(usage: Usage): number;
47
+ export declare function calculatePromptTokens(usage: Usage): number;
48
+ /**
49
+ * Find the last non-aborted assistant message usage from session entries.
50
+ */
51
+ export declare function getLastAssistantUsage(entries: SessionEntry[]): Usage | undefined;
52
+ /**
53
+ * Effective reserve: at least 15% of context window or the configured floor, whichever is larger.
54
+ */
55
+ export declare function effectiveReserveTokens(contextWindow: number, settings: CompactionSettings): number;
56
+ /**
57
+ * Check if compaction should trigger based on context usage.
58
+ */
59
+ export declare function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean;
60
+ export declare function resolveThresholdTokens(contextWindow: number, settings: CompactionSettings): number;
61
+ /**
62
+ * Estimate token count for a message using cl100k_base via the native
63
+ * tokenizer. This is not Anthropic's first-party tokenizer (Anthropic doesn't
64
+ * publish one) but is within ~5–10% across English/code text.
65
+ */
66
+ export declare function estimateTokens(message: AgentMessage): number;
67
+ /**
68
+ * Find the user message (or bashExecution) that starts the turn containing the given entry index.
69
+ * Returns -1 if no turn start found before the index.
70
+ * BashExecutionMessage is treated like a user message for turn boundaries.
71
+ */
72
+ export declare function findTurnStartIndex(entries: SessionEntry[], entryIndex: number, startIndex: number): number;
73
+ export interface CutPointResult {
74
+ /** Index of first entry to keep */
75
+ firstKeptEntryIndex: number;
76
+ /** Index of user message that starts the turn being split, or -1 if not splitting */
77
+ turnStartIndex: number;
78
+ /** Whether this cut splits a turn (cut point is not a user message) */
79
+ isSplitTurn: boolean;
80
+ }
81
+ /**
82
+ * Find the cut point in session entries that keeps approximately `keepRecentTokens`.
83
+ *
84
+ * Algorithm: Walk backwards from newest, accumulating estimated message sizes.
85
+ * Stop when we've accumulated >= keepRecentTokens. Cut at that point.
86
+ *
87
+ * Can cut at user OR assistant messages (never tool results). When cutting at an
88
+ * assistant message with tool calls, its tool results come after and will be kept.
89
+ *
90
+ * Returns CutPointResult with:
91
+ * - firstKeptEntryIndex: the entry index to start keeping from
92
+ * - turnStartIndex: if cutting mid-turn, the user message that started that turn
93
+ * - isSplitTurn: whether we're cutting in the middle of a turn
94
+ *
95
+ * Only considers entries between `startIndex` and `endIndex` (exclusive).
96
+ */
97
+ export declare function findCutPoint(entries: SessionEntry[], startIndex: number, endIndex: number, keepRecentTokens: number): CutPointResult;
98
+ export declare const AUTO_HANDOFF_THRESHOLD_FOCUS: string;
99
+ /**
100
+ * Generate a summary of the conversation using the LLM.
101
+ * If previousSummary is provided, uses the update prompt to merge.
102
+ */
103
+ export interface SummaryOptions {
104
+ promptOverride?: string;
105
+ extraContext?: string[];
106
+ remoteEndpoint?: string;
107
+ remoteInstructions?: string;
108
+ initiatorOverride?: MessageAttribution;
109
+ metadata?: Record<string, unknown>;
110
+ convertToLlm?: ConvertToLlm;
111
+ /**
112
+ * Optional telemetry handle. When provided, every LLM call emitted during
113
+ * compaction is wrapped in an OTEL chat span tagged with
114
+ * `pi.gen_ai.oneshot.kind` (`compaction_summary`, `compaction_short_summary`,
115
+ * or `compaction_turn_prefix`). `undefined` keeps the call paths zero-cost.
116
+ */
117
+ telemetry?: AgentTelemetry;
118
+ }
119
+ export declare function generateSummary(currentMessages: AgentMessage[], model: Model, reserveTokens: number, apiKey: string, signal?: AbortSignal, customInstructions?: string, previousSummary?: string, options?: SummaryOptions): Promise<string>;
120
+ export interface HandoffOptions {
121
+ /** Live agent system prompt — passed verbatim so providers hit the cached prefix. */
122
+ systemPrompt: string[];
123
+ /** Live agent tool list — same purpose. Forced to `toolChoice: "none"`. */
124
+ tools?: AgentTool<any>[];
125
+ customInstructions?: string;
126
+ convertToLlm?: ConvertToLlm;
127
+ initiatorOverride?: MessageAttribution;
128
+ metadata?: Record<string, unknown>;
129
+ /**
130
+ * Optional telemetry handle. When provided, the handoff LLM call is
131
+ * wrapped in an OTEL chat span tagged with `pi.gen_ai.oneshot.kind = "handoff"`.
132
+ */
133
+ telemetry?: AgentTelemetry;
134
+ }
135
+ export declare function renderHandoffPrompt(customInstructions?: string): string;
136
+ export declare function generateHandoff(messages: AgentMessage[], model: Model, apiKey: string, options: HandoffOptions, signal?: AbortSignal): Promise<string>;
137
+ export interface CompactionPreparation {
138
+ /** UUID of first entry to keep */
139
+ firstKeptEntryId: string;
140
+ /** Messages that will be summarized and discarded */
141
+ messagesToSummarize: AgentMessage[];
142
+ /** Messages that will be turned into turn prefix summary (if splitting) */
143
+ turnPrefixMessages: AgentMessage[];
144
+ /** Messages kept in full after compaction (recent history) */
145
+ recentMessages: AgentMessage[];
146
+ /** Whether this is a split turn (cut point in middle of turn) */
147
+ isSplitTurn: boolean;
148
+ tokensBefore: number;
149
+ /** Summary from previous compaction, for iterative update */
150
+ previousSummary?: string;
151
+ /** Preserved opaque compaction payload from the previous compaction, if any. */
152
+ previousPreserveData?: Record<string, unknown>;
153
+ /** File operations extracted from messagesToSummarize */
154
+ fileOps: FileOperations;
155
+ /** Compaction settions from settings.jsonl */
156
+ settings: CompactionSettings;
157
+ }
158
+ export declare function prepareCompaction(pathEntries: SessionEntry[], settings: CompactionSettings): CompactionPreparation | undefined;
159
+ /**
160
+ * Generate summaries for compaction using prepared data.
161
+ * Returns CompactionResult - SessionManager adds id/parentId when saving.
162
+ *
163
+ * @param preparation - Pre-calculated preparation from prepareCompaction()
164
+ * @param customInstructions - Optional custom focus for the summary
165
+ */
166
+ export declare function compact(preparation: CompactionPreparation, model: Model, apiKey: string, customInstructions?: string, signal?: AbortSignal, options?: SummaryOptions): Promise<CompactionResult>;
@@ -0,0 +1,103 @@
1
+ import type { ImageContent, MessageAttribution, ServiceTier, TextContent } from "@gajae-code/ai";
2
+ import type { AgentMessage } from "../types";
3
+ export interface SessionEntryBase {
4
+ type: string;
5
+ id: string;
6
+ parentId: string | null;
7
+ timestamp: string;
8
+ }
9
+ export interface SessionMessageEntry extends SessionEntryBase {
10
+ type: "message";
11
+ message: AgentMessage;
12
+ }
13
+ export interface ThinkingLevelChangeEntry extends SessionEntryBase {
14
+ type: "thinking_level_change";
15
+ thinkingLevel?: string | null;
16
+ }
17
+ export interface ModelChangeEntry extends SessionEntryBase {
18
+ type: "model_change";
19
+ /** Model in "provider/modelId" format */
20
+ model: string;
21
+ /** Role: "default", "smol", "slow", etc. Undefined treated as "default" */
22
+ role?: string;
23
+ }
24
+ export interface ServiceTierChangeEntry extends SessionEntryBase {
25
+ type: "service_tier_change";
26
+ serviceTier: ServiceTier | null;
27
+ }
28
+ export interface CompactionEntry<T = unknown> extends SessionEntryBase {
29
+ type: "compaction";
30
+ summary: string;
31
+ shortSummary?: string;
32
+ firstKeptEntryId: string;
33
+ tokensBefore: number;
34
+ /** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
35
+ details?: T;
36
+ /** Hook-provided data to persist across compaction */
37
+ preserveData?: Record<string, unknown>;
38
+ /** True if generated by an extension, undefined/false if pi-generated (backward compatible) */
39
+ fromExtension?: boolean;
40
+ }
41
+ export interface BranchSummaryEntry<T = unknown> extends SessionEntryBase {
42
+ type: "branch_summary";
43
+ fromId: string;
44
+ summary: string;
45
+ /** Extension-specific data (not sent to LLM) */
46
+ details?: T;
47
+ /** True if generated by an extension, false if pi-generated */
48
+ fromExtension?: boolean;
49
+ }
50
+ export interface CustomMessageEntry<T = unknown> extends SessionEntryBase {
51
+ type: "custom_message";
52
+ customType: string;
53
+ content: string | (TextContent | ImageContent)[];
54
+ details?: T;
55
+ display: boolean;
56
+ /** Who initiated this message for billing/attribution semantics. */
57
+ attribution?: MessageAttribution;
58
+ }
59
+ export interface CustomEntry<T = unknown> extends SessionEntryBase {
60
+ type: "custom";
61
+ customType: string;
62
+ data?: T;
63
+ }
64
+ export interface LabelEntry extends SessionEntryBase {
65
+ type: "label";
66
+ targetId: string;
67
+ label: string | undefined;
68
+ }
69
+ export interface TtsrInjectionEntry extends SessionEntryBase {
70
+ type: "ttsr_injection";
71
+ /** Names of rules that were injected */
72
+ injectedRules: string[];
73
+ }
74
+ export interface MCPToolSelectionEntry extends SessionEntryBase {
75
+ type: "mcp_tool_selection";
76
+ /** MCP tool names selected for visibility in discovery mode. */
77
+ selectedToolNames: string[];
78
+ }
79
+ export interface SessionInitEntry extends SessionEntryBase {
80
+ type: "session_init";
81
+ /** Full system prompt sent to the model */
82
+ systemPrompt: string;
83
+ /** Initial task/user message */
84
+ task: string;
85
+ /** Tools available to the agent */
86
+ tools: string[];
87
+ /** Output schema if structured output was requested */
88
+ outputSchema?: unknown;
89
+ }
90
+ export interface ModeChangeEntry extends SessionEntryBase {
91
+ type: "mode_change";
92
+ /** Current mode name, or "none" when exiting a mode */
93
+ mode: string;
94
+ /** Optional mode-specific data (e.g. plan file path) */
95
+ data?: Record<string, unknown>;
96
+ }
97
+ export interface CustomCompactionSessionEntries {
98
+ }
99
+ export type SessionEntry = SessionMessageEntry | ThinkingLevelChangeEntry | ModelChangeEntry | ServiceTierChangeEntry | CompactionEntry | BranchSummaryEntry | CustomEntry | CustomMessageEntry | LabelEntry | TtsrInjectionEntry | MCPToolSelectionEntry | SessionInitEntry | ModeChangeEntry | CustomCompactionSessionEntries[keyof CustomCompactionSessionEntries];
100
+ export interface ReadonlySessionManager {
101
+ getBranch(leafId?: string | null): SessionEntry[];
102
+ getEntry(id: string): SessionEntry | undefined;
103
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Compaction error types.
3
+ *
4
+ * `CompactionCancelledError` is the canonical signal raised when a compaction
5
+ * is explicitly aborted — operator Esc, extension hook returning `cancel`,
6
+ * programmatic `session.abortCompaction()` call, or any other deliberate
7
+ * abort source. Downstream callers (e.g. `executeCompaction`) discriminate
8
+ * cancellation from other failures via `instanceof CompactionCancelledError`
9
+ * rather than introspecting error messages or `name` fields — the typed
10
+ * sentinel makes classification source-agnostic and refactor-stable.
11
+ */
12
+ export declare class CompactionCancelledError extends Error {
13
+ readonly name: "CompactionCancelledError";
14
+ constructor(message?: string);
15
+ }
16
+ /**
17
+ * Outcome of a compaction attempt, surfaced by `CommandController.executeCompaction`
18
+ * so callers (e.g. the plan-mode approval flow) can distinguish a deliberate abort
19
+ * from an unrelated failure.
20
+ *
21
+ * "ok" — compaction completed; transcript was summarized.
22
+ * "cancelled" — `CompactionCancelledError` was raised. Operator Esc, extension
23
+ * hook, programmatic abort — all source-agnostic.
24
+ * "failed" — any other rejection from `session.compact()`.
25
+ */
26
+ export type CompactionOutcome = "ok" | "cancelled" | "failed";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Compaction and summarization utilities.
3
+ */
4
+ export * from "./branch-summarization";
5
+ export * from "./compaction";
6
+ export * from "./entries";
7
+ export * from "./errors";
8
+ export * from "./messages";
9
+ export * from "./openai";
10
+ export * from "./pruning";
11
+ export * from "./utils";
@@ -0,0 +1,61 @@
1
+ import type { ImageContent, Message, MessageAttribution, ProviderPayload, TextContent } from "@gajae-code/ai";
2
+ import type { AgentMessage } from "../types";
3
+ export interface CustomMessage<T = unknown> {
4
+ role: "custom";
5
+ customType: string;
6
+ content: string | (TextContent | ImageContent)[];
7
+ display: boolean;
8
+ details?: T;
9
+ /** Who initiated this message for billing/attribution semantics. */
10
+ attribution?: MessageAttribution;
11
+ timestamp: number;
12
+ }
13
+ /** Legacy hook message type (pre-extensions). Kept for session migration. */
14
+ export interface HookMessage<T = unknown> {
15
+ role: "hookMessage";
16
+ customType: string;
17
+ content: string | (TextContent | ImageContent)[];
18
+ display: boolean;
19
+ details?: T;
20
+ /** Who initiated this message for billing/attribution semantics. */
21
+ attribution?: MessageAttribution;
22
+ timestamp: number;
23
+ }
24
+ export interface BranchSummaryMessage {
25
+ role: "branchSummary";
26
+ summary: string;
27
+ fromId: string;
28
+ timestamp: number;
29
+ }
30
+ export interface CompactionSummaryMessage {
31
+ role: "compactionSummary";
32
+ summary: string;
33
+ shortSummary?: string;
34
+ tokensBefore: number;
35
+ providerPayload?: ProviderPayload;
36
+ timestamp: number;
37
+ }
38
+ export type CoreCompactionMessage = CustomMessage | HookMessage | BranchSummaryMessage | CompactionSummaryMessage;
39
+ declare module "../types" {
40
+ interface CustomAgentMessages {
41
+ custom: CustomMessage;
42
+ hookMessage: HookMessage;
43
+ branchSummary: BranchSummaryMessage;
44
+ compactionSummary: CompactionSummaryMessage;
45
+ }
46
+ }
47
+ export type ConvertToLlm = (messages: AgentMessage[]) => Message[];
48
+ export declare function renderBranchSummaryContext(summary: string): string;
49
+ export declare function renderCompactionSummaryContext(summary: string): string;
50
+ export declare function createBranchSummaryMessage(summary: string, fromId: string, timestamp: string): BranchSummaryMessage;
51
+ export declare function createCompactionSummaryMessage(summary: string, tokensBefore: number, timestamp: string, shortSummary?: string, providerPayload?: ProviderPayload): CompactionSummaryMessage;
52
+ export declare function createCustomMessage(customType: string, content: string | (TextContent | ImageContent)[], display: boolean, details: unknown | undefined, timestamp: string, attribution?: MessageAttribution): CustomMessage;
53
+ /**
54
+ * Default compaction-domain transformer.
55
+ *
56
+ * Embedders with their own app messages should pass a richer transformer through
57
+ * `SummaryOptions.convertToLlm`; this default intentionally preserves only the
58
+ * core LLM roles and the compaction messages owned by this package.
59
+ */
60
+ export declare function defaultConvertToLlm(messages: AgentMessage[]): Message[];
61
+ export declare const convertToLlm: typeof defaultConvertToLlm;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Remote compaction utilities.
3
+ *
4
+ * Provider-side conversation summarization endpoints. Two flavors:
5
+ *
6
+ * - **OpenAI remote compaction** (`/responses/compact`): preserves encrypted
7
+ * reasoning across compactions by submitting the full responses-API native
8
+ * history and storing the returned `compaction` / `compaction_summary`
9
+ * item in `preserveData` so future turns can replay the encrypted state.
10
+ * - **Generic remote compaction**: a thin POST helper for self-hosted
11
+ * summarization endpoints that accept `{ systemPrompt, prompt }` and reply
12
+ * with `{ summary, shortSummary? }`.
13
+ */
14
+ import type { Message, Model } from "@gajae-code/ai/types";
15
+ export declare const OPENAI_REMOTE_COMPACTION_PRESERVE_KEY = "openaiRemoteCompaction";
16
+ export type OpenAiRemoteCompactionItem = {
17
+ type: "compaction" | "compaction_summary";
18
+ encrypted_content?: string;
19
+ summary?: string;
20
+ };
21
+ export interface OpenAiRemoteCompactionPreserveData {
22
+ provider?: string;
23
+ replacementHistory: Array<Record<string, unknown>>;
24
+ compactionItem: OpenAiRemoteCompactionItem;
25
+ }
26
+ export interface OpenAiRemoteCompactionRequest {
27
+ model: string;
28
+ input: Array<Record<string, unknown>>;
29
+ instructions: string;
30
+ }
31
+ export interface OpenAiRemoteCompactionResponse extends OpenAiRemoteCompactionPreserveData {
32
+ }
33
+ export interface RemoteCompactionRequest {
34
+ systemPrompt: string;
35
+ prompt: string;
36
+ }
37
+ export interface RemoteCompactionResponse {
38
+ summary: string;
39
+ shortSummary?: string;
40
+ }
41
+ export declare function shouldUseOpenAiRemoteCompaction(model: Model): boolean;
42
+ export declare function getPreservedOpenAiRemoteCompactionData(preserveData: Record<string, unknown> | undefined): OpenAiRemoteCompactionPreserveData | undefined;
43
+ export declare function withOpenAiRemoteCompactionPreserveData(preserveData: Record<string, unknown> | undefined, remoteCompaction: OpenAiRemoteCompactionPreserveData | undefined): Record<string, unknown> | undefined;
44
+ /**
45
+ * Build the OpenAI Responses-API native history array from LLM messages.
46
+ *
47
+ * Caller is responsible for converting any custom message types to
48
+ * `Message[]` first (e.g. via the agent's `convertToLlm`); this function
49
+ * operates purely on the LLM-domain shape.
50
+ *
51
+ * @param messages - LLM messages to encode.
52
+ * @param model - Target model (used for provider gating + tool-call id rules).
53
+ * @param previousReplacementHistory - History from a prior compaction whose
54
+ * encrypted reasoning we want to preserve.
55
+ */
56
+ export declare function buildOpenAiNativeHistory(messages: Message[], model: Model, previousReplacementHistory?: Array<Record<string, unknown>>): Array<Record<string, unknown>>;
57
+ export declare function requestOpenAiRemoteCompaction(model: Model, apiKey: string, compactInput: Array<Record<string, unknown>>, instructions: string, signal?: AbortSignal): Promise<OpenAiRemoteCompactionResponse>;
58
+ export declare function requestRemoteCompaction(endpoint: string, request: RemoteCompactionRequest, signal?: AbortSignal): Promise<RemoteCompactionResponse>;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Tool output pruning utilities for compaction.
3
+ */
4
+ import type { SessionEntry } from "./entries";
5
+ export interface PruneConfig {
6
+ /** Keep the most recent tool output tokens intact. */
7
+ protectTokens: number;
8
+ /** Only prune if total savings meets this threshold. */
9
+ minimumSavings: number;
10
+ /** Tool names that should never be pruned. */
11
+ protectedTools: string[];
12
+ }
13
+ export declare const DEFAULT_PRUNE_CONFIG: PruneConfig;
14
+ export interface PruneResult {
15
+ prunedCount: number;
16
+ tokensSaved: number;
17
+ }
18
+ export declare function pruneToolOutputs(entries: SessionEntry[], config?: PruneConfig): PruneResult;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Shared utilities for compaction and branch summarization.
3
+ */
4
+ import type { Message } from "@gajae-code/ai";
5
+ import type { AgentMessage } from "../types";
6
+ export interface FileOperations {
7
+ read: Set<string>;
8
+ written: Set<string>;
9
+ edited: Set<string>;
10
+ }
11
+ export declare function createFileOps(): FileOperations;
12
+ /**
13
+ * Extract file operations from tool calls in an assistant message.
14
+ */
15
+ export declare function extractFileOpsFromMessage(message: AgentMessage, fileOps: FileOperations): void;
16
+ /**
17
+ * Compute final file lists from file operations.
18
+ * Returns readFiles (files only read, not modified) and modifiedFiles.
19
+ */
20
+ export declare function computeFileLists(fileOps: FileOperations): {
21
+ readFiles: string[];
22
+ modifiedFiles: string[];
23
+ };
24
+ export declare function formatFileOperations(readFiles: string[], modifiedFiles: string[]): string;
25
+ export declare function upsertFileOperations(summary: string, readFiles: string[], modifiedFiles: string[]): string;
26
+ /**
27
+ * Serialize LLM messages to text for summarization.
28
+ * This prevents the model from treating it as a conversation to continue.
29
+ * Call convertToLlm() first to handle custom message types.
30
+ */
31
+ export declare function serializeConversation(messages: Message[]): string;
32
+ export declare const SUMMARIZATION_SYSTEM_PROMPT: string;
@@ -0,0 +1 @@
1
+ export * from "./compaction/index";
@@ -0,0 +1,99 @@
1
+ /**
2
+ * GPT-5 Harmony-header leakage detection and recovery.
3
+ *
4
+ * Background and policy: see `docs/ERRATA-GPT5-HARMONY.md`. This module
5
+ * implements §3 of that document: detection by signal fusion, plus a
6
+ * truncate-and-resume primitive for the `edit` tool when its input is in
7
+ * hashline DSL form. Other tools and surfaces fall through to
8
+ * abort-and-retry handled by the agent loop.
9
+ */
10
+ import type { AssistantMessage, Model } from "@gajae-code/ai";
11
+ declare const SIGNAL_ORDER: readonly ["M", "C", "G", "S", "B", "R", "T"];
12
+ export type HarmonySignalClass = "H" | (typeof SIGNAL_ORDER)[number];
13
+ export type HarmonySurface = "assistant_text" | "assistant_thinking" | "tool_arg";
14
+ export interface HarmonySignal {
15
+ classes: HarmonySignalClass[];
16
+ start: number;
17
+ end: number;
18
+ text: string;
19
+ }
20
+ export interface HarmonyDetection {
21
+ surface: HarmonySurface;
22
+ contentIndex?: number;
23
+ toolName?: string;
24
+ toolCallId?: string;
25
+ signals: HarmonySignal[];
26
+ }
27
+ export interface HarmonyAuditEvent {
28
+ action: "truncate_resume" | "abort_retry" | "escalated";
29
+ surface: HarmonySurface;
30
+ signal: string;
31
+ retryN: number;
32
+ model: string;
33
+ provider: string;
34
+ toolName?: string;
35
+ removedLen: number;
36
+ removedSha8: string;
37
+ removedPreview: string;
38
+ removedBlob?: string;
39
+ }
40
+ export interface HarmonyRecoveredToolCall {
41
+ message: AssistantMessage;
42
+ removed: string;
43
+ }
44
+ /**
45
+ * Whether to run leak detection on responses from this model. We default-on
46
+ * for every OpenAI code provider model rather than enumerating ids, so a future
47
+ * gpt-5.6 (or whatever) doesn't silently bypass the mitigation. Detection
48
+ * itself is cheap; the cost of missing a leak on a new model is not.
49
+ */
50
+ export declare function isHarmonyLeakMitigationTarget(model: Model): boolean;
51
+ export declare function signalListLabel(signals: readonly HarmonySignal[]): string;
52
+ /**
53
+ * Detect harmony-protocol leakage in `text`. Returns undefined if clean.
54
+ *
55
+ * Trip rule: `H` alone, or `M` paired with at least one co-signal
56
+ * (`C`/`G`/`S`/`B`/`R`/`T`). Bare `M` does not trip — this document, its
57
+ * tests, and bug reports legitimately carry the marker.
58
+ *
59
+ * `parsedEnd`, when supplied, marks the byte at which a structurally valid
60
+ * tool-argument parse ends; markers strictly after it set the `T` co-signal.
61
+ * `contentIndex`/`toolName`/`toolCallId` flow through to the returned
62
+ * detection for downstream auditing.
63
+ */
64
+ export declare function detectHarmonyLeak(text: string, surface: HarmonySurface, options?: {
65
+ parsedEnd?: number;
66
+ contentIndex?: number;
67
+ toolName?: string;
68
+ toolCallId?: string;
69
+ }): HarmonyDetection | undefined;
70
+ /** Scan an assistant message's content blocks; return the first detection. */
71
+ export declare function detectHarmonyLeakInAssistantMessage(message: AssistantMessage): HarmonyDetection | undefined;
72
+ /**
73
+ * Truncate a contaminated tool call at the start of the contaminated line and
74
+ * append the tool's recovery sentinel. Returns a recovered AssistantMessage
75
+ * (containing only the cleaned tool call), a synthetic continuation user
76
+ * message asking the model to re-issue the rest, and the removed substring
77
+ * for auditing. Returns undefined when the tool is not recovery-eligible or
78
+ * the truncation would leave nothing meaningful to dispatch.
79
+ *
80
+ * `providerPayload` is dropped from the recovered message: for OpenAI code backend the
81
+ * encrypted reasoning blob is opaque/signed and we cannot validate that it is
82
+ * uncontaminated. The model re-reasons on the next turn.
83
+ */
84
+ export declare function recoverHarmonyToolCall(message: AssistantMessage, detection: HarmonyDetection): HarmonyRecoveredToolCall | undefined;
85
+ /**
86
+ * Return the contaminated substring from `message` for audit purposes when
87
+ * recovery is not applicable (abort path). Walks from the first detected
88
+ * signal to end-of-content within the relevant block. Returns "" if the
89
+ * detection cannot be resolved against the message.
90
+ */
91
+ export declare function extractHarmonyRemoved(message: AssistantMessage, detection: HarmonyDetection): string;
92
+ export declare function createHarmonyAuditEvent(params: {
93
+ action: HarmonyAuditEvent["action"];
94
+ detection: HarmonyDetection;
95
+ model: Model;
96
+ retryN: number;
97
+ removed: string;
98
+ }): HarmonyAuditEvent;
99
+ export {};
@@ -0,0 +1,10 @@
1
+ export * from "./agent";
2
+ export * from "./agent-loop";
3
+ export * from "./append-only-context";
4
+ export * from "./compaction";
5
+ export * from "./harmony-leak";
6
+ export * from "./proxy";
7
+ export * from "./run-collector";
8
+ export * from "./telemetry";
9
+ export * from "./thinking";
10
+ export * from "./types";