@mikode13/harness 11.0.1 → 11.2.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 (120) hide show
  1. package/README.md +186 -16
  2. package/dist/agent/domain/agent.d.ts +41 -3
  3. package/dist/agent/domain/agent.js.map +1 -1
  4. package/dist/agent/domain/approval.d.ts +20 -0
  5. package/dist/agent/domain/approval.js.map +1 -1
  6. package/dist/agent/domain/inbox.d.ts +42 -0
  7. package/dist/agent/domain/inbox.js +81 -0
  8. package/dist/agent/domain/inbox.js.map +1 -0
  9. package/dist/agent/domain/oneRunAtATime.d.ts +17 -0
  10. package/dist/agent/domain/oneRunAtATime.js +32 -0
  11. package/dist/agent/domain/oneRunAtATime.js.map +1 -0
  12. package/dist/agent/domain/runContext.d.ts +9 -0
  13. package/dist/agent/domain/runContext.js +5 -0
  14. package/dist/agent/domain/runContext.js.map +1 -1
  15. package/dist/agent/domain/session.d.ts +15 -0
  16. package/dist/agent/domain/session.js +80 -0
  17. package/dist/agent/domain/session.js.map +1 -0
  18. package/dist/engines/claude/infrastructure/model/claudeAgent.d.ts +6 -2
  19. package/dist/engines/claude/infrastructure/model/claudeAgent.js +20 -5
  20. package/dist/engines/claude/infrastructure/model/claudeAgent.js.map +1 -1
  21. package/dist/engines/codex/infrastructure/model/codexAgent.d.ts +5 -1
  22. package/dist/engines/codex/infrastructure/model/codexAgent.js +13 -2
  23. package/dist/engines/codex/infrastructure/model/codexAgent.js.map +1 -1
  24. package/dist/engines/domain/model/backgroundAgents.d.ts +5 -2
  25. package/dist/engines/domain/model/backgroundAgents.js +11 -3
  26. package/dist/engines/domain/model/backgroundAgents.js.map +1 -1
  27. package/dist/engines/domain/model/compactor.d.ts +40 -0
  28. package/dist/engines/domain/model/compactor.js +5 -0
  29. package/dist/engines/domain/model/compactor.js.map +1 -0
  30. package/dist/engines/domain/model/llmAgent.d.ts +53 -2
  31. package/dist/engines/domain/model/llmAgent.js +419 -24
  32. package/dist/engines/domain/model/llmAgent.js.map +1 -1
  33. package/dist/engines/domain/model/recentCompaction.d.ts +39 -0
  34. package/dist/engines/domain/model/recentCompaction.js +119 -0
  35. package/dist/engines/domain/model/recentCompaction.js.map +1 -0
  36. package/dist/engines/domain/model/stepContext.js +3 -0
  37. package/dist/engines/domain/model/stepContext.js.map +1 -1
  38. package/dist/engines/domain/model/triagedCompaction.d.ts +27 -0
  39. package/dist/engines/domain/model/triagedCompaction.js +60 -0
  40. package/dist/engines/domain/model/triagedCompaction.js.map +1 -0
  41. package/dist/factory/infrastructure/agentLLMFactory.d.ts +56 -15
  42. package/dist/factory/infrastructure/agentLLMFactory.js +144 -25
  43. package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
  44. package/dist/index.d.ts +6 -2
  45. package/dist/index.js +3 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/llm/domain/contextTriage.d.ts +26 -0
  48. package/dist/llm/domain/contextTriage.js +3 -0
  49. package/dist/llm/domain/contextTriage.js.map +1 -0
  50. package/dist/llm/domain/llm.d.ts +18 -1
  51. package/dist/llm/domain/llm.js.map +1 -1
  52. package/dist/llm/domain/message.d.ts +18 -1
  53. package/dist/llm/domain/message.js +20 -0
  54. package/dist/llm/domain/message.js.map +1 -1
  55. package/dist/llm/infrastructure/claudeLLMClient.d.ts +4 -5
  56. package/dist/llm/infrastructure/claudeLLMClient.js +39 -13
  57. package/dist/llm/infrastructure/claudeLLMClient.js.map +1 -1
  58. package/dist/llm/infrastructure/jevContextTriage.d.ts +18 -0
  59. package/dist/llm/infrastructure/jevContextTriage.js +92 -0
  60. package/dist/llm/infrastructure/jevContextTriage.js.map +1 -0
  61. package/dist/llm/infrastructure/openAILLMClient.d.ts +4 -5
  62. package/dist/llm/infrastructure/openAILLMClient.js +20 -9
  63. package/dist/llm/infrastructure/openAILLMClient.js.map +1 -1
  64. package/dist/orchestration/domain/model/orchestratorAgent.d.ts +9 -1
  65. package/dist/orchestration/domain/model/orchestratorAgent.js +20 -3
  66. package/dist/orchestration/domain/model/orchestratorAgent.js.map +1 -1
  67. package/dist/recovery/domain/history.d.ts +10 -3
  68. package/dist/recovery/domain/history.js +1 -0
  69. package/dist/recovery/domain/history.js.map +1 -1
  70. package/dist/recovery/domain/recoveryStore.d.ts +14 -2
  71. package/dist/recovery/domain/recoveryStore.js.map +1 -1
  72. package/dist/recovery/infrastructure/fileRecoveryStore.d.ts +21 -2
  73. package/dist/recovery/infrastructure/fileRecoveryStore.js +58 -6
  74. package/dist/recovery/infrastructure/fileRecoveryStore.js.map +1 -1
  75. package/dist/recovery/infrastructure/folderClaims.d.ts +27 -0
  76. package/dist/recovery/infrastructure/folderClaims.js +124 -0
  77. package/dist/recovery/infrastructure/folderClaims.js.map +1 -0
  78. package/dist/shared/domain/session.d.ts +12 -0
  79. package/dist/shared/domain/session.js +2 -0
  80. package/dist/shared/domain/session.js.map +1 -0
  81. package/dist/tools/domain/preparedCall.d.ts +5 -2
  82. package/dist/tools/domain/preparedCall.js +7 -1
  83. package/dist/tools/domain/preparedCall.js.map +1 -1
  84. package/dist/tools/domain/readRegistry.d.ts +11 -0
  85. package/dist/tools/domain/readRegistry.js.map +1 -1
  86. package/dist/tools/infrastructure/agentAsTool.d.ts +6 -0
  87. package/dist/tools/infrastructure/agentAsTool.js +9 -0
  88. package/dist/tools/infrastructure/agentAsTool.js.map +1 -1
  89. package/dist/tools/infrastructure/commandTool.js +6 -1
  90. package/dist/tools/infrastructure/commandTool.js.map +1 -1
  91. package/dist/tools/infrastructure/exploreTool.js +2 -0
  92. package/dist/tools/infrastructure/exploreTool.js.map +1 -1
  93. package/dist/tools/infrastructure/fileTools.d.ts +2 -0
  94. package/dist/tools/infrastructure/fileTools.js +10 -1
  95. package/dist/tools/infrastructure/fileTools.js.map +1 -1
  96. package/dist/tools/infrastructure/removeWorktreeTool.d.ts +23 -0
  97. package/dist/tools/infrastructure/removeWorktreeTool.js +178 -0
  98. package/dist/tools/infrastructure/removeWorktreeTool.js.map +1 -0
  99. package/dist/tools/infrastructure/subagents.d.ts +3 -1
  100. package/dist/tools/infrastructure/subagents.js +2 -1
  101. package/dist/tools/infrastructure/subagents.js.map +1 -1
  102. package/dist/tools/infrastructure/trackedEdits.js +1 -0
  103. package/dist/tools/infrastructure/trackedEdits.js.map +1 -1
  104. package/dist/tools/infrastructure/workspaceWrites.d.ts +17 -1
  105. package/dist/tools/infrastructure/workspaceWrites.js +48 -17
  106. package/dist/tools/infrastructure/workspaceWrites.js.map +1 -1
  107. package/dist/tools/infrastructure/worktrees.d.ts +49 -0
  108. package/dist/tools/infrastructure/worktrees.js +154 -0
  109. package/dist/tools/infrastructure/worktrees.js.map +1 -0
  110. package/dist/tools/infrastructure/writeCeiling.d.ts +16 -0
  111. package/dist/tools/infrastructure/writeCeiling.js +49 -0
  112. package/dist/tools/infrastructure/writeCeiling.js.map +1 -0
  113. package/dist/tools/infrastructure/writeSession.d.ts +5 -1
  114. package/dist/tools/infrastructure/writeSession.js +7 -3
  115. package/dist/tools/infrastructure/writeSession.js.map +1 -1
  116. package/dist/workflow/domain/workflow.d.ts +3 -0
  117. package/dist/workflow/domain/workflow.js.map +1 -1
  118. package/dist/workflow/domain/workflowRunner.js +8 -2
  119. package/dist/workflow/domain/workflowRunner.js.map +1 -1
  120. package/package.json +2 -1
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,eAAe,EACf,SAAS,EACT,mBAAmB,GAEnB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACN,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,sBAAsB,EACtB,SAAS,EACT,mBAAmB,GAKnB,MAAM,6CAA6C,CAAC;AAErD,OAAO,EACN,WAAW,GAGX,MAAM,uCAAuC,CAAC;AAW/C,OAAO,EACN,cAAc,GAKd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAQN,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACN,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAE/D,OAAO,EACN,iBAAiB,GAOjB,MAAM,4BAA4B,CAAC;AAKpC,OAAO,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,MAAM,4CAA4C,CAAC;AAE3E,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GAGnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,6CAA6C,CAAC;AAC7E,OAAO,EACN,oBAAoB,GASpB,MAAM,+BAA+B,CAAC","sourcesContent":["export type { ILogger } from './shared/domain/logger.ts';\nexport {\n\tcreateAgent,\n\tcreateOrchestrator,\n\tagentModels,\n\tagentReasoningEfforts,\n\tisAgentProvider,\n\tisModelOf,\n\tisReasoningEffortOf,\n\ttype CreateAgentOptions,\n} from './factory/infrastructure/agentFactory.ts';\nexport {\n\tcreateFileTools,\n\tcreateLLMAgent,\n\tcreateLLMOrchestrator,\n\tisLLMModelOf,\n\tisLLMReasoningEffortOf,\n\tllmModels,\n\tllmReasoningEfforts,\n\ttype CreateLLMAgentOptions,\n\ttype CreateLLMOrchestratorOptions,\n\ttype RoleSteps,\n\ttype RoleTokens,\n} from './factory/infrastructure/agentLLMFactory.ts';\nexport type { FileTool, WorkspaceOptions } from './tools/infrastructure/fileTools.ts';\nexport {\n\tagentAsTool,\n\ttype AgentAsToolOptions,\n\ttype DelegationTool,\n} from './tools/infrastructure/agentAsTool.ts';\nexport type { WorkspaceRoot } from './tools/domain/accessPolicy.ts';\nexport type {\n\tCommandArgument,\n\tCommandDefinition,\n\tCommandParameter,\n\tCommandRole,\n\tCommands,\n\tFormatter,\n} from './tools/domain/commands.ts';\nexport type { SecretRules } from './tools/infrastructure/rootsAccessPolicy.ts';\nexport {\n\tagentProviders,\n\ttype AgentModel,\n\ttype AgentProvider,\n\ttype CreateOrchestratorOptions,\n\ttype ReasoningEffort,\n} from './factory/infrastructure/types.ts';\nexport {\n\ttype Agent,\n\ttype AgentResponse,\n\ttype Callback,\n\ttype ProgressAgent,\n\ttype ProgressEvent,\n\ttype StepContext,\n\ttype RunOptions,\n} from './agent/domain/agent.ts';\nexport type { Transcript, TranscriptCallback } from './agent/domain/transcript.ts';\nexport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n} from './shared/domain/errors.ts';\nexport { isAbortError } from './shared/domain/isAbortError.ts';\nexport type { Tokens } from './shared/domain/tokens.ts';\nexport {\n\trememberApprovals,\n\ttype ApprovalDecision,\n\ttype ApprovalRequest,\n\ttype Approver,\n\ttype CommandLine,\n\ttype RememberableDecision,\n\ttype ToolRisk,\n} from './agent/domain/approval.ts';\nexport type { Asker, QuestionOption, UserQuestion } from './agent/domain/question.ts';\nexport type { JSONSchema, ToolDefinition } from './llm/domain/tool.ts';\nexport type { Tool, ToolSummarizer } from './tools/domain/tool.ts';\nexport type { TextMatch, Workspace } from './tools/domain/workspace.ts';\nexport { defineTool } from './tools/infrastructure/defineTool.ts';\nexport { createWorkspace } from './tools/infrastructure/createWorkspace.ts';\nexport { createWorkspaceTools } from './tools/infrastructure/workspaceTools.ts';\nexport { createHistory } from './recovery/infrastructure/createHistory.ts';\nexport type { History, HistoryRun, Move } from './recovery/domain/history.ts';\nexport {\n\thistoryStart,\n\tNothingToMoveError,\n\tWorkspaceBusyError,\n\tWorkspaceMovedError,\n\ttype MoveOptions,\n\ttype RunStatus,\n} from './recovery/domain/recoveryStore.ts';\nexport { HistoryExpiredError, UnknownRunError } from './recovery/domain/runTree.ts';\nexport { createWorkflow } from './factory/infrastructure/workflowFactory.ts';\nexport {\n\tdefineWorkflowStages,\n\ttype CreateWorkflowOptions,\n\ttype Workflow,\n\ttype WorkflowResult,\n\ttype WorkflowStage,\n\ttype WorkflowStageDefinition,\n\ttype WorkflowStageName,\n\ttype WorkflowStageReport,\n\ttype WorkflowTransition,\n} from './workflow/domain/workflow.ts';\nexport type { Validator } from './shared/domain/validator.ts';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,eAAe,EACf,SAAS,EACT,mBAAmB,GAEnB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACN,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,sBAAsB,EACtB,SAAS,EACT,mBAAmB,GAOnB,MAAM,6CAA6C,CAAC;AAErD,OAAO,EACN,WAAW,GAGX,MAAM,uCAAuC,CAAC;AAW/C,OAAO,EACN,cAAc,GAKd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAQN,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EACN,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAG/D,OAAO,EACN,iBAAiB,GAQjB,MAAM,4BAA4B,CAAC;AAKpC,OAAO,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,MAAM,4CAA4C,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAErE,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GAGnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,6CAA6C,CAAC;AAC7E,OAAO,EACN,oBAAoB,GASpB,MAAM,+BAA+B,CAAC","sourcesContent":["export type { ILogger } from './shared/domain/logger.ts';\nexport {\n\tcreateAgent,\n\tcreateOrchestrator,\n\tagentModels,\n\tagentReasoningEfforts,\n\tisAgentProvider,\n\tisModelOf,\n\tisReasoningEffortOf,\n\ttype CreateAgentOptions,\n} from './factory/infrastructure/agentFactory.ts';\nexport {\n\tcreateFileTools,\n\tcreateLLMAgent,\n\tcreateLLMOrchestrator,\n\tisLLMModelOf,\n\tisLLMReasoningEffortOf,\n\tllmModels,\n\tllmReasoningEfforts,\n\ttype CreateLLMAgentOptions,\n\ttype CreateLLMOrchestratorOptions,\n\ttype RoleSteps,\n\ttype RoleTokens,\n\ttype RoleAgents,\n\ttype CompactionMode,\n} from './factory/infrastructure/agentLLMFactory.ts';\nexport type { FileTool, WorkspaceOptions } from './tools/infrastructure/fileTools.ts';\nexport {\n\tagentAsTool,\n\ttype AgentAsToolOptions,\n\ttype DelegationTool,\n} from './tools/infrastructure/agentAsTool.ts';\nexport type { WorkspaceRoot } from './tools/domain/accessPolicy.ts';\nexport type {\n\tCommandArgument,\n\tCommandDefinition,\n\tCommandParameter,\n\tCommandRole,\n\tCommands,\n\tFormatter,\n} from './tools/domain/commands.ts';\nexport type { SecretRules } from './tools/infrastructure/rootsAccessPolicy.ts';\nexport {\n\tagentProviders,\n\ttype AgentModel,\n\ttype AgentProvider,\n\ttype CreateOrchestratorOptions,\n\ttype ReasoningEffort,\n} from './factory/infrastructure/types.ts';\nexport {\n\ttype Agent,\n\ttype AgentResponse,\n\ttype Callback,\n\ttype ProgressAgent,\n\ttype ProgressEvent,\n\ttype StepContext,\n\ttype RunOptions,\n} from './agent/domain/agent.ts';\nexport type { Transcript, TranscriptCallback } from './agent/domain/transcript.ts';\nexport { Inbox } from './agent/domain/inbox.ts';\nexport { RunInProgressError } from './agent/domain/oneRunAtATime.ts';\nexport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n} from './shared/domain/errors.ts';\nexport { isAbortError } from './shared/domain/isAbortError.ts';\nexport type { Session } from './shared/domain/session.ts';\nexport type { Tokens } from './shared/domain/tokens.ts';\nexport {\n\trememberApprovals,\n\ttype ApprovalDecision,\n\ttype ApprovalRequest,\n\ttype Approver,\n\ttype CommandLine,\n\ttype RememberableDecision,\n\ttype ToolRisk,\n\ttype WorktreeRemoval,\n} from './agent/domain/approval.ts';\nexport type { Asker, QuestionOption, UserQuestion } from './agent/domain/question.ts';\nexport type { JSONSchema, ToolDefinition } from './llm/domain/tool.ts';\nexport type { Tool, ToolSummarizer } from './tools/domain/tool.ts';\nexport type { TextMatch, Workspace } from './tools/domain/workspace.ts';\nexport { defineTool } from './tools/infrastructure/defineTool.ts';\nexport { createWorkspace } from './tools/infrastructure/createWorkspace.ts';\nexport { createWorkspaceTools } from './tools/infrastructure/workspaceTools.ts';\nexport { createHistory } from './recovery/infrastructure/createHistory.ts';\nexport { createWorktree } from './tools/infrastructure/worktrees.ts';\nexport type { History, HistoryRun, Move } from './recovery/domain/history.ts';\nexport {\n\thistoryStart,\n\tNothingToMoveError,\n\tWorkspaceBusyError,\n\tWorkspaceMovedError,\n\ttype MoveOptions,\n\ttype RunStatus,\n} from './recovery/domain/recoveryStore.ts';\nexport { HistoryExpiredError, UnknownRunError } from './recovery/domain/runTree.ts';\nexport { createWorkflow } from './factory/infrastructure/workflowFactory.ts';\nexport {\n\tdefineWorkflowStages,\n\ttype CreateWorkflowOptions,\n\ttype Workflow,\n\ttype WorkflowResult,\n\ttype WorkflowStage,\n\ttype WorkflowStageDefinition,\n\ttype WorkflowStageName,\n\ttype WorkflowStageReport,\n\ttype WorkflowTransition,\n} from './workflow/domain/workflow.ts';\nexport type { Validator } from './shared/domain/validator.ts';\n"]}
@@ -0,0 +1,26 @@
1
+ /** A tool result the agent may still need word for word, with the call it answers. */
2
+ export interface TriagedResult {
3
+ tool: string;
4
+ input: unknown;
5
+ output: string;
6
+ }
7
+ /**
8
+ * Judges which tool results an agent needs word for word once its context is compacted: Jev's
9
+ * decision, behind its own port, so a compactor works the same without it. Internal.
10
+ */
11
+ export interface ContextTriage {
12
+ /**
13
+ * How much the agent needs each of `results` word for word to go on from its latest answer,
14
+ * in their order: from 0, not again, to 3, word for word.
15
+ *
16
+ * @param task the user's request for the run.
17
+ * @param latestAnswer the model's latest answer, as text.
18
+ */
19
+ score(request: {
20
+ task: string;
21
+ latestAnswer: string;
22
+ results: TriagedResult[];
23
+ }, signal: AbortSignal): Promise<number[]>;
24
+ }
25
+ /** The score from which a tool result is kept word for word: its exact details matter. */
26
+ export declare const keptScore = 2;
@@ -0,0 +1,3 @@
1
+ /** The score from which a tool result is kept word for word: its exact details matter. */
2
+ export const keptScore = 2;
3
+ //# sourceMappingURL=contextTriage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextTriage.js","sourceRoot":"","sources":["../../../src/llm/domain/contextTriage.ts"],"names":[],"mappings":"AAyBA,0FAA0F;AAC1F,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC","sourcesContent":["/** A tool result the agent may still need word for word, with the call it answers. */\nexport interface TriagedResult {\n\ttool: string;\n\tinput: unknown;\n\toutput: string;\n}\n\n/**\n * Judges which tool results an agent needs word for word once its context is compacted: Jev's\n * decision, behind its own port, so a compactor works the same without it. Internal.\n */\nexport interface ContextTriage {\n\t/**\n\t * How much the agent needs each of `results` word for word to go on from its latest answer,\n\t * in their order: from 0, not again, to 3, word for word.\n\t *\n\t * @param task the user's request for the run.\n\t * @param latestAnswer the model's latest answer, as text.\n\t */\n\tscore(\n\t\trequest: { task: string; latestAnswer: string; results: TriagedResult[] },\n\t\tsignal: AbortSignal,\n\t): Promise<number[]>;\n}\n\n/** The score from which a tool result is kept word for word: its exact details matter. */\nexport const keptScore = 2;\n"]}
@@ -13,6 +13,11 @@ export interface LLMResponse {
13
13
  */
14
14
  usage: Tokens | undefined;
15
15
  stopReason: StopReason;
16
+ /**
17
+ * When the provider compacted the context in this call: the tokens of context it compacted,
18
+ * as the provider reported them. Missing when it did not compact, or did not report them.
19
+ */
20
+ compactedContextTokens?: number;
16
21
  }
17
22
  export interface LLMClient {
18
23
  /** The system prompt every call sends, for measuring what a call carries. */
@@ -24,8 +29,20 @@ export interface LLMClient {
24
29
  readonly providerDataSource?: string;
25
30
  /** Whether this client can declare a native tool. Missing means it declares none. */
26
31
  supportsNative?(tool: NativeTool): boolean;
27
- send({ context, tools }: {
32
+ /**
33
+ * Checks a threshold for `send`'s `compactAt`. Missing when the provider cannot compact.
34
+ *
35
+ * @throws {InvalidAgentConfigError} for a threshold the provider cannot take.
36
+ */
37
+ checkCompactAt?(compactAt: number): void;
38
+ /**
39
+ * @param compactAt the tokens of context at which the provider compacts it inside this call,
40
+ * answering with its summary first; `now` compacts it whatever its size, as far as the
41
+ * provider can. Without it, the call never compacts.
42
+ */
43
+ send({ context, tools, compactAt, }: {
28
44
  context: Message[];
29
45
  tools: ToolDefinition[];
46
+ compactAt?: number | 'now' | undefined;
30
47
  }, signal: AbortSignal): Promise<LLMResponse>;
31
48
  }
@@ -1 +1 @@
1
- {"version":3,"file":"llm.js","sourceRoot":"","sources":["../../../src/llm/domain/llm.ts"],"names":[],"mappings":"","sourcesContent":["import type { Tokens } from '#src/shared/domain/tokens';\nimport type { Message } from './message.ts';\nimport type { NativeTool, ToolDefinition } from './tool.ts';\n\nexport type StopReason = 'completed' | 'truncated' | 'refused';\n\nexport interface LLMResponse {\n\tmessage: Message & { role: 'assistant' };\n\t/**\n\t * Missing when the provider did not report it. The call was made and may have been billed,\n\t * so the agent keeps the answer and only its tokens become unknown; a failure raised from\n\t * such a response is marked `usageUnreported`.\n\t */\n\tusage: Tokens | undefined;\n\tstopReason: StopReason;\n}\n\nexport interface LLMClient {\n\t/** The system prompt every call sends, for measuring what a call carries. */\n\treadonly systemPrompt?: string;\n\t/**\n\t * The `source` of the `providerData` and `compaction` parts this client sends back; it drops\n\t * every other. For measuring what a call carries.\n\t */\n\treadonly providerDataSource?: string;\n\t/** Whether this client can declare a native tool. Missing means it declares none. */\n\tsupportsNative?(tool: NativeTool): boolean;\n\tsend(\n\t\t{ context, tools }: { context: Message[]; tools: ToolDefinition[] },\n\t\tsignal: AbortSignal,\n\t): Promise<LLMResponse>;\n}\n"]}
1
+ {"version":3,"file":"llm.js","sourceRoot":"","sources":["../../../src/llm/domain/llm.ts"],"names":[],"mappings":"","sourcesContent":["import type { Tokens } from '#src/shared/domain/tokens';\nimport type { Message } from './message.ts';\nimport type { NativeTool, ToolDefinition } from './tool.ts';\n\nexport type StopReason = 'completed' | 'truncated' | 'refused';\n\nexport interface LLMResponse {\n\tmessage: Message & { role: 'assistant' };\n\t/**\n\t * Missing when the provider did not report it. The call was made and may have been billed,\n\t * so the agent keeps the answer and only its tokens become unknown; a failure raised from\n\t * such a response is marked `usageUnreported`.\n\t */\n\tusage: Tokens | undefined;\n\tstopReason: StopReason;\n\t/**\n\t * When the provider compacted the context in this call: the tokens of context it compacted,\n\t * as the provider reported them. Missing when it did not compact, or did not report them.\n\t */\n\tcompactedContextTokens?: number;\n}\n\nexport interface LLMClient {\n\t/** The system prompt every call sends, for measuring what a call carries. */\n\treadonly systemPrompt?: string;\n\t/**\n\t * The `source` of the `providerData` and `compaction` parts this client sends back; it drops\n\t * every other. For measuring what a call carries.\n\t */\n\treadonly providerDataSource?: string;\n\t/** Whether this client can declare a native tool. Missing means it declares none. */\n\tsupportsNative?(tool: NativeTool): boolean;\n\t/**\n\t * Checks a threshold for `send`'s `compactAt`. Missing when the provider cannot compact.\n\t *\n\t * @throws {InvalidAgentConfigError} for a threshold the provider cannot take.\n\t */\n\tcheckCompactAt?(compactAt: number): void;\n\t/**\n\t * @param compactAt the tokens of context at which the provider compacts it inside this call,\n\t * answering with its summary first; `now` compacts it whatever its size, as far as the\n\t * provider can. Without it, the call never compacts.\n\t */\n\tsend(\n\t\t{\n\t\t\tcontext,\n\t\t\ttools,\n\t\t\tcompactAt,\n\t\t}: { context: Message[]; tools: ToolDefinition[]; compactAt?: number | 'now' | undefined },\n\t\tsignal: AbortSignal,\n\t): Promise<LLMResponse>;\n}\n"]}
@@ -1,4 +1,4 @@
1
- export type MessagePart = TextPart | ReasoningPart | ToolCallPart | ToolResultPart | ProviderDataPart | CompactionPart;
1
+ export type MessagePart = TextPart | ReasoningPart | ToolCallPart | ToolResultPart | ProviderDataPart | CompactionPart | ContextSummaryPart;
2
2
  export interface TextPart {
3
3
  type: 'text';
4
4
  text: string;
@@ -42,6 +42,17 @@ export interface CompactionPart {
42
42
  source: string;
43
43
  data: unknown;
44
44
  }
45
+ /**
46
+ * The harness's own summary of the context before it, written once the context passed the
47
+ * agent's threshold, with a copy of each message it kept word for word. Unlike a `CompactionPart`
48
+ * it belongs to no provider: the context every client sends is derived from the latest one on
49
+ * (`compactedContext`), and the conversation still holds every part it replaced.
50
+ */
51
+ export interface ContextSummaryPart {
52
+ type: 'contextSummary';
53
+ summary: string;
54
+ kept: Message[];
55
+ }
45
56
  export interface Message {
46
57
  role: 'user' | 'assistant' | 'tool';
47
58
  content: MessagePart[];
@@ -51,3 +62,9 @@ export interface Message {
51
62
  * on, that compaction included, or the whole context when it has none.
52
63
  */
53
64
  export declare function sentContext(context: Message[], source: string | undefined): Message[];
65
+ /**
66
+ * `context` from its latest `ContextSummaryPart` on: the summary, then the messages it kept word
67
+ * for word, then everything after it. The whole context when it has none. Messages after the
68
+ * summary are the same objects, not copies.
69
+ */
70
+ export declare function compactedContext(context: Message[]): Message[];
@@ -12,4 +12,24 @@ export function sentContext(context, source) {
12
12
  }
13
13
  return context;
14
14
  }
15
+ /** How the model is told that a summary stands for what came before it. */
16
+ const summaryHeading = 'The earlier part of this conversation was compacted into this summary, which you wrote:';
17
+ /**
18
+ * `context` from its latest `ContextSummaryPart` on: the summary, then the messages it kept word
19
+ * for word, then everything after it. The whole context when it has none. Messages after the
20
+ * summary are the same objects, not copies.
21
+ */
22
+ export function compactedContext(context) {
23
+ const at = context.findLastIndex(message => message.content.some(part => part.type === 'contextSummary'));
24
+ const part = context[at]?.content.findLast(part => part.type === 'contextSummary');
25
+ if (!part)
26
+ return context;
27
+ const summary = { type: 'text', text: `${summaryHeading}\n\n${part.summary}` };
28
+ const [first, ...kept] = part.kept;
29
+ // One user message, not two in a row, when what was kept starts with the user's.
30
+ const opening = first?.role === 'user'
31
+ ? [{ role: 'user', content: [summary, ...first.content] }]
32
+ : [{ role: 'user', content: [summary] }, ...(first ? [first] : [])];
33
+ return [...opening, ...kept, ...context.slice(at + 1)];
34
+ }
15
35
  //# sourceMappingURL=message.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/llm/domain/message.ts"],"names":[],"mappings":"AAyDA;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAAkB,EAAE,MAA0B;IACzE,KAAK,IAAI,KAAK,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;QAC1D,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC/B,MAAM,EAAE,GACP,OAAO,EAAE,OAAO,CAAC,aAAa,CAC7B,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAC5D,IAAI,CAAC,CAAC,CAAC;QACT,IAAI,OAAO,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;QAC1F,CAAC;IACF,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC","sourcesContent":["export type MessagePart =\n\tTextPart | ReasoningPart | ToolCallPart | ToolResultPart | ProviderDataPart | CompactionPart;\n\nexport interface TextPart {\n\ttype: 'text';\n\ttext: string;\n}\n\nexport interface ReasoningPart {\n\ttype: 'reasoning';\n\ttext: string;\n}\n\nexport interface ToolCallPart {\n\tid: string;\n\ttype: 'toolCall';\n\tname: string;\n\tinput: unknown;\n}\n\nexport interface ToolResultPart {\n\tcallId: string;\n\ttype: 'toolResult';\n\tname: string;\n\toutput: string;\n\tisError: boolean;\n}\n\n/**\n * A block only the client that produced it can read back, such as signed or encrypted\n * reasoning, kept whole so that client can send it again on the next call. `source` is the\n * client's own label and the domain gives it no meaning: every other client leaves the part out.\n * `data` must stay plain JSON, since the conversation is copied and will be persisted.\n */\nexport interface ProviderDataPart {\n\ttype: 'providerData';\n\tsource: string;\n\tdata: unknown;\n}\n\n/**\n * A provider's own summary of everything before it, written when its context passed the\n * client's `compactAt`. Like `ProviderDataPart`, only the client labelled `source` reads it\n * back: that client sends the conversation from its latest compaction on, and every other client\n * leaves the part out and sends the conversation whole, which the conversation still holds.\n */\nexport interface CompactionPart {\n\ttype: 'compaction';\n\tsource: string;\n\tdata: unknown;\n}\n\nexport interface Message {\n\trole: 'user' | 'assistant' | 'tool';\n\tcontent: MessagePart[];\n}\n\n/**\n * What the client labelled `source` sends of `context`: everything from its latest compaction\n * on, that compaction included, or the whole context when it has none.\n */\nexport function sentContext(context: Message[], source: string | undefined): Message[] {\n\tfor (let index = context.length - 1; index >= 0; index--) {\n\t\tconst message = context[index];\n\t\tconst at =\n\t\t\tmessage?.content.findLastIndex(\n\t\t\t\tpart => part.type === 'compaction' && part.source === source,\n\t\t\t) ?? -1;\n\t\tif (message && at !== -1) {\n\t\t\treturn [{ ...message, content: message.content.slice(at) }, ...context.slice(index + 1)];\n\t\t}\n\t}\n\treturn context;\n}\n"]}
1
+ {"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/llm/domain/message.ts"],"names":[],"mappings":"AA2EA;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAAkB,EAAE,MAA0B;IACzE,KAAK,IAAI,KAAK,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;QAC1D,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC/B,MAAM,EAAE,GACP,OAAO,EAAE,OAAO,CAAC,aAAa,CAC7B,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAC5D,IAAI,CAAC,CAAC,CAAC;QACT,IAAI,OAAO,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;QAC1F,CAAC;IACF,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,2EAA2E;AAC3E,MAAM,cAAc,GACnB,yFAAyF,CAAC;AAE3F;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAkB;IAClD,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAC1C,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,gBAAgB,CAAC,CAC5D,CAAC;IACF,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,gBAAgB,CAAC,CAAC;IACnF,IAAI,CAAC,IAAI;QAAE,OAAO,OAAO,CAAC;IAC1B,MAAM,OAAO,GAAa,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,cAAc,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;IACzF,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;IACnC,iFAAiF;IACjF,MAAM,OAAO,GACZ,KAAK,EAAE,IAAI,KAAK,MAAM;QACrB,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1D,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,GAAG,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;AACxD,CAAC","sourcesContent":["export type MessagePart =\n\t| TextPart\n\t| ReasoningPart\n\t| ToolCallPart\n\t| ToolResultPart\n\t| ProviderDataPart\n\t| CompactionPart\n\t| ContextSummaryPart;\n\nexport interface TextPart {\n\ttype: 'text';\n\ttext: string;\n}\n\nexport interface ReasoningPart {\n\ttype: 'reasoning';\n\ttext: string;\n}\n\nexport interface ToolCallPart {\n\tid: string;\n\ttype: 'toolCall';\n\tname: string;\n\tinput: unknown;\n}\n\nexport interface ToolResultPart {\n\tcallId: string;\n\ttype: 'toolResult';\n\tname: string;\n\toutput: string;\n\tisError: boolean;\n}\n\n/**\n * A block only the client that produced it can read back, such as signed or encrypted\n * reasoning, kept whole so that client can send it again on the next call. `source` is the\n * client's own label and the domain gives it no meaning: every other client leaves the part out.\n * `data` must stay plain JSON, since the conversation is copied and will be persisted.\n */\nexport interface ProviderDataPart {\n\ttype: 'providerData';\n\tsource: string;\n\tdata: unknown;\n}\n\n/**\n * A provider's own summary of everything before it, written when its context passed the\n * client's `compactAt`. Like `ProviderDataPart`, only the client labelled `source` reads it\n * back: that client sends the conversation from its latest compaction on, and every other client\n * leaves the part out and sends the conversation whole, which the conversation still holds.\n */\nexport interface CompactionPart {\n\ttype: 'compaction';\n\tsource: string;\n\tdata: unknown;\n}\n\n/**\n * The harness's own summary of the context before it, written once the context passed the\n * agent's threshold, with a copy of each message it kept word for word. Unlike a `CompactionPart`\n * it belongs to no provider: the context every client sends is derived from the latest one on\n * (`compactedContext`), and the conversation still holds every part it replaced.\n */\nexport interface ContextSummaryPart {\n\ttype: 'contextSummary';\n\tsummary: string;\n\tkept: Message[];\n}\n\nexport interface Message {\n\trole: 'user' | 'assistant' | 'tool';\n\tcontent: MessagePart[];\n}\n\n/**\n * What the client labelled `source` sends of `context`: everything from its latest compaction\n * on, that compaction included, or the whole context when it has none.\n */\nexport function sentContext(context: Message[], source: string | undefined): Message[] {\n\tfor (let index = context.length - 1; index >= 0; index--) {\n\t\tconst message = context[index];\n\t\tconst at =\n\t\t\tmessage?.content.findLastIndex(\n\t\t\t\tpart => part.type === 'compaction' && part.source === source,\n\t\t\t) ?? -1;\n\t\tif (message && at !== -1) {\n\t\t\treturn [{ ...message, content: message.content.slice(at) }, ...context.slice(index + 1)];\n\t\t}\n\t}\n\treturn context;\n}\n\n/** How the model is told that a summary stands for what came before it. */\nconst summaryHeading =\n\t'The earlier part of this conversation was compacted into this summary, which you wrote:';\n\n/**\n * `context` from its latest `ContextSummaryPart` on: the summary, then the messages it kept word\n * for word, then everything after it. The whole context when it has none. Messages after the\n * summary are the same objects, not copies.\n */\nexport function compactedContext(context: Message[]): Message[] {\n\tconst at = context.findLastIndex(message =>\n\t\tmessage.content.some(part => part.type === 'contextSummary'),\n\t);\n\tconst part = context[at]?.content.findLast(part => part.type === 'contextSummary');\n\tif (!part) return context;\n\tconst summary: TextPart = { type: 'text', text: `${summaryHeading}\\n\\n${part.summary}` };\n\tconst [first, ...kept] = part.kept;\n\t// One user message, not two in a row, when what was kept starts with the user's.\n\tconst opening: Message[] =\n\t\tfirst?.role === 'user'\n\t\t\t? [{ role: 'user', content: [summary, ...first.content] }]\n\t\t\t: [{ role: 'user', content: [summary] }, ...(first ? [first] : [])];\n\treturn [...opening, ...kept, ...context.slice(at + 1)];\n}\n"]}
@@ -14,21 +14,20 @@ export declare class ClaudeLLMClient implements LLMClient {
14
14
  private readonly client;
15
15
  private readonly model;
16
16
  private readonly reasoningEffort;
17
- private readonly compactAt;
18
17
  readonly systemPrompt: string;
19
18
  readonly providerDataSource = "claude";
20
19
  private readonly logger;
21
- constructor({ model, reasoningEffort, compactAt, systemPrompt, logger, }: {
20
+ constructor({ model, reasoningEffort, systemPrompt, logger, }: {
22
21
  model: string;
23
22
  reasoningEffort?: string;
24
- /** The input tokens at which Anthropic compacts the context on its side. Without it, never. */
25
- compactAt?: number | undefined;
26
23
  systemPrompt: string;
27
24
  logger: ILogger;
28
25
  });
29
26
  supportsNative(tool: NativeTool): boolean;
30
- send({ context, tools }: {
27
+ checkCompactAt(compactAt: number): void;
28
+ send({ context, tools, compactAt, }: {
31
29
  context: Message[];
32
30
  tools: ToolDefinition[];
31
+ compactAt?: number | 'now' | undefined;
33
32
  }, signal: AbortSignal): Promise<LLMResponse>;
34
33
  }
@@ -73,6 +73,9 @@ function toClaudeBlocks(part) {
73
73
  case 'reasoning':
74
74
  // Unsigned text cannot be replayed as thinking. The API accepts a turn without it.
75
75
  return [];
76
+ case 'contextSummary':
77
+ // Never sent: the agent sends the context derived from it.
78
+ return [];
76
79
  }
77
80
  }
78
81
  /**
@@ -202,6 +205,20 @@ function toTokens(usage, logger) {
202
205
  outputTokens: usage.output_tokens,
203
206
  };
204
207
  }
208
+ /**
209
+ * How many tokens of context the call compacted, when it did: what its compaction iteration read,
210
+ * since Anthropic bills a compaction as a call of its own over the context before it.
211
+ */
212
+ function compactedContext(usage) {
213
+ const compaction = usage?.iterations?.find(iteration => iteration.type === 'compaction');
214
+ if (!compaction)
215
+ return {};
216
+ return {
217
+ compactedContextTokens: compaction.input_tokens +
218
+ compaction.cache_read_input_tokens +
219
+ compaction.cache_creation_input_tokens,
220
+ };
221
+ }
205
222
  /**
206
223
  * @param summaries each compaction's summary as it was streamed, by the index of its block. The
207
224
  * SDK joins a delta that carries none as the text "null", so the final message cannot tell.
@@ -223,6 +240,7 @@ function toLLMResponse(response, logger, summaries) {
223
240
  },
224
241
  usage,
225
242
  stopReason: toStopReason(response),
243
+ ...compactedContext(response.usage),
226
244
  };
227
245
  }
228
246
  catch (error) {
@@ -280,13 +298,12 @@ export class ClaudeLLMClient {
280
298
  client;
281
299
  model;
282
300
  reasoningEffort;
283
- compactAt;
284
301
  systemPrompt;
285
302
  providerDataSource = claudeSource;
286
303
  logger;
287
304
  // `model` and `reasoningEffort` are untyped on purpose: this adapter is the one that knows
288
305
  // what Claude supports.
289
- constructor({ model, reasoningEffort, compactAt, systemPrompt, logger, }) {
306
+ constructor({ model, reasoningEffort, systemPrompt, logger, }) {
290
307
  if (!isOneOf(claudeLLMModels, model)) {
291
308
  throw new InvalidAgentConfigError(`"${model}" is not a Claude API model; expected one of: ${claudeLLMModels.join(', ')}`);
292
309
  }
@@ -294,9 +311,6 @@ export class ClaudeLLMClient {
294
311
  if (!isOneOf(claudeLLMReasoningEfforts, effort)) {
295
312
  throw new InvalidAgentConfigError(`"${effort}" is not a Claude API reasoning effort; expected one of: ${claudeLLMReasoningEfforts.join(', ')}`);
296
313
  }
297
- if (compactAt !== undefined && !(Number.isInteger(compactAt) && compactAt >= minCompactAt)) {
298
- throw new InvalidAgentConfigError(`compactAt must be a whole number of tokens of at least ${String(minCompactAt)} on Claude, not ${String(compactAt)}`);
299
- }
300
314
  try {
301
315
  // The SDK's default retries stay on, as OpenAI's do. See "The provider SDKs keep their
302
316
  // transport retries under RetryingAgent" in decisions.md.
@@ -311,14 +325,27 @@ export class ClaudeLLMClient {
311
325
  }
312
326
  this.model = claudeModelIds[model];
313
327
  this.reasoningEffort = effort;
314
- this.compactAt = compactAt;
315
328
  this.systemPrompt = systemPrompt;
316
329
  this.logger = logger;
317
330
  }
318
331
  supportsNative(tool) {
319
332
  return tool === 'textEditor';
320
333
  }
321
- async send({ context, tools }, signal) {
334
+ checkCompactAt(compactAt) {
335
+ if (!(Number.isInteger(compactAt) && compactAt >= minCompactAt)) {
336
+ throw new InvalidAgentConfigError(`compactAt must be a whole number of tokens of at least ${String(minCompactAt)} on Claude, not ${String(compactAt)}`);
337
+ }
338
+ }
339
+ async send({ context, tools, compactAt, }, signal) {
340
+ // Compacting now is compacting at the least threshold Anthropic takes, which a context
341
+ // too large for the model has long passed.
342
+ const trigger = compactAt === 'now' ? minCompactAt : compactAt;
343
+ // The latest compaction carries everything before it, which Anthropic would only ignore.
344
+ const messages = sentContext(context, claudeSource);
345
+ // A compaction sent back needs the beta as much as one asked for, even with compaction off,
346
+ // as after a context that no longer fit was compacted once.
347
+ const first = messages[0]?.content[0];
348
+ const compacted = trigger !== undefined || (first?.type === 'compaction' && first.source === claudeSource);
322
349
  let response;
323
350
  let stream;
324
351
  // What each compaction streamed of its summary, by block index, its missing parts left out.
@@ -331,11 +358,10 @@ export class ClaudeLLMClient {
331
358
  // A compaction changes the conversation's first block, so the cache would otherwise
332
359
  // keep nothing: this keeps the system prompt cached across it. The API rejects an
333
360
  // empty text block, and an empty prompt has nothing to cache.
334
- system: this.compactAt === undefined || this.systemPrompt === ''
361
+ system: !compacted || this.systemPrompt === ''
335
362
  ? this.systemPrompt
336
363
  : [{ type: 'text', text: this.systemPrompt, cache_control: { type: 'ephemeral' } }],
337
- // The latest compaction carries everything before it, which Anthropic would only ignore.
338
- messages: sentContext(context, claudeSource).flatMap(toClaudeInput),
364
+ messages: messages.flatMap(toClaudeInput),
339
365
  // Left out when empty, so a request without tools stays as it always was.
340
366
  ...(tools.length > 0 && { tools: tools.map(toClaudeTool) }),
341
367
  thinking: { type: 'adaptive', display: 'summarized' },
@@ -343,14 +369,14 @@ export class ClaudeLLMClient {
343
369
  // Anthropic caches only on request. This marks the last block, so the next call
344
370
  // reads everything before it from the cache instead of paying for it again.
345
371
  cache_control: { type: 'ephemeral' },
372
+ ...(compacted && { betas: [compactionBeta] }),
346
373
  // Anthropic compacts inside the call that passes it, and answers with the compaction first.
347
- ...(this.compactAt !== undefined && {
348
- betas: [compactionBeta],
374
+ ...(trigger !== undefined && {
349
375
  context_management: {
350
376
  edits: [
351
377
  {
352
378
  type: 'compact_20260112',
353
- trigger: { type: 'input_tokens', value: this.compactAt },
379
+ trigger: { type: 'input_tokens', value: trigger },
354
380
  },
355
381
  ],
356
382
  },
@@ -1 +1 @@
1
- {"version":3,"file":"claudeLLMClient.js","sourceRoot":"","sources":["../../../src/llm/infrastructure/claudeLLMClient.ts"],"names":[],"mappings":"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","sourcesContent":["import Anthropic, { AnthropicError, APIError } from '@anthropic-ai/sdk';\nimport type { BetaMessageStream } from '@anthropic-ai/sdk/lib/BetaMessageStream';\n// The beta surface, for compaction: without its beta header, a request is the stable one.\nimport type {\n\tBetaCompactionBlockParam as CompactionBlockParam,\n\tBetaContentBlock as ContentBlock,\n\tBetaContentBlockParam as ContentBlockParam,\n\tBetaMessage as AnthropicMessage,\n\tBetaMessageParam as MessageParam,\n\tBetaOutputConfig as OutputConfig,\n\tBetaRedactedThinkingBlockParam as RedactedThinkingBlockParam,\n\tBetaThinkingBlockParam as ThinkingBlockParam,\n\tBetaTool as ClaudeTool,\n\tBetaToolTextEditor20250728 as ToolTextEditor20250728,\n\tBetaUsage as Usage,\n} from '@anthropic-ai/sdk/resources/beta/messages/messages';\nimport {\n\tInvalidAgentConfigError,\n\tUnrecoverableError,\n\twithSpentTokens,\n} from '#src/shared/domain/errors';\nimport {\n\tclassifyHostFailure,\n\tclassifyLocalFailure,\n\tclassifyProviderFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/shared/domain/providerFailure';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport type { Tokens } from '#src/shared/domain/tokens';\nimport { MaxContextError } from '../domain/errors.ts';\nimport type { LLMClient, LLMResponse, StopReason } from '../domain/llm.ts';\nimport { type Message, type MessagePart, sentContext } from '../domain/message.ts';\nimport { type NativeTool, nativeOf, type ToolDefinition } from '../domain/tool.ts';\nimport { claudeModelIds } from './claudeModelIds.ts';\n\n// The Messages API takes full model IDs; `claudeModelIds` pins the one each alias names.\nexport const claudeLLMModels = [\n\t'opus',\n\t'fable',\n\t'sonnet',\n\t'haiku',\n] as const satisfies readonly (keyof typeof claudeModelIds)[];\nexport type ClaudeLLMModel = (typeof claudeLLMModels)[number];\n\nexport const claudeLLMReasoningEfforts = [\n\t'low',\n\t'medium',\n\t'high',\n\t'xhigh',\n\t'max',\n] as const satisfies readonly NonNullable<OutputConfig['effort']>[];\nexport type ClaudeLLMReasoningEffort = (typeof claudeLLMReasoningEfforts)[number];\n\n// Adaptive thinking spends from this budget too, so a hard step at a high effort can need most\n// of it. The request is streamed so it is not bound by the SDK's ten-minute timeout, which made\n// it refuse more than about 21,000 without a stream. Every model the aliases name writes up to\n// 128,000; Haiku 5.5 at `xhigh` once spent 64,000 on one answer.\nconst maxOutputTokens = 128_000;\n\n// The request itself is wrong or not allowed, so sending it again cannot succeed.\nconst unrecoverableStatuses = [400, 401, 403, 404, 413, 422];\n\n// The account is out of credit, or its key cannot make the request. Resending cannot fix\n// either, whatever status the error arrives with.\nconst unrecoverableErrorTypes = ['billing_error', 'authentication_error', 'permission_error'];\n\n// This client's label on the thinking blocks it keeps for replay; see `ProviderDataPart`.\nconst claudeSource = 'claude';\n\n// Anthropic's compaction at a token threshold, and the least threshold it accepts.\nconst compactionBeta = 'compact-2026-01-12';\nconst minCompactAt = 50_000;\n\n/**\n * Anthropic rejects a `tool_use` whose input is not an object, with a 400 on every later call.\n * Another provider's call can hold anything else, such as OpenAI arguments that did not parse;\n * its result already told the model the call failed, so sending it as empty loses nothing.\n */\nfunction toClaudeInputObject(input: unknown): unknown {\n\treturn typeof input === 'object' && input !== null && !Array.isArray(input) ? input : {};\n}\n\n/** Thinking crosses back only as the signed block this client kept, never as its text. */\nfunction toClaudeBlocks(part: MessagePart): ContentBlockParam[] {\n\tswitch (part.type) {\n\t\tcase 'text':\n\t\t\t// The API rejects an empty text block.\n\t\t\treturn part.text ? [{ type: 'text', text: part.text }] : [];\n\t\tcase 'toolCall':\n\t\t\treturn [\n\t\t\t\t{ type: 'tool_use', id: part.id, name: part.name, input: toClaudeInputObject(part.input) },\n\t\t\t];\n\t\tcase 'toolResult':\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'tool_result',\n\t\t\t\t\ttool_use_id: part.callId,\n\t\t\t\t\tcontent: part.output,\n\t\t\t\t\tis_error: part.isError,\n\t\t\t\t},\n\t\t\t];\n\t\tcase 'providerData':\n\t\t\t// Another provider's block would be rejected; losing it only costs that provider's reasoning.\n\t\t\treturn part.source === claudeSource\n\t\t\t\t? [part.data as ThinkingBlockParam | RedactedThinkingBlockParam]\n\t\t\t\t: [];\n\t\tcase 'compaction':\n\t\t\t// Another provider's summary means nothing here; this client sends the turns it replaced.\n\t\t\treturn part.source === claudeSource ? [part.data as CompactionBlockParam] : [];\n\t\tcase 'reasoning':\n\t\t\t// Unsigned text cannot be replayed as thinking. The API accepts a turn without it.\n\t\t\treturn [];\n\t}\n}\n\n/**\n * Tool results travel in a user turn, as the API requires. A message left with no blocks is\n * left out whole; the API joins the consecutive user turns that leaves.\n */\nfunction toClaudeInput(message: Message): MessageParam[] {\n\tconst content = message.content.flatMap(toClaudeBlocks);\n\tif (content.length === 0) {\n\t\treturn [];\n\t}\n\n\treturn [{ role: message.role === 'tool' ? 'user' : message.role, content }];\n}\n\n// Anthropic fixes the text editor's name: its calls arrive under it.\nconst textEditorName = 'str_replace_based_edit_tool';\n\n/**\n * Strict: the API constrains the model to the schema, so its input always parses and matches.\n * A schema outside what strict mode supports fails the request with a 400. The native text\n * editor has Anthropic's own schema, and its calls and results are ordinary tool blocks.\n */\nfunction toClaudeTool(definition: ToolDefinition): ClaudeTool | ToolTextEditor20250728 {\n\tconst native = nativeOf(definition);\n\tif (native === 'textEditor') {\n\t\tif (definition.name !== textEditorName) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`Claude's text editor must be named \"${textEditorName}\", not \"${definition.name}\"`,\n\t\t\t);\n\t\t}\n\t\treturn { type: 'text_editor_20250728', name: textEditorName };\n\t}\n\tif (native !== undefined) {\n\t\tthrow new InvalidAgentConfigError(`Claude has no native tool \"${native}\"`);\n\t}\n\tconst { name, description, inputSchema } = definition;\n\treturn { name, description, input_schema: inputSchema, strict: true };\n}\n\nfunction describeBlock(block: ContentBlock, logger: ILogger): MessagePart[] {\n\tswitch (block.type) {\n\t\tcase 'text':\n\t\t\treturn [{ type: 'text', text: block.text }];\n\t\tcase 'thinking': {\n\t\t\t// Kept signed for replay; the summarized text is only for narration.\n\t\t\tconst signed: ThinkingBlockParam = {\n\t\t\t\ttype: 'thinking',\n\t\t\t\tthinking: block.thinking,\n\t\t\t\tsignature: block.signature,\n\t\t\t};\n\t\t\tconst replay: MessagePart = { type: 'providerData', source: claudeSource, data: signed };\n\t\t\treturn block.thinking ? [{ type: 'reasoning', text: block.thinking }, replay] : [replay];\n\t\t}\n\t\tcase 'redacted_thinking': {\n\t\t\t// Encrypted by design: nothing to narrate, but it still goes back.\n\t\t\tconst redacted: RedactedThinkingBlockParam = { type: 'redacted_thinking', data: block.data };\n\t\t\treturn [{ type: 'providerData', source: claudeSource, data: redacted }];\n\t\t}\n\t\tcase 'tool_use':\n\t\t\treturn [{ type: 'toolCall', id: block.id, name: block.name, input: block.input }];\n\t\tcase 'compaction': {\n\t\t\t// A compaction that failed, with no summary. Claude reads one sent back as if it were not\n\t\t\t// there, so it is left out: the context goes on whole, and Claude compacts again later.\n\t\t\tif (!block.content) {\n\t\t\t\ttreatErrors(\n\t\t\t\t\t() => {\n\t\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t\t'Claude could not compact the conversation; it is sent whole until Claude compacts it',\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\tclassifyHostFailure,\n\t\t\t\t\t'Claude client logger failed while mapping a response',\n\t\t\t\t);\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\t// Claude's summary of the context before it, readable, with what it alone reads back.\n\t\t\tconst compaction: CompactionBlockParam = {\n\t\t\t\ttype: 'compaction',\n\t\t\t\tcontent: block.content,\n\t\t\t\tencrypted_content: block.encrypted_content,\n\t\t\t};\n\t\t\treturn [{ type: 'compaction', source: claudeSource, data: compaction }];\n\t\t}\n\t\tdefault:\n\t\t\t// Server tools are never offered, so any other block is output this client does not expect.\n\t\t\ttreatErrors(\n\t\t\t\t() => {\n\t\t\t\t\tlogger.warn(block, 'Claude returned a content block the client does not map');\n\t\t\t\t},\n\t\t\t\tclassifyHostFailure,\n\t\t\t\t'Claude client logger failed while mapping a response',\n\t\t\t);\n\t\t\treturn [];\n\t}\n}\n\nfunction toStopReason(response: AnthropicMessage): StopReason {\n\tswitch (response.stop_reason) {\n\t\t// `tool_use` is a complete answer that asks for tools: running them is the agent's call.\n\t\tcase 'end_turn':\n\t\tcase 'tool_use':\n\t\t\treturn 'completed';\n\t\tcase 'max_tokens':\n\t\t\treturn 'truncated';\n\t\tcase 'refusal':\n\t\t\treturn 'refused';\n\t\tcase 'model_context_window_exceeded':\n\t\t\tthrow new MaxContextError('The context no longer fits the Claude model', {\n\t\t\t\tcause: 'The response stopped at the model context window.',\n\t\t\t});\n\t\tdefault:\n\t\t\t// `pause_turn` and `stop_sequence` need server tools or stop sequences, and this\n\t\t\t// client sends neither.\n\t\t\tthrow new UnrecoverableError('The Claude response stopped unexpectedly', {\n\t\t\t\tcause: `The response stopped with \"${response.stop_reason ?? 'unknown'}\".`,\n\t\t\t});\n\t}\n}\n\n/**\n * Unlike OpenAI, Anthropic counts cache reads and writes apart from `input_tokens`. A call that\n * compacted leaves the compaction out of its totals, so with iterations their sum is the bill.\n */\nfunction toTokens(usage: Usage | undefined, logger: ILogger): Tokens | undefined {\n\tif (!usage) {\n\t\t// Zeros would report a call that may have been billed as free.\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tlogger.warn('Claude returned a response without usage; it cannot be accounted for');\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Claude client logger failed while reading usage',\n\t\t);\n\t\treturn undefined;\n\t}\n\n\tif (usage.iterations && usage.iterations.length > 0) {\n\t\treturn usage.iterations.reduce<Tokens>(\n\t\t\t(sum, iteration) => ({\n\t\t\t\tinputTokens: sum.inputTokens + iteration.input_tokens,\n\t\t\t\treadCacheTokens: sum.readCacheTokens + iteration.cache_read_input_tokens,\n\t\t\t\twrittenCacheTokens: sum.writtenCacheTokens + iteration.cache_creation_input_tokens,\n\t\t\t\toutputTokens: sum.outputTokens + iteration.output_tokens,\n\t\t\t}),\n\t\t\t{ inputTokens: 0, readCacheTokens: 0, writtenCacheTokens: 0, outputTokens: 0 },\n\t\t);\n\t}\n\treturn {\n\t\tinputTokens: usage.input_tokens,\n\t\treadCacheTokens: usage.cache_read_input_tokens ?? 0,\n\t\twrittenCacheTokens: usage.cache_creation_input_tokens ?? 0,\n\t\toutputTokens: usage.output_tokens,\n\t};\n}\n\n/**\n * @param summaries each compaction's summary as it was streamed, by the index of its block. The\n * SDK joins a delta that carries none as the text \"null\", so the final message cannot tell.\n */\nfunction toLLMResponse(\n\tresponse: AnthropicMessage,\n\tlogger: ILogger,\n\tsummaries: ReadonlyMap<number, string>,\n): LLMResponse {\n\tlet usage: Tokens | undefined;\n\n\ttry {\n\t\t// Inside the try: it warns when the usage is missing, and that warning can throw too.\n\t\tusage = toTokens(response.usage, logger);\n\t\treturn {\n\t\t\tmessage: {\n\t\t\t\trole: 'assistant',\n\t\t\t\tcontent: response.content.flatMap((block, index) => {\n\t\t\t\t\tconst summary = summaries.get(index);\n\t\t\t\t\treturn describeBlock(\n\t\t\t\t\t\tblock.type === 'compaction' && summary !== undefined\n\t\t\t\t\t\t\t? { ...block, content: summary }\n\t\t\t\t\t\t\t: block,\n\t\t\t\t\t\tlogger,\n\t\t\t\t\t);\n\t\t\t\t}),\n\t\t\t},\n\t\t\tusage,\n\t\t\tstopReason: toStopReason(response),\n\t\t};\n\t} catch (error) {\n\t\t// The call was billed whether the response failed or a logger did while mapping it, and\n\t\t// an answer without usage leaves the run's count unknown.\n\t\tthrow withSpentTokens(error, usage, !response.usage);\n\t}\n}\n\n/**\n * The SDK resolves credentials lazily, so a missing key surfaces on the first call, as a plain\n * `Error` the SDK gives no class of its own. Before the stream connects, an `AnthropicError` that\n * is not an `APIError` never carries an answer from the API: it is the SDK refusing to make the\n * call, such as an unresolved profile. Once it connects, the stream reports any failure as one,\n * such as a connection lost halfway; see `classifyClaudeFailure`.\n */\nfunction isLocalSDKFailure(error: unknown): boolean {\n\treturn (\n\t\t(error instanceof AnthropicError && !(error instanceof APIError)) ||\n\t\t(error instanceof Error && error.message.startsWith('Could not resolve authentication method'))\n\t);\n}\n\n/** `connected` is whether the stream had its response: a failure after that broke the stream off. */\nfunction classifyClaudeFailure(error: unknown, connected: boolean): Error {\n\tif (connected && !(error instanceof APIError)) {\n\t\t// The API was answering, so a later attempt can succeed. An error event the API sends\n\t\t// mid-stream, such as an overload, arrives as an `APIError` and is classified below.\n\t\treturn classifyProviderFailure(error, 'The Claude stream broke off');\n\t}\n\tif (isLocalSDKFailure(error)) {\n\t\t// The API never answered, and calling again would fail the same way.\n\t\treturn new UnrecoverableError('The Claude client cannot send the request', {\n\t\t\tcause: describeFailure(error),\n\t\t});\n\t}\n\tif (error instanceof APIError) {\n\t\t// Anthropic gives an overlong prompt no code of its own, only a 400 with this message.\n\t\tif (error.status === 400 && error.message.includes('prompt is too long')) {\n\t\t\treturn new MaxContextError('The context no longer fits the Claude model', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t\tif (\n\t\t\tunrecoverableErrorTypes.some(type => type === error.type) ||\n\t\t\tunrecoverableStatuses.some(status => status === error.status)\n\t\t) {\n\t\t\treturn new UnrecoverableError('Claude rejected the request', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t}\n\n\t// A rate limit, an overload (529), a 5xx or a network failure: a later attempt can succeed.\n\treturn classifyProviderFailure(error, 'The Claude request failed');\n}\n\n/**\n * A stateless `LLMClient` on the Anthropic Messages API, which keeps nothing between calls:\n * every call carries the whole context, so the conversation is MiKode's, not the provider's.\n */\nexport class ClaudeLLMClient implements LLMClient {\n\tprivate readonly client: Anthropic;\n\tprivate readonly model: (typeof claudeModelIds)[ClaudeLLMModel];\n\tprivate readonly reasoningEffort: ClaudeLLMReasoningEffort;\n\tprivate readonly compactAt: number | undefined;\n\treadonly systemPrompt: string;\n\treadonly providerDataSource = claudeSource;\n\tprivate readonly logger: ILogger;\n\n\t// `model` and `reasoningEffort` are untyped on purpose: this adapter is the one that knows\n\t// what Claude supports.\n\tconstructor({\n\t\tmodel,\n\t\treasoningEffort,\n\t\tcompactAt,\n\t\tsystemPrompt,\n\t\tlogger,\n\t}: {\n\t\tmodel: string;\n\t\treasoningEffort?: string;\n\t\t/** The input tokens at which Anthropic compacts the context on its side. Without it, never. */\n\t\tcompactAt?: number | undefined;\n\t\tsystemPrompt: string;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!isOneOf(claudeLLMModels, model)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${model}\" is not a Claude API model; expected one of: ${claudeLLMModels.join(', ')}`,\n\t\t\t);\n\t\t}\n\t\tconst effort = reasoningEffort ?? 'high';\n\t\tif (!isOneOf(claudeLLMReasoningEfforts, effort)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${effort}\" is not a Claude API reasoning effort; expected one of: ${claudeLLMReasoningEfforts.join(', ')}`,\n\t\t\t);\n\t\t}\n\t\tif (compactAt !== undefined && !(Number.isInteger(compactAt) && compactAt >= minCompactAt)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`compactAt must be a whole number of tokens of at least ${String(minCompactAt)} on Claude, not ${String(compactAt)}`,\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\t// The SDK's default retries stay on, as OpenAI's do. See \"The provider SDKs keep their\n\t\t\t// transport retries under RetryingAgent\" in decisions.md.\n\t\t\tthis.client = new Anthropic();\n\t\t} catch (error) {\n\t\t\t// Conflicting credential options, for example: building it again cannot fix them. A\n\t\t\t// missing key does not fail here but on the first call; see `isLocalSDKFailure`.\n\t\t\tthrow new UnrecoverableError('The Claude client could not be set up', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\n\t\tthis.model = claudeModelIds[model];\n\t\tthis.reasoningEffort = effort;\n\t\tthis.compactAt = compactAt;\n\t\tthis.systemPrompt = systemPrompt;\n\t\tthis.logger = logger;\n\t}\n\n\tsupportsNative(tool: NativeTool): boolean {\n\t\treturn tool === 'textEditor';\n\t}\n\n\tasync send(\n\t\t{ context, tools }: { context: Message[]; tools: ToolDefinition[] },\n\t\tsignal: AbortSignal,\n\t): Promise<LLMResponse> {\n\t\tlet response: AnthropicMessage;\n\t\tlet stream: BetaMessageStream | undefined;\n\t\t// What each compaction streamed of its summary, by block index, its missing parts left out.\n\t\tconst summaries = new Map<number, string>();\n\n\t\ttry {\n\t\t\t// Streamed only to lift the timeout: the client still answers with the whole message.\n\t\t\tconst sent = this.client.beta.messages.stream(\n\t\t\t\t{\n\t\t\t\t\tmodel: this.model,\n\t\t\t\t\tmax_tokens: maxOutputTokens,\n\t\t\t\t\t// A compaction changes the conversation's first block, so the cache would otherwise\n\t\t\t\t\t// keep nothing: this keeps the system prompt cached across it. The API rejects an\n\t\t\t\t\t// empty text block, and an empty prompt has nothing to cache.\n\t\t\t\t\tsystem:\n\t\t\t\t\t\tthis.compactAt === undefined || this.systemPrompt === ''\n\t\t\t\t\t\t\t? this.systemPrompt\n\t\t\t\t\t\t\t: [{ type: 'text', text: this.systemPrompt, cache_control: { type: 'ephemeral' } }],\n\t\t\t\t\t// The latest compaction carries everything before it, which Anthropic would only ignore.\n\t\t\t\t\tmessages: sentContext(context, claudeSource).flatMap(toClaudeInput),\n\t\t\t\t\t// Left out when empty, so a request without tools stays as it always was.\n\t\t\t\t\t...(tools.length > 0 && { tools: tools.map(toClaudeTool) }),\n\t\t\t\t\tthinking: { type: 'adaptive', display: 'summarized' },\n\t\t\t\t\toutput_config: { effort: this.reasoningEffort },\n\t\t\t\t\t// Anthropic caches only on request. This marks the last block, so the next call\n\t\t\t\t\t// reads everything before it from the cache instead of paying for it again.\n\t\t\t\t\tcache_control: { type: 'ephemeral' },\n\t\t\t\t\t// Anthropic compacts inside the call that passes it, and answers with the compaction first.\n\t\t\t\t\t...(this.compactAt !== undefined && {\n\t\t\t\t\t\tbetas: [compactionBeta],\n\t\t\t\t\t\tcontext_management: {\n\t\t\t\t\t\t\tedits: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: 'compact_20260112',\n\t\t\t\t\t\t\t\t\ttrigger: { type: 'input_tokens', value: this.compactAt },\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t\tstream = sent;\n\t\t\tsent.on('streamEvent', event => {\n\t\t\t\tif (event.type === 'content_block_start' && event.content_block.type === 'compaction') {\n\t\t\t\t\tsummaries.set(event.index, event.content_block.content ?? '');\n\t\t\t\t} else if (\n\t\t\t\t\tevent.type === 'content_block_delta' &&\n\t\t\t\t\tevent.delta.type === 'compaction_delta'\n\t\t\t\t) {\n\t\t\t\t\tsummaries.set(\n\t\t\t\t\t\tevent.index,\n\t\t\t\t\t\t(summaries.get(event.index) ?? '') + (event.delta.content ?? ''),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\t\t\tresponse = await sent.finalMessage();\n\t\t} catch (error) {\n\t\t\t// The SDK's abort error is named 'Error', so it would pass for a provider failure.\n\t\t\tsignal.throwIfAborted();\n\t\t\tconst connected = Boolean(stream?.response);\n\t\t\tconst failure = classifyClaudeFailure(error, connected);\n\t\t\t// Once the stream connected, Claude may have written, and billed, part of an answer it\n\t\t\t// never finished sending, so the run's count cannot be known.\n\t\t\tthrow connected ? withSpentTokens(failure, undefined, true) : failure;\n\t\t}\n\n\t\treturn treatErrors(\n\t\t\t() => toLLMResponse(response, this.logger, summaries),\n\t\t\tclassifyLocalFailure,\n\t\t\t'Claude response mapping failed',\n\t\t);\n\t}\n}\n"]}
1
+ {"version":3,"file":"claudeLLMClient.js","sourceRoot":"","sources":["../../../src/llm/infrastructure/claudeLLMClient.ts"],"names":[],"mappings":"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","sourcesContent":["import Anthropic, { AnthropicError, APIError } from '@anthropic-ai/sdk';\nimport type { BetaMessageStream } from '@anthropic-ai/sdk/lib/BetaMessageStream';\n// The beta surface, for compaction: without its beta header, a request is the stable one.\nimport type {\n\tBetaCompactionBlockParam as CompactionBlockParam,\n\tBetaContentBlock as ContentBlock,\n\tBetaContentBlockParam as ContentBlockParam,\n\tBetaMessage as AnthropicMessage,\n\tBetaMessageParam as MessageParam,\n\tBetaOutputConfig as OutputConfig,\n\tBetaRedactedThinkingBlockParam as RedactedThinkingBlockParam,\n\tBetaThinkingBlockParam as ThinkingBlockParam,\n\tBetaTool as ClaudeTool,\n\tBetaToolTextEditor20250728 as ToolTextEditor20250728,\n\tBetaUsage as Usage,\n} from '@anthropic-ai/sdk/resources/beta/messages/messages';\nimport {\n\tInvalidAgentConfigError,\n\tUnrecoverableError,\n\twithSpentTokens,\n} from '#src/shared/domain/errors';\nimport {\n\tclassifyHostFailure,\n\tclassifyLocalFailure,\n\tclassifyProviderFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/shared/domain/providerFailure';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport type { Tokens } from '#src/shared/domain/tokens';\nimport { MaxContextError } from '../domain/errors.ts';\nimport type { LLMClient, LLMResponse, StopReason } from '../domain/llm.ts';\nimport { type Message, type MessagePart, sentContext } from '../domain/message.ts';\nimport { type NativeTool, nativeOf, type ToolDefinition } from '../domain/tool.ts';\nimport { claudeModelIds } from './claudeModelIds.ts';\n\n// The Messages API takes full model IDs; `claudeModelIds` pins the one each alias names.\nexport const claudeLLMModels = [\n\t'opus',\n\t'fable',\n\t'sonnet',\n\t'haiku',\n] as const satisfies readonly (keyof typeof claudeModelIds)[];\nexport type ClaudeLLMModel = (typeof claudeLLMModels)[number];\n\nexport const claudeLLMReasoningEfforts = [\n\t'low',\n\t'medium',\n\t'high',\n\t'xhigh',\n\t'max',\n] as const satisfies readonly NonNullable<OutputConfig['effort']>[];\nexport type ClaudeLLMReasoningEffort = (typeof claudeLLMReasoningEfforts)[number];\n\n// Adaptive thinking spends from this budget too, so a hard step at a high effort can need most\n// of it. The request is streamed so it is not bound by the SDK's ten-minute timeout, which made\n// it refuse more than about 21,000 without a stream. Every model the aliases name writes up to\n// 128,000; Haiku 5.5 at `xhigh` once spent 64,000 on one answer.\nconst maxOutputTokens = 128_000;\n\n// The request itself is wrong or not allowed, so sending it again cannot succeed.\nconst unrecoverableStatuses = [400, 401, 403, 404, 413, 422];\n\n// The account is out of credit, or its key cannot make the request. Resending cannot fix\n// either, whatever status the error arrives with.\nconst unrecoverableErrorTypes = ['billing_error', 'authentication_error', 'permission_error'];\n\n// This client's label on the thinking blocks it keeps for replay; see `ProviderDataPart`.\nconst claudeSource = 'claude';\n\n// Anthropic's compaction at a token threshold, and the least threshold it accepts.\nconst compactionBeta = 'compact-2026-01-12';\nconst minCompactAt = 50_000;\n\n/**\n * Anthropic rejects a `tool_use` whose input is not an object, with a 400 on every later call.\n * Another provider's call can hold anything else, such as OpenAI arguments that did not parse;\n * its result already told the model the call failed, so sending it as empty loses nothing.\n */\nfunction toClaudeInputObject(input: unknown): unknown {\n\treturn typeof input === 'object' && input !== null && !Array.isArray(input) ? input : {};\n}\n\n/** Thinking crosses back only as the signed block this client kept, never as its text. */\nfunction toClaudeBlocks(part: MessagePart): ContentBlockParam[] {\n\tswitch (part.type) {\n\t\tcase 'text':\n\t\t\t// The API rejects an empty text block.\n\t\t\treturn part.text ? [{ type: 'text', text: part.text }] : [];\n\t\tcase 'toolCall':\n\t\t\treturn [\n\t\t\t\t{ type: 'tool_use', id: part.id, name: part.name, input: toClaudeInputObject(part.input) },\n\t\t\t];\n\t\tcase 'toolResult':\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: 'tool_result',\n\t\t\t\t\ttool_use_id: part.callId,\n\t\t\t\t\tcontent: part.output,\n\t\t\t\t\tis_error: part.isError,\n\t\t\t\t},\n\t\t\t];\n\t\tcase 'providerData':\n\t\t\t// Another provider's block would be rejected; losing it only costs that provider's reasoning.\n\t\t\treturn part.source === claudeSource\n\t\t\t\t? [part.data as ThinkingBlockParam | RedactedThinkingBlockParam]\n\t\t\t\t: [];\n\t\tcase 'compaction':\n\t\t\t// Another provider's summary means nothing here; this client sends the turns it replaced.\n\t\t\treturn part.source === claudeSource ? [part.data as CompactionBlockParam] : [];\n\t\tcase 'reasoning':\n\t\t\t// Unsigned text cannot be replayed as thinking. The API accepts a turn without it.\n\t\t\treturn [];\n\t\tcase 'contextSummary':\n\t\t\t// Never sent: the agent sends the context derived from it.\n\t\t\treturn [];\n\t}\n}\n\n/**\n * Tool results travel in a user turn, as the API requires. A message left with no blocks is\n * left out whole; the API joins the consecutive user turns that leaves.\n */\nfunction toClaudeInput(message: Message): MessageParam[] {\n\tconst content = message.content.flatMap(toClaudeBlocks);\n\tif (content.length === 0) {\n\t\treturn [];\n\t}\n\n\treturn [{ role: message.role === 'tool' ? 'user' : message.role, content }];\n}\n\n// Anthropic fixes the text editor's name: its calls arrive under it.\nconst textEditorName = 'str_replace_based_edit_tool';\n\n/**\n * Strict: the API constrains the model to the schema, so its input always parses and matches.\n * A schema outside what strict mode supports fails the request with a 400. The native text\n * editor has Anthropic's own schema, and its calls and results are ordinary tool blocks.\n */\nfunction toClaudeTool(definition: ToolDefinition): ClaudeTool | ToolTextEditor20250728 {\n\tconst native = nativeOf(definition);\n\tif (native === 'textEditor') {\n\t\tif (definition.name !== textEditorName) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`Claude's text editor must be named \"${textEditorName}\", not \"${definition.name}\"`,\n\t\t\t);\n\t\t}\n\t\treturn { type: 'text_editor_20250728', name: textEditorName };\n\t}\n\tif (native !== undefined) {\n\t\tthrow new InvalidAgentConfigError(`Claude has no native tool \"${native}\"`);\n\t}\n\tconst { name, description, inputSchema } = definition;\n\treturn { name, description, input_schema: inputSchema, strict: true };\n}\n\nfunction describeBlock(block: ContentBlock, logger: ILogger): MessagePart[] {\n\tswitch (block.type) {\n\t\tcase 'text':\n\t\t\treturn [{ type: 'text', text: block.text }];\n\t\tcase 'thinking': {\n\t\t\t// Kept signed for replay; the summarized text is only for narration.\n\t\t\tconst signed: ThinkingBlockParam = {\n\t\t\t\ttype: 'thinking',\n\t\t\t\tthinking: block.thinking,\n\t\t\t\tsignature: block.signature,\n\t\t\t};\n\t\t\tconst replay: MessagePart = { type: 'providerData', source: claudeSource, data: signed };\n\t\t\treturn block.thinking ? [{ type: 'reasoning', text: block.thinking }, replay] : [replay];\n\t\t}\n\t\tcase 'redacted_thinking': {\n\t\t\t// Encrypted by design: nothing to narrate, but it still goes back.\n\t\t\tconst redacted: RedactedThinkingBlockParam = { type: 'redacted_thinking', data: block.data };\n\t\t\treturn [{ type: 'providerData', source: claudeSource, data: redacted }];\n\t\t}\n\t\tcase 'tool_use':\n\t\t\treturn [{ type: 'toolCall', id: block.id, name: block.name, input: block.input }];\n\t\tcase 'compaction': {\n\t\t\t// A compaction that failed, with no summary. Claude reads one sent back as if it were not\n\t\t\t// there, so it is left out: the context goes on whole, and Claude compacts again later.\n\t\t\tif (!block.content) {\n\t\t\t\ttreatErrors(\n\t\t\t\t\t() => {\n\t\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t\t'Claude could not compact the conversation; it is sent whole until Claude compacts it',\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\tclassifyHostFailure,\n\t\t\t\t\t'Claude client logger failed while mapping a response',\n\t\t\t\t);\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\t// Claude's summary of the context before it, readable, with what it alone reads back.\n\t\t\tconst compaction: CompactionBlockParam = {\n\t\t\t\ttype: 'compaction',\n\t\t\t\tcontent: block.content,\n\t\t\t\tencrypted_content: block.encrypted_content,\n\t\t\t};\n\t\t\treturn [{ type: 'compaction', source: claudeSource, data: compaction }];\n\t\t}\n\t\tdefault:\n\t\t\t// Server tools are never offered, so any other block is output this client does not expect.\n\t\t\ttreatErrors(\n\t\t\t\t() => {\n\t\t\t\t\tlogger.warn(block, 'Claude returned a content block the client does not map');\n\t\t\t\t},\n\t\t\t\tclassifyHostFailure,\n\t\t\t\t'Claude client logger failed while mapping a response',\n\t\t\t);\n\t\t\treturn [];\n\t}\n}\n\nfunction toStopReason(response: AnthropicMessage): StopReason {\n\tswitch (response.stop_reason) {\n\t\t// `tool_use` is a complete answer that asks for tools: running them is the agent's call.\n\t\tcase 'end_turn':\n\t\tcase 'tool_use':\n\t\t\treturn 'completed';\n\t\tcase 'max_tokens':\n\t\t\treturn 'truncated';\n\t\tcase 'refusal':\n\t\t\treturn 'refused';\n\t\tcase 'model_context_window_exceeded':\n\t\t\tthrow new MaxContextError('The context no longer fits the Claude model', {\n\t\t\t\tcause: 'The response stopped at the model context window.',\n\t\t\t});\n\t\tdefault:\n\t\t\t// `pause_turn` and `stop_sequence` need server tools or stop sequences, and this\n\t\t\t// client sends neither.\n\t\t\tthrow new UnrecoverableError('The Claude response stopped unexpectedly', {\n\t\t\t\tcause: `The response stopped with \"${response.stop_reason ?? 'unknown'}\".`,\n\t\t\t});\n\t}\n}\n\n/**\n * Unlike OpenAI, Anthropic counts cache reads and writes apart from `input_tokens`. A call that\n * compacted leaves the compaction out of its totals, so with iterations their sum is the bill.\n */\nfunction toTokens(usage: Usage | undefined, logger: ILogger): Tokens | undefined {\n\tif (!usage) {\n\t\t// Zeros would report a call that may have been billed as free.\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tlogger.warn('Claude returned a response without usage; it cannot be accounted for');\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Claude client logger failed while reading usage',\n\t\t);\n\t\treturn undefined;\n\t}\n\n\tif (usage.iterations && usage.iterations.length > 0) {\n\t\treturn usage.iterations.reduce<Tokens>(\n\t\t\t(sum, iteration) => ({\n\t\t\t\tinputTokens: sum.inputTokens + iteration.input_tokens,\n\t\t\t\treadCacheTokens: sum.readCacheTokens + iteration.cache_read_input_tokens,\n\t\t\t\twrittenCacheTokens: sum.writtenCacheTokens + iteration.cache_creation_input_tokens,\n\t\t\t\toutputTokens: sum.outputTokens + iteration.output_tokens,\n\t\t\t}),\n\t\t\t{ inputTokens: 0, readCacheTokens: 0, writtenCacheTokens: 0, outputTokens: 0 },\n\t\t);\n\t}\n\treturn {\n\t\tinputTokens: usage.input_tokens,\n\t\treadCacheTokens: usage.cache_read_input_tokens ?? 0,\n\t\twrittenCacheTokens: usage.cache_creation_input_tokens ?? 0,\n\t\toutputTokens: usage.output_tokens,\n\t};\n}\n\n/**\n * How many tokens of context the call compacted, when it did: what its compaction iteration read,\n * since Anthropic bills a compaction as a call of its own over the context before it.\n */\nfunction compactedContext(usage: Usage | undefined): { compactedContextTokens?: number } {\n\tconst compaction = usage?.iterations?.find(iteration => iteration.type === 'compaction');\n\tif (!compaction) return {};\n\treturn {\n\t\tcompactedContextTokens:\n\t\t\tcompaction.input_tokens +\n\t\t\tcompaction.cache_read_input_tokens +\n\t\t\tcompaction.cache_creation_input_tokens,\n\t};\n}\n\n/**\n * @param summaries each compaction's summary as it was streamed, by the index of its block. The\n * SDK joins a delta that carries none as the text \"null\", so the final message cannot tell.\n */\nfunction toLLMResponse(\n\tresponse: AnthropicMessage,\n\tlogger: ILogger,\n\tsummaries: ReadonlyMap<number, string>,\n): LLMResponse {\n\tlet usage: Tokens | undefined;\n\n\ttry {\n\t\t// Inside the try: it warns when the usage is missing, and that warning can throw too.\n\t\tusage = toTokens(response.usage, logger);\n\t\treturn {\n\t\t\tmessage: {\n\t\t\t\trole: 'assistant',\n\t\t\t\tcontent: response.content.flatMap((block, index) => {\n\t\t\t\t\tconst summary = summaries.get(index);\n\t\t\t\t\treturn describeBlock(\n\t\t\t\t\t\tblock.type === 'compaction' && summary !== undefined\n\t\t\t\t\t\t\t? { ...block, content: summary }\n\t\t\t\t\t\t\t: block,\n\t\t\t\t\t\tlogger,\n\t\t\t\t\t);\n\t\t\t\t}),\n\t\t\t},\n\t\t\tusage,\n\t\t\tstopReason: toStopReason(response),\n\t\t\t...compactedContext(response.usage),\n\t\t};\n\t} catch (error) {\n\t\t// The call was billed whether the response failed or a logger did while mapping it, and\n\t\t// an answer without usage leaves the run's count unknown.\n\t\tthrow withSpentTokens(error, usage, !response.usage);\n\t}\n}\n\n/**\n * The SDK resolves credentials lazily, so a missing key surfaces on the first call, as a plain\n * `Error` the SDK gives no class of its own. Before the stream connects, an `AnthropicError` that\n * is not an `APIError` never carries an answer from the API: it is the SDK refusing to make the\n * call, such as an unresolved profile. Once it connects, the stream reports any failure as one,\n * such as a connection lost halfway; see `classifyClaudeFailure`.\n */\nfunction isLocalSDKFailure(error: unknown): boolean {\n\treturn (\n\t\t(error instanceof AnthropicError && !(error instanceof APIError)) ||\n\t\t(error instanceof Error && error.message.startsWith('Could not resolve authentication method'))\n\t);\n}\n\n/** `connected` is whether the stream had its response: a failure after that broke the stream off. */\nfunction classifyClaudeFailure(error: unknown, connected: boolean): Error {\n\tif (connected && !(error instanceof APIError)) {\n\t\t// The API was answering, so a later attempt can succeed. An error event the API sends\n\t\t// mid-stream, such as an overload, arrives as an `APIError` and is classified below.\n\t\treturn classifyProviderFailure(error, 'The Claude stream broke off');\n\t}\n\tif (isLocalSDKFailure(error)) {\n\t\t// The API never answered, and calling again would fail the same way.\n\t\treturn new UnrecoverableError('The Claude client cannot send the request', {\n\t\t\tcause: describeFailure(error),\n\t\t});\n\t}\n\tif (error instanceof APIError) {\n\t\t// Anthropic gives an overlong prompt no code of its own, only a 400 with this message.\n\t\tif (error.status === 400 && error.message.includes('prompt is too long')) {\n\t\t\treturn new MaxContextError('The context no longer fits the Claude model', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t\tif (\n\t\t\tunrecoverableErrorTypes.some(type => type === error.type) ||\n\t\t\tunrecoverableStatuses.some(status => status === error.status)\n\t\t) {\n\t\t\treturn new UnrecoverableError('Claude rejected the request', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t}\n\n\t// A rate limit, an overload (529), a 5xx or a network failure: a later attempt can succeed.\n\treturn classifyProviderFailure(error, 'The Claude request failed');\n}\n\n/**\n * A stateless `LLMClient` on the Anthropic Messages API, which keeps nothing between calls:\n * every call carries the whole context, so the conversation is MiKode's, not the provider's.\n */\nexport class ClaudeLLMClient implements LLMClient {\n\tprivate readonly client: Anthropic;\n\tprivate readonly model: (typeof claudeModelIds)[ClaudeLLMModel];\n\tprivate readonly reasoningEffort: ClaudeLLMReasoningEffort;\n\treadonly systemPrompt: string;\n\treadonly providerDataSource = claudeSource;\n\tprivate readonly logger: ILogger;\n\n\t// `model` and `reasoningEffort` are untyped on purpose: this adapter is the one that knows\n\t// what Claude supports.\n\tconstructor({\n\t\tmodel,\n\t\treasoningEffort,\n\t\tsystemPrompt,\n\t\tlogger,\n\t}: {\n\t\tmodel: string;\n\t\treasoningEffort?: string;\n\t\tsystemPrompt: string;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!isOneOf(claudeLLMModels, model)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${model}\" is not a Claude API model; expected one of: ${claudeLLMModels.join(', ')}`,\n\t\t\t);\n\t\t}\n\t\tconst effort = reasoningEffort ?? 'high';\n\t\tif (!isOneOf(claudeLLMReasoningEfforts, effort)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${effort}\" is not a Claude API reasoning effort; expected one of: ${claudeLLMReasoningEfforts.join(', ')}`,\n\t\t\t);\n\t\t}\n\t\ttry {\n\t\t\t// The SDK's default retries stay on, as OpenAI's do. See \"The provider SDKs keep their\n\t\t\t// transport retries under RetryingAgent\" in decisions.md.\n\t\t\tthis.client = new Anthropic();\n\t\t} catch (error) {\n\t\t\t// Conflicting credential options, for example: building it again cannot fix them. A\n\t\t\t// missing key does not fail here but on the first call; see `isLocalSDKFailure`.\n\t\t\tthrow new UnrecoverableError('The Claude client could not be set up', {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\n\t\tthis.model = claudeModelIds[model];\n\t\tthis.reasoningEffort = effort;\n\t\tthis.systemPrompt = systemPrompt;\n\t\tthis.logger = logger;\n\t}\n\n\tsupportsNative(tool: NativeTool): boolean {\n\t\treturn tool === 'textEditor';\n\t}\n\n\tcheckCompactAt(compactAt: number): void {\n\t\tif (!(Number.isInteger(compactAt) && compactAt >= minCompactAt)) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`compactAt must be a whole number of tokens of at least ${String(minCompactAt)} on Claude, not ${String(compactAt)}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync send(\n\t\t{\n\t\t\tcontext,\n\t\t\ttools,\n\t\t\tcompactAt,\n\t\t}: { context: Message[]; tools: ToolDefinition[]; compactAt?: number | 'now' | undefined },\n\t\tsignal: AbortSignal,\n\t): Promise<LLMResponse> {\n\t\t// Compacting now is compacting at the least threshold Anthropic takes, which a context\n\t\t// too large for the model has long passed.\n\t\tconst trigger = compactAt === 'now' ? minCompactAt : compactAt;\n\t\t// The latest compaction carries everything before it, which Anthropic would only ignore.\n\t\tconst messages = sentContext(context, claudeSource);\n\t\t// A compaction sent back needs the beta as much as one asked for, even with compaction off,\n\t\t// as after a context that no longer fit was compacted once.\n\t\tconst first = messages[0]?.content[0];\n\t\tconst compacted =\n\t\t\ttrigger !== undefined || (first?.type === 'compaction' && first.source === claudeSource);\n\t\tlet response: AnthropicMessage;\n\t\tlet stream: BetaMessageStream | undefined;\n\t\t// What each compaction streamed of its summary, by block index, its missing parts left out.\n\t\tconst summaries = new Map<number, string>();\n\n\t\ttry {\n\t\t\t// Streamed only to lift the timeout: the client still answers with the whole message.\n\t\t\tconst sent = this.client.beta.messages.stream(\n\t\t\t\t{\n\t\t\t\t\tmodel: this.model,\n\t\t\t\t\tmax_tokens: maxOutputTokens,\n\t\t\t\t\t// A compaction changes the conversation's first block, so the cache would otherwise\n\t\t\t\t\t// keep nothing: this keeps the system prompt cached across it. The API rejects an\n\t\t\t\t\t// empty text block, and an empty prompt has nothing to cache.\n\t\t\t\t\tsystem:\n\t\t\t\t\t\t!compacted || this.systemPrompt === ''\n\t\t\t\t\t\t\t? this.systemPrompt\n\t\t\t\t\t\t\t: [{ type: 'text', text: this.systemPrompt, cache_control: { type: 'ephemeral' } }],\n\t\t\t\t\tmessages: messages.flatMap(toClaudeInput),\n\t\t\t\t\t// Left out when empty, so a request without tools stays as it always was.\n\t\t\t\t\t...(tools.length > 0 && { tools: tools.map(toClaudeTool) }),\n\t\t\t\t\tthinking: { type: 'adaptive', display: 'summarized' },\n\t\t\t\t\toutput_config: { effort: this.reasoningEffort },\n\t\t\t\t\t// Anthropic caches only on request. This marks the last block, so the next call\n\t\t\t\t\t// reads everything before it from the cache instead of paying for it again.\n\t\t\t\t\tcache_control: { type: 'ephemeral' },\n\t\t\t\t\t...(compacted && { betas: [compactionBeta] }),\n\t\t\t\t\t// Anthropic compacts inside the call that passes it, and answers with the compaction first.\n\t\t\t\t\t...(trigger !== undefined && {\n\t\t\t\t\t\tcontext_management: {\n\t\t\t\t\t\t\tedits: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: 'compact_20260112',\n\t\t\t\t\t\t\t\t\ttrigger: { type: 'input_tokens', value: trigger },\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t\tstream = sent;\n\t\t\tsent.on('streamEvent', event => {\n\t\t\t\tif (event.type === 'content_block_start' && event.content_block.type === 'compaction') {\n\t\t\t\t\tsummaries.set(event.index, event.content_block.content ?? '');\n\t\t\t\t} else if (\n\t\t\t\t\tevent.type === 'content_block_delta' &&\n\t\t\t\t\tevent.delta.type === 'compaction_delta'\n\t\t\t\t) {\n\t\t\t\t\tsummaries.set(\n\t\t\t\t\t\tevent.index,\n\t\t\t\t\t\t(summaries.get(event.index) ?? '') + (event.delta.content ?? ''),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\t\t\tresponse = await sent.finalMessage();\n\t\t} catch (error) {\n\t\t\t// The SDK's abort error is named 'Error', so it would pass for a provider failure.\n\t\t\tsignal.throwIfAborted();\n\t\t\tconst connected = Boolean(stream?.response);\n\t\t\tconst failure = classifyClaudeFailure(error, connected);\n\t\t\t// Once the stream connected, Claude may have written, and billed, part of an answer it\n\t\t\t// never finished sending, so the run's count cannot be known.\n\t\t\tthrow connected ? withSpentTokens(failure, undefined, true) : failure;\n\t\t}\n\n\t\treturn treatErrors(\n\t\t\t() => toLLMResponse(response, this.logger, summaries),\n\t\t\tclassifyLocalFailure,\n\t\t\t'Claude response mapping failed',\n\t\t);\n\t}\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { type TypeSafeClientConfig } from '@typesafe-ai/sdk';
2
+ import type { ContextTriage, TriagedResult } from '#src/llm/domain/contextTriage';
3
+ /**
4
+ * Jev's triage of the context, one request per tool result. The client is created on the first
5
+ * triage, so a missing `TYPESAFE_API_KEY` fails that triage, never the agent's construction.
6
+ */
7
+ export declare class JevContextTriage implements ContextTriage {
8
+ private client;
9
+ private readonly config;
10
+ /** @param config for the tests: the SDK's `apiKey`, `fetch`, `retry` and `compression`. */
11
+ constructor(config?: Pick<TypeSafeClientConfig, 'apiKey' | 'fetch' | 'retry' | 'compression'>);
12
+ score({ task, latestAnswer, results, }: {
13
+ task: string;
14
+ latestAnswer: string;
15
+ results: TriagedResult[];
16
+ }, signal: AbortSignal): Promise<number[]>;
17
+ private connect;
18
+ }
@@ -0,0 +1,92 @@
1
+ import { score, TypeSafeClient } from '@typesafe-ai/sdk';
2
+ import { UnrecoverableError } from '#src/shared/domain/errors';
3
+ import { classifyProviderFailure, describeFailure } from '#src/shared/domain/providerFailure';
4
+ /** Pinned, so a new Jev release does not change what the compactor keeps unannounced. */
5
+ const jevModel = 'jev-1.14.0';
6
+ // Each question is judged with the state alone, which Jev bounds at 32k tokens without its
7
+ // slower, less accurate long context: a result is cut well within it.
8
+ const outputCharacters = 60_000;
9
+ const textCharacters = 8_000;
10
+ const inputCharacters = 4_000;
11
+ // The public endpoint limits the rate: a context of hundreds of results is triaged a few at once.
12
+ const concurrency = 8;
13
+ const instructions = 'An agent works on the task. Its context is being compacted. How much does it need this tool result word for word to go on from its latest answer?';
14
+ const criteria = [
15
+ 'Not needed again: the work has moved past it.',
16
+ 'Its gist is enough: a short summary keeps what matters.',
17
+ 'Some exact details matter: names, numbers, paths or lines the agent will reuse.',
18
+ 'Needed word for word: the agent is still working on exactly this content.',
19
+ ];
20
+ /**
21
+ * Jev's triage of the context, one request per tool result. The client is created on the first
22
+ * triage, so a missing `TYPESAFE_API_KEY` fails that triage, never the agent's construction.
23
+ */
24
+ export class JevContextTriage {
25
+ client;
26
+ config;
27
+ /** @param config for the tests: the SDK's `apiKey`, `fetch`, `retry` and `compression`. */
28
+ constructor(config = {}) {
29
+ this.config = config;
30
+ }
31
+ async score({ task, latestAnswer, results, }, signal) {
32
+ const client = this.connect();
33
+ // The first failure stops the requests still to go: the whole triage has failed.
34
+ const failed = new AbortController();
35
+ const requestSignal = AbortSignal.any([signal, failed.signal]);
36
+ const request = async (result) => {
37
+ try {
38
+ return await client.systemOne({
39
+ model: jevModel,
40
+ state: {
41
+ task: shorten(task, textCharacters),
42
+ latestAnswer: shorten(latestAnswer, textCharacters),
43
+ toolCall: {
44
+ tool: result.tool,
45
+ input: shorten(JSON.stringify(result.input), inputCharacters),
46
+ },
47
+ result: shorten(result.output, outputCharacters),
48
+ },
49
+ questions: { needed: score(instructions, criteria) },
50
+ }, { signal: requestSignal });
51
+ }
52
+ catch (error) {
53
+ failed.abort();
54
+ // The SDK's abort error is not named 'AbortError', so it would pass for a provider failure.
55
+ signal.throwIfAborted();
56
+ throw classifyProviderFailure(error, 'Jev could not triage the context');
57
+ }
58
+ };
59
+ return inPool(results, concurrency, async (result) => {
60
+ const response = await request(result);
61
+ return response.answers.needed.score;
62
+ });
63
+ }
64
+ connect() {
65
+ try {
66
+ // Its logger writes to the console, which belongs to the consumer.
67
+ this.client ??= new TypeSafeClient({ ...this.config, logLevel: 'off' });
68
+ }
69
+ catch (error) {
70
+ throw new UnrecoverableError('Jev is not configured', { cause: describeFailure(error) });
71
+ }
72
+ return this.client;
73
+ }
74
+ }
75
+ /** `map` over `items`, at most `limit` at once, the results in their order. */
76
+ async function inPool(items, limit, map) {
77
+ const results = [];
78
+ let next = 0;
79
+ const worker = async () => {
80
+ for (let index = next++; index < items.length; index = next++) {
81
+ const item = items[index];
82
+ if (item !== undefined)
83
+ results[index] = await map(item);
84
+ }
85
+ };
86
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
87
+ return results;
88
+ }
89
+ function shorten(text, max) {
90
+ return text.length <= max ? text : `${text.slice(0, max)}…`;
91
+ }
92
+ //# sourceMappingURL=jevContextTriage.js.map