@capekai/core 1.0.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.
Files changed (148) hide show
  1. package/README.md +12 -0
  2. package/package.json +105 -0
  3. package/src/adapters/ai-sdk.ts +84 -0
  4. package/src/compaction/contracts.ts +82 -0
  5. package/src/compaction/executor.ts +161 -0
  6. package/src/compaction/policy.ts +318 -0
  7. package/src/compaction/recovery.ts +139 -0
  8. package/src/compaction/task.ts +540 -0
  9. package/src/configuration/contracts.ts +58 -0
  10. package/src/configuration/defaults.ts +27 -0
  11. package/src/configuration/runtime.ts +42 -0
  12. package/src/configuration/single-model.ts +75 -0
  13. package/src/context/assembler.ts +112 -0
  14. package/src/context/index.ts +2 -0
  15. package/src/context/sources.ts +119 -0
  16. package/src/context/workspace.ts +63 -0
  17. package/src/core/agent.ts +401 -0
  18. package/src/core/build-tools.ts +139 -0
  19. package/src/core/chat-handler.ts +858 -0
  20. package/src/core/error-handling.ts +18 -0
  21. package/src/core/fork.ts +103 -0
  22. package/src/core/interrupt.ts +192 -0
  23. package/src/core/message-utils.ts +261 -0
  24. package/src/core/model-utils.ts +149 -0
  25. package/src/core/part-utils.ts +88 -0
  26. package/src/core/provider-utils.ts +67 -0
  27. package/src/core/revert.ts +46 -0
  28. package/src/core/step-handlers.ts +157 -0
  29. package/src/core/stream/finalization.ts +65 -0
  30. package/src/core/stream/stream-config.ts +82 -0
  31. package/src/core/stream-handlers.ts +242 -0
  32. package/src/core/structured-output.ts +68 -0
  33. package/src/core/tool-builders/agent-tools.ts +71 -0
  34. package/src/core/tool-builders/external-tools.ts +179 -0
  35. package/src/core/tool-builders/types.ts +16 -0
  36. package/src/core/tool-builders/workspace-tools.ts +293 -0
  37. package/src/core/tool-capabilities.ts +65 -0
  38. package/src/goals/evaluator.ts +171 -0
  39. package/src/goals/index.ts +3 -0
  40. package/src/goals/loop.ts +167 -0
  41. package/src/goals/service.ts +39 -0
  42. package/src/index.ts +10 -0
  43. package/src/internal/ask-authority.ts +29 -0
  44. package/src/internal/composition.ts +44 -0
  45. package/src/internal/configuration.ts +22 -0
  46. package/src/internal/execution.ts +108 -0
  47. package/src/internal/hosts.ts +64 -0
  48. package/src/internal/plugins.ts +71 -0
  49. package/src/internal/providers.ts +32 -0
  50. package/src/internal/sandbox.ts +19 -0
  51. package/src/internal/tools.ts +48 -0
  52. package/src/internal/workspace.ts +25 -0
  53. package/src/kernel/diagnostics.ts +249 -0
  54. package/src/kernel/errors.ts +120 -0
  55. package/src/kernel/events.ts +82 -0
  56. package/src/kernel/index.ts +72 -0
  57. package/src/kernel/kernel.ts +62 -0
  58. package/src/kernel/lifecycle.ts +72 -0
  59. package/src/kernel/plugin.ts +218 -0
  60. package/src/kernel/registry.ts +493 -0
  61. package/src/kernel/scope.ts +776 -0
  62. package/src/kernel/service-key.ts +19 -0
  63. package/src/kernel/types.ts +317 -0
  64. package/src/memory/index.ts +2 -0
  65. package/src/memory/memory-tool.ts +75 -0
  66. package/src/memory/registry.ts +172 -0
  67. package/src/permission/ask-user-api.ts +70 -0
  68. package/src/permission/contracts.ts +135 -0
  69. package/src/permission/permission-request-manager.ts +58 -0
  70. package/src/permission/policy.ts +277 -0
  71. package/src/permission/runtime.ts +612 -0
  72. package/src/plugins/compaction-policy.ts +46 -0
  73. package/src/plugins/compose.ts +171 -0
  74. package/src/plugins/context-sections.ts +246 -0
  75. package/src/plugins/default-agent-driver.ts +14 -0
  76. package/src/plugins/facade-plugins.ts +129 -0
  77. package/src/plugins/goal-domain.ts +82 -0
  78. package/src/plugins/legacy-system-message.ts +152 -0
  79. package/src/plugins/loaded-tools.ts +23 -0
  80. package/src/plugins/memory-domain.ts +264 -0
  81. package/src/plugins/orchestrator-session.ts +29 -0
  82. package/src/plugins/permission-policy.ts +49 -0
  83. package/src/plugins/retry-policy.ts +28 -0
  84. package/src/plugins/scheduler-domain.ts +192 -0
  85. package/src/plugins/service-keys.ts +294 -0
  86. package/src/plugins/session-search-domain.ts +238 -0
  87. package/src/plugins/skills-domain.ts +272 -0
  88. package/src/plugins/subagent-domain.ts +287 -0
  89. package/src/plugins/tool-catalog.ts +78 -0
  90. package/src/plugins/tool-output-policy.ts +52 -0
  91. package/src/plugins/value-plugins.ts +150 -0
  92. package/src/plugins/workflow-domain.ts +198 -0
  93. package/src/plugins/workspace-policy.ts +37 -0
  94. package/src/providers/registry.ts +63 -0
  95. package/src/providers/types.ts +44 -0
  96. package/src/retry/policy.ts +282 -0
  97. package/src/retry/stream-chat.ts +312 -0
  98. package/src/runtime/agent-runtime.ts +83 -0
  99. package/src/runtime/default-agent-driver.ts +23 -0
  100. package/src/runtime/domain-tool-source.ts +156 -0
  101. package/src/runtime/events.ts +61 -0
  102. package/src/runtime/host-dependencies.ts +71 -0
  103. package/src/runtime/host-guidance.ts +22 -0
  104. package/src/runtime/host-layout.ts +23 -0
  105. package/src/runtime/host.ts +129 -0
  106. package/src/runtime/standalone-host.ts +118 -0
  107. package/src/sandbox/controller.ts +204 -0
  108. package/src/sandbox/model.ts +305 -0
  109. package/src/sandbox/provider.ts +53 -0
  110. package/src/sandbox/types.ts +110 -0
  111. package/src/scheduler/host.ts +22 -0
  112. package/src/scheduler/scheduler-tool.ts +172 -0
  113. package/src/session-search/host.ts +56 -0
  114. package/src/session-search/index.ts +23 -0
  115. package/src/session-search/session-search-tool.ts +151 -0
  116. package/src/skills/index.ts +3 -0
  117. package/src/skills/registry.ts +63 -0
  118. package/src/skills/skill-manage-tool.ts +205 -0
  119. package/src/skills/skill-tool.ts +42 -0
  120. package/src/storage/contracts.ts +159 -0
  121. package/src/storage/memory.ts +321 -0
  122. package/src/storage/options.ts +75 -0
  123. package/src/storage/runtime.ts +115 -0
  124. package/src/storage/sqlite-tool-output-artifacts.ts +106 -0
  125. package/src/storage/sqlite.ts +321 -0
  126. package/src/storage/tool-output-artifacts.ts +75 -0
  127. package/src/storage.ts +31 -0
  128. package/src/subagent/child-session.ts +282 -0
  129. package/src/subagent/guidance.ts +8 -0
  130. package/src/subagent/policy.ts +198 -0
  131. package/src/subagent/task-tool.ts +584 -0
  132. package/src/tool-output/contracts.ts +111 -0
  133. package/src/tool-output/policy.ts +410 -0
  134. package/src/tool.ts +1 -0
  135. package/src/tools/executor.ts +258 -0
  136. package/src/tools/install-manifest.ts +40 -0
  137. package/src/tools/llm-api.ts +77 -0
  138. package/src/tools/registry.ts +206 -0
  139. package/src/tools/tool-artifact.ts +182 -0
  140. package/src/tools/tool-source.ts +53 -0
  141. package/src/utils/errors.ts +334 -0
  142. package/src/utils/strip-visualization.ts +50 -0
  143. package/src/workflow/decomposer.ts +139 -0
  144. package/src/workflow/execution.ts +523 -0
  145. package/src/workflow/orchestrator-session.ts +161 -0
  146. package/src/workflow/synthesizer.ts +130 -0
  147. package/src/workspace/contracts.ts +135 -0
  148. package/src/workspace/policy.ts +327 -0
package/README.md ADDED
@@ -0,0 +1,12 @@
1
+ # @capekai/core
2
+
3
+ Bun-native agent framework contracts and runtime implementations for composition, execution, hosts, providers, tools, sandbox, and storage.
4
+
5
+ Requires Bun 1.3 or newer.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @capekai/core
11
+ ```
12
+ Public subpaths include `composition`, `plugins`, `hosts`, `execution`, `providers`, `tools`, `ask-authority`, `sandbox`, `workspace`, `configuration`, `tool`, and `storage`.
package/package.json ADDED
@@ -0,0 +1,105 @@
1
+ {
2
+ "name": "@capekai/core",
3
+ "version": "1.0.0",
4
+ "description": "Bun-native composable agent runtime and framework for Capek.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/capek-dev/prokop.git",
9
+ "directory": "packages/capek"
10
+ },
11
+ "type": "module",
12
+ "types": "./src/index.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./src/index.ts",
16
+ "default": "./src/index.ts"
17
+ },
18
+ "./composition": {
19
+ "types": "./src/internal/composition.ts",
20
+ "default": "./src/internal/composition.ts"
21
+ },
22
+ "./plugins": {
23
+ "types": "./src/internal/plugins.ts",
24
+ "default": "./src/internal/plugins.ts"
25
+ },
26
+ "./hosts": {
27
+ "types": "./src/internal/hosts.ts",
28
+ "default": "./src/internal/hosts.ts"
29
+ },
30
+ "./execution": {
31
+ "types": "./src/internal/execution.ts",
32
+ "default": "./src/internal/execution.ts"
33
+ },
34
+ "./providers": {
35
+ "types": "./src/internal/providers.ts",
36
+ "default": "./src/internal/providers.ts"
37
+ },
38
+ "./tools": {
39
+ "types": "./src/internal/tools.ts",
40
+ "default": "./src/internal/tools.ts"
41
+ },
42
+ "./ask-authority": {
43
+ "types": "./src/internal/ask-authority.ts",
44
+ "default": "./src/internal/ask-authority.ts"
45
+ },
46
+ "./sandbox": {
47
+ "types": "./src/internal/sandbox.ts",
48
+ "default": "./src/internal/sandbox.ts"
49
+ },
50
+ "./workspace": {
51
+ "types": "./src/internal/workspace.ts",
52
+ "default": "./src/internal/workspace.ts"
53
+ },
54
+ "./configuration": {
55
+ "types": "./src/internal/configuration.ts",
56
+ "default": "./src/internal/configuration.ts"
57
+ },
58
+ "./tool": {
59
+ "types": "./src/tool.ts",
60
+ "default": "./src/tool.ts"
61
+ },
62
+ "./storage": {
63
+ "types": "./src/storage.ts",
64
+ "default": "./src/storage.ts"
65
+ }
66
+ },
67
+ "files": [
68
+ "src",
69
+ "README.md"
70
+ ],
71
+ "engines": {
72
+ "bun": ">=1.3.0"
73
+ },
74
+ "publishConfig": {
75
+ "access": "public"
76
+ },
77
+ "dependencies": {
78
+ "@ai-sdk/deepseek": "^2.0.35",
79
+ "@ai-sdk/openai": "^3.0.84",
80
+ "@capekai/tool": "^1.0.0",
81
+ "@capekai/types": "^1.0.0",
82
+ "@openrouter/ai-sdk-provider": "^2.3.3",
83
+ "@zip.js/zip.js": "^2.7.60",
84
+ "ai": "^6.0.116",
85
+ "ignore": "^7.0.3",
86
+ "picomatch": "^4.0.2",
87
+ "tar": "^7.5.13",
88
+ "vercel-minimax-ai-provider": "^0.0.2",
89
+ "zhipu-ai-provider": "^0.2.2"
90
+ },
91
+ "scripts": {
92
+ "build": "tsc --noEmit",
93
+ "typecheck": "tsc --noEmit",
94
+ "lint": "eslint src tests",
95
+ "test": "bun test tests"
96
+ },
97
+ "devDependencies": {
98
+ "@types/bun": "^1.3.11",
99
+ "@types/node": "^22.19.15",
100
+ "@types/picomatch": "^4.0.1",
101
+ "@types/tar": "^7.0.87",
102
+ "bun-types": "^1.3.9",
103
+ "typescript": "^5.7.2"
104
+ }
105
+ }
@@ -0,0 +1,84 @@
1
+ import { createOpenAI } from '@ai-sdk/openai';
2
+ import {
3
+ dynamicTool,
4
+ jsonSchema,
5
+ streamText,
6
+ type JSONSchema7,
7
+ type LanguageModel,
8
+ type Tool,
9
+ } from 'ai';
10
+ import { getModelWithMetadata } from '../core/model-utils';
11
+ import type { ModelFactoryResult } from '../providers/types';
12
+
13
+ export interface TextModelRequest {
14
+ modelId?: string;
15
+ providerId?: string;
16
+ systemPrompt: string;
17
+ prompt: string;
18
+ sessionId?: string;
19
+ maxOutputTokens?: number;
20
+ temperature?: number;
21
+ }
22
+
23
+ export async function runTextModel(request: TextModelRequest): Promise<string> {
24
+ const { model, omitMaxOutputTokens, providerOptions, useProviderInstructions } = await getModelWithMetadata({
25
+ modelId: request.modelId,
26
+ providerId: request.providerId,
27
+ systemPrompt: request.systemPrompt,
28
+ sessionId: request.sessionId,
29
+ });
30
+ const stream = streamText({
31
+ model,
32
+ system: useProviderInstructions ? undefined : request.systemPrompt,
33
+ prompt: request.prompt,
34
+ ...(omitMaxOutputTokens || request.maxOutputTokens === undefined
35
+ ? {}
36
+ : { maxOutputTokens: request.maxOutputTokens }),
37
+ temperature: request.temperature,
38
+ providerOptions: providerOptions as Parameters<typeof streamText>[0]['providerOptions'],
39
+ });
40
+ return stream.text;
41
+ }
42
+
43
+ export interface OpenAiResponsesModelRequest {
44
+ modelId: string;
45
+ apiKey: string;
46
+ fetch: typeof globalThis.fetch;
47
+ systemPrompt?: string;
48
+ sessionId?: string;
49
+ }
50
+
51
+ export function createOpenAiResponsesModel(request: OpenAiResponsesModelRequest): ModelFactoryResult {
52
+ const openai = createOpenAI({
53
+ apiKey: request.apiKey,
54
+ fetch: request.fetch,
55
+ });
56
+ return {
57
+ model: openai.responses(request.modelId) as unknown as LanguageModel,
58
+ useProviderInstructions: true,
59
+ omitMaxOutputTokens: true,
60
+ providerOptions: {
61
+ openai: {
62
+ instructions: request.systemPrompt || 'You are a helpful assistant.',
63
+ promptCacheKey: request.sessionId,
64
+ store: false,
65
+ },
66
+ },
67
+ };
68
+ }
69
+
70
+ export interface CapabilityToolRequest {
71
+ description: string;
72
+ inputSchema: Record<string, unknown>;
73
+ execute(args: unknown): Promise<unknown>;
74
+ }
75
+
76
+ export function createCapabilityTool(request: CapabilityToolRequest): Tool {
77
+ return dynamicTool({
78
+ description: request.description,
79
+ inputSchema: jsonSchema(request.inputSchema as JSONSchema7),
80
+ execute: request.execute,
81
+ });
82
+ }
83
+
84
+ export type CapabilityTool = Tool;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * C6 compaction contracts: the data shapes the compaction service and task
3
+ * pipeline consume. The `CompactionPolicy` data interface keeps its exact
4
+ * pre-C6 identity; `core/compaction.ts` re-exports these types so the compat
5
+ * barrel, `core/agent.ts`, and every existing consumer keep working.
6
+ */
7
+
8
+ import type { AssistantMessage, TextPart } from '@capekai/types';
9
+
10
+ /** Compaction trigger reasons */
11
+ export type CompactionTriggerReason = 'manual' | 'auto' | 'overflow';
12
+
13
+ /**
14
+ * Compaction policy for configuring summary generation.
15
+ * All model/provider fields are optional - null means "use session/default".
16
+ * Pruning fields control which tool outputs get marked as compacted.
17
+ * Auto-threshold fields control the hybrid formula for pre-overflow compaction.
18
+ */
19
+ export interface CompactionPolicy {
20
+ modelId: string | null;
21
+ providerId: string | null;
22
+ maxOutputTokens: number;
23
+ overflowThresholdRatio: number | null;
24
+ // WS4: Budget-aware pruning knobs
25
+ preserveRecentToolCount: number;
26
+ preserveSmallToolChars: number;
27
+ toolClearCharsThreshold: number;
28
+ maxPrunedToolCount: number;
29
+ // Hybrid formula for auto-compaction threshold
30
+ autoThresholdRatio: number;
31
+ autoReserveCapTokens: number;
32
+ autoSafetyMarginTokens: number;
33
+ }
34
+
35
+ /**
36
+ * Trigger created before compaction.
37
+ * The trigger is a user message with a standard CompactionPart.
38
+ */
39
+ export interface CompactionTrigger {
40
+ messageId: string;
41
+ reason: CompactionTriggerReason;
42
+ }
43
+
44
+ /**
45
+ * Compaction task result
46
+ */
47
+ export interface CompactionTaskResult {
48
+ trigger: CompactionTrigger;
49
+ summaryMessage: AssistantMessage;
50
+ textParts: TextPart[];
51
+ tokensUsed: {
52
+ prompt: number;
53
+ completion: number;
54
+ cacheRead?: number;
55
+ cacheWrite?: number;
56
+ noCache?: number;
57
+ };
58
+ }
59
+
60
+ /**
61
+ * A function that generates a summary from a prompt.
62
+ * Abstracts away model resolution and the generateText/streamText call.
63
+ */
64
+ export interface GenerateSummaryFn {
65
+ (prompt: string, policy: CompactionPolicy, sessionId: string, abortSignal?: AbortSignal): Promise<{
66
+ text: string;
67
+ usage: {
68
+ prompt: number;
69
+ completion: number;
70
+ cacheRead?: number;
71
+ cacheWrite?: number;
72
+ noCache?: number;
73
+ };
74
+ effectiveModelId: string;
75
+ effectiveProviderId: string;
76
+ }>;
77
+ }
78
+
79
+ export interface AutoThresholdResult {
80
+ threshold: number;
81
+ contextWindow: number | undefined;
82
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * C6 compaction executor. The pre-C6 module-global active-session set is
3
+ * replaced by scope-owned state: every guard and state transition resolves
4
+ * through `getCompactionService()`, so composed agent scopes hold isolated
5
+ * concurrency guards and unscoped consumers keep the process-default
6
+ * behavior.
7
+ */
8
+
9
+ import { getModelsConfig } from '../configuration/runtime';
10
+ import {
11
+ emitRuntimeEvent,
12
+ emitSessionUpdated,
13
+ } from '../runtime/host-dependencies';
14
+ import { getMessageWithParts, getSession, updateSession } from '../storage/runtime';
15
+ import type { BroadcastFn, BroadcastSessionFn } from '../runtime/host';
16
+ import type { CompactionTriggerReason, GenerateSummaryFn } from './contracts';
17
+ import { getCompactionService } from './policy';
18
+ import {
19
+ createCompactionTrigger,
20
+ persistCompactionFailure,
21
+ processCompactionTask,
22
+ } from './task';
23
+
24
+ export interface CompactionExecutorResult {
25
+ ok: true;
26
+ result: {
27
+ tokensUsed: {
28
+ prompt: number;
29
+ completion: number;
30
+ cacheRead?: number;
31
+ cacheWrite?: number;
32
+ noCache?: number;
33
+ };
34
+ summaryMessageId: string;
35
+ textParts: Array<{ id: string; messageId: string; createdAt: number; type: string; text: string }>;
36
+ };
37
+ triggerMessageId: string;
38
+ reason: CompactionTriggerReason;
39
+ }
40
+
41
+ export interface CompactionExecutorError {
42
+ ok: false;
43
+ error: string;
44
+ triggerMessageId: string | null;
45
+ reason: CompactionTriggerReason;
46
+ skipped: boolean;
47
+ }
48
+
49
+ export function isCompactionActive(sessionId: string): boolean {
50
+ return getCompactionService().isCompactionActive(sessionId);
51
+ }
52
+
53
+ export async function executeCompaction(
54
+ sessionId: string,
55
+ reason: CompactionTriggerReason,
56
+ broadcast: BroadcastFn = emitRuntimeEvent,
57
+ broadcastSessUpdate: BroadcastSessionFn = emitSessionUpdated,
58
+ abortSignal?: AbortSignal,
59
+ /** Additive test/injection seam mirroring `processCompactionTask`'s own
60
+ * generateSummaryFn parameter. Production callers pass at most five
61
+ * arguments exactly like before; focused tests inject a deterministic
62
+ * summary generator to pin abort and failure behavior without a real
63
+ * model call. */
64
+ generateSummaryFn?: GenerateSummaryFn,
65
+ ): Promise<CompactionExecutorResult | CompactionExecutorError> {
66
+ const service = getCompactionService();
67
+ if (service.isCompactionActive(sessionId)) {
68
+ return {
69
+ ok: false,
70
+ error: 'Compaction is already in progress for this session',
71
+ triggerMessageId: null,
72
+ reason,
73
+ skipped: true,
74
+ };
75
+ }
76
+
77
+ let triggerMessageId: string | null = null;
78
+ const session = await getSession(sessionId);
79
+ if (!session || session.parentId) {
80
+ return {
81
+ ok: false,
82
+ error: 'Compaction is only available for main sessions',
83
+ triggerMessageId: null,
84
+ reason,
85
+ skipped: true,
86
+ };
87
+ }
88
+
89
+ // C6 step 6: mandatory concurrency gate backed by the persisted session
90
+ // flag. The flag is written by this executor and cleared by recovery, so
91
+ // a custom policy service whose in-memory guard is broken still cannot
92
+ // start a second compaction for the same session.
93
+ //
94
+ // Honest limitation (pre-existing, preserved): the persisted flag guards
95
+ // single-process exclusivity plus recovery. A second OS process writing
96
+ // the same database directly can still race the flag (check-then-set
97
+ // TOCTOU); SQLite locking is out of C6 scope.
98
+ if (session.compacting) {
99
+ return {
100
+ ok: false,
101
+ error: 'Compaction is already in progress for this session',
102
+ triggerMessageId: null,
103
+ reason,
104
+ skipped: true,
105
+ };
106
+ }
107
+
108
+ const config = getModelsConfig();
109
+ const policy = service.resolvePolicy(
110
+ session.selectedModel || config.defaultModel,
111
+ session.selectedProvider || config.defaultProvider,
112
+ );
113
+
114
+ service.beginCompaction(sessionId);
115
+ const compactingSession = await updateSession(sessionId, { compacting: true });
116
+ if (compactingSession) broadcastSessUpdate(compactingSession);
117
+
118
+ try {
119
+ const trigger = await createCompactionTrigger(sessionId, reason);
120
+ triggerMessageId = trigger.messageId;
121
+ const triggerMsg = await getMessageWithParts(trigger.messageId);
122
+ if (triggerMsg) {
123
+ broadcast({ kind: 'message', action: 'created', message: triggerMsg.message });
124
+ for (const part of triggerMsg.parts) broadcast({ kind: 'part', action: 'created', sessionId, part });
125
+ }
126
+
127
+ const result = await processCompactionTask(sessionId, trigger.messageId, policy, generateSummaryFn, abortSignal);
128
+ broadcast({ kind: 'message', action: 'created', message: result.summaryMessage });
129
+ for (const part of result.textParts) broadcast({ kind: 'part', action: 'created', sessionId, part });
130
+
131
+ const completedSession = await updateSession(sessionId, {
132
+ promptTokens: result.tokensUsed.prompt,
133
+ completionTokens: result.tokensUsed.completion,
134
+ totalTokens: result.tokensUsed.prompt + result.tokensUsed.completion,
135
+ cacheReadTokens: result.tokensUsed.cacheRead ?? 0,
136
+ cacheWriteTokens: result.tokensUsed.cacheWrite ?? 0,
137
+ noCacheTokens: result.tokensUsed.noCache ?? 0,
138
+ compacting: false,
139
+ });
140
+ if (completedSession) broadcastSessUpdate(completedSession);
141
+
142
+ return {
143
+ ok: true,
144
+ result: {
145
+ tokensUsed: result.tokensUsed,
146
+ summaryMessageId: result.summaryMessage.id,
147
+ textParts: result.textParts,
148
+ },
149
+ triggerMessageId,
150
+ reason,
151
+ };
152
+ } catch (err: unknown) {
153
+ const updatedSession = await updateSession(sessionId, { compacting: false });
154
+ if (updatedSession) broadcastSessUpdate(updatedSession);
155
+ const errorMessage = err instanceof Error ? err.message : 'Compaction failed';
156
+ if (triggerMessageId) await persistCompactionFailure(sessionId, triggerMessageId, errorMessage, broadcast);
157
+ return { ok: false, error: errorMessage, triggerMessageId, reason, skipped: false };
158
+ } finally {
159
+ service.endCompaction(sessionId);
160
+ }
161
+ }