@runtypelabs/sdk 1.8.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3038,7 +3038,11 @@ var designPhase = {
3038
3038
  buildTransitionSummary(_state, _nextPhaseName) {
3039
3039
  return [
3040
3040
  "Automatic phase transition: design \u2192 build.",
3041
- "Requirements understood. Build the game and deploy it using deploy_sandbox with the files parameter."
3041
+ "Requirements understood. Build the game and deploy it using deploy_sandbox.",
3042
+ "",
3043
+ "MANDATORY: Use the `files` parameter for ALL game HTML/JS/CSS.",
3044
+ "code = minimal Express static server (~4 lines), files = game assets.",
3045
+ "NEVER embed HTML inside res.send() template literals."
3042
3046
  ].join("\n");
3043
3047
  },
3044
3048
  interceptToolCall(toolName, _args, _ctx) {
@@ -3098,7 +3102,32 @@ var buildPhase = {
3098
3102
  " 4. If the deploy fails, read the error output, fix the code, and call deploy_sandbox again.",
3099
3103
  " 5. The sandbox is persistent \u2014 subsequent calls reuse the same sandbox.",
3100
3104
  " 6. Load game libraries from CDN in your HTML (e.g., Three.js from unpkg/cdnjs) OR include them in packageJson.",
3101
- " 7. Make the game fullscreen by default (width: 100vw, height: 100vh, no margin/padding on body)."
3105
+ " 7. Make the game fullscreen by default (width: 100vw, height: 100vh, no margin/padding on body).",
3106
+ "",
3107
+ "STARTER TEMPLATES \u2014 Copy and customize based on the game type:",
3108
+ "",
3109
+ "ALL templates use this server code:",
3110
+ ' code: `const express = require("express"); const app = express(); app.use(express.static("public")); app.listen(3000, () => console.log("Game server running on port 3000"));`',
3111
+ ' packageJson: { "dependencies": { "express": "^4.18.2" } }',
3112
+ ' language: "javascript"',
3113
+ "",
3114
+ "THREE.JS (3D games \u2014 racing, flying, FPS, etc.):",
3115
+ " files: {",
3116
+ ' "public/index.html": HTML with <script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js"}}</script> and <script type="module" src="game.js"></script>',
3117
+ ' "public/game.js": Import three, create scene/camera/renderer, game loop with requestAnimationFrame',
3118
+ " }",
3119
+ "",
3120
+ "PHASER (2D games \u2014 platformers, shooters, puzzles):",
3121
+ " files: {",
3122
+ ' "public/index.html": HTML with <script src="https://cdn.jsdelivr.net/npm/phaser@3.80.1/dist/phaser.min.js"></script> and <script src="game.js"></script>',
3123
+ ' "public/game.js": Phaser.Game config with scenes, preload/create/update lifecycle',
3124
+ " }",
3125
+ "",
3126
+ "CANVAS 2D (simple games \u2014 pong, snake, breakout):",
3127
+ " files: {",
3128
+ ' "public/index.html": HTML with <canvas id="game" width="800" height="600"></canvas> and <script src="game.js"></script>',
3129
+ ' "public/game.js": Get canvas context, game loop with requestAnimationFrame, draw calls',
3130
+ " }"
3102
3131
  ].join("\n");
3103
3132
  },
3104
3133
  buildToolGuidance(_state) {
@@ -3112,8 +3141,23 @@ var buildPhase = {
3112
3141
  isComplete() {
3113
3142
  return false;
3114
3143
  },
3115
- interceptToolCall(toolName, _args, _ctx) {
3116
- return blockLocalTools(toolName);
3144
+ interceptToolCall(toolName, args, _ctx) {
3145
+ const localBlock = blockLocalTools(toolName);
3146
+ if (localBlock) return localBlock;
3147
+ if (toolName === "deploy_sandbox" && args) {
3148
+ const hasFiles = args.files && typeof args.files === "object" && Object.keys(args.files).length > 0;
3149
+ const codeStr = typeof args.code === "string" ? args.code : "";
3150
+ const embedsHtml = /res\.send\s*\(/.test(codeStr) && /<html|<body|<script/i.test(codeStr);
3151
+ if (!hasFiles && embedsHtml) {
3152
+ return [
3153
+ "Blocked: You are embedding HTML inside res.send() \u2014 this will break due to nested template literals.",
3154
+ "You MUST use the `files` parameter. Put your game HTML/JS/CSS in files and use a minimal Express static server for code.",
3155
+ 'Example: code: `const express = require("express"); const app = express(); app.use(express.static("public")); app.listen(3000);`',
3156
+ 'files: { "public/index.html": "<!DOCTYPE html>...", "public/game.js": "// game code" }'
3157
+ ].join("\n");
3158
+ }
3159
+ }
3160
+ return void 0;
3117
3161
  },
3118
3162
  canAcceptCompletion(_state, trace) {
3119
3163
  return trace.entries.some((entry) => entry.startsWith("deploy_sandbox"));
@@ -5409,6 +5453,8 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5409
5453
  this.updateWorkflowPhase(state, this.createEmptyToolTrace(), workflow);
5410
5454
  let recordId;
5411
5455
  const localToolNames = options.localTools ? Object.keys(options.localTools) : void 0;
5456
+ const builtinToolSchemas = await this.loadBuiltinToolSchemas(options.toolIds);
5457
+ const compactInstructions = this.resolveCompactInstructions(options.compactInstructions, agent);
5412
5458
  if (!options.previousMessages) {
5413
5459
  if (workflow.generateBootstrapContext) {
5414
5460
  state.bootstrapContext = await workflow.generateBootstrapContext(
@@ -5442,7 +5488,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5442
5488
  if (session === 0 && !options.previousMessages) {
5443
5489
  state.originalMessage = options.message;
5444
5490
  }
5445
- const messages = this.buildSessionMessages(
5491
+ const preparedSession = await this.prepareSessionContext(
5446
5492
  options.message,
5447
5493
  state,
5448
5494
  session,
@@ -5450,61 +5496,93 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5450
5496
  localToolNames,
5451
5497
  continuationContext,
5452
5498
  workflow,
5453
- options.toolIds
5499
+ options.toolIds,
5500
+ {
5501
+ autoCompactTokenThreshold: options.autoCompactTokenThreshold,
5502
+ contextLimitTokens: options.contextLimitTokens,
5503
+ model: options.model,
5504
+ compactStrategy: options.compactStrategy,
5505
+ compactInstructions,
5506
+ localTools: options.localTools,
5507
+ builtinToolSchemas,
5508
+ onContextCompaction: options.onContextCompaction,
5509
+ onContextNotice: options.onContextNotice
5510
+ }
5454
5511
  );
5512
+ const {
5513
+ messages,
5514
+ requestContextManagement,
5515
+ pendingNativeCompactionEvent
5516
+ } = preparedSession;
5455
5517
  let sessionResult;
5456
5518
  const sessionData = {
5457
5519
  messages,
5458
5520
  debugMode: options.debugMode,
5459
5521
  model: options.model,
5460
- ...options.toolIds?.length ? { tools: { toolIds: options.toolIds } } : {}
5522
+ ...options.toolIds?.length ? { tools: { toolIds: options.toolIds } } : {},
5523
+ ...requestContextManagement ? { contextManagement: requestContextManagement } : {}
5461
5524
  };
5462
5525
  let sessionToolMessages = [];
5463
- if (useStream && options.localTools) {
5464
- const localToolResult = await this.executeWithLocalTools(
5465
- id,
5466
- sessionData,
5467
- sessionLocalTools || options.localTools,
5468
- sessionCallbacks,
5469
- {
5470
- onLocalToolResult: this.createLocalToolLoopGuard(state, sessionTrace, workflow)
5471
- },
5472
- state.taskName
5473
- );
5474
- if (!localToolResult) {
5475
- throw new Error("Agent stream ended without a complete event");
5526
+ if (pendingNativeCompactionEvent) {
5527
+ await this.emitContextCompactionEvent(options.onContextCompaction, {
5528
+ phase: "start",
5529
+ ...pendingNativeCompactionEvent
5530
+ });
5531
+ }
5532
+ try {
5533
+ if (useStream && options.localTools) {
5534
+ const localToolResult = await this.executeWithLocalTools(
5535
+ id,
5536
+ sessionData,
5537
+ sessionLocalTools || options.localTools,
5538
+ sessionCallbacks,
5539
+ {
5540
+ onLocalToolResult: this.createLocalToolLoopGuard(state, sessionTrace, workflow)
5541
+ },
5542
+ state.taskName
5543
+ );
5544
+ if (!localToolResult) {
5545
+ throw new Error("Agent stream ended without a complete event");
5546
+ }
5547
+ const { completeEvent, toolMessages: captured } = localToolResult;
5548
+ sessionToolMessages = captured;
5549
+ sessionResult = {
5550
+ success: completeEvent.success,
5551
+ result: completeEvent.finalOutput || "",
5552
+ iterations: completeEvent.iterations,
5553
+ totalCost: completeEvent.totalCost || 0,
5554
+ totalTokens: completeEvent.totalTokens,
5555
+ stopReason: completeEvent.stopReason,
5556
+ error: completeEvent.error
5557
+ };
5558
+ } else if (useStream && options.streamCallbacks) {
5559
+ const completeEvent = await this.executeWithCallbacks(
5560
+ id,
5561
+ sessionData,
5562
+ sessionCallbacks || options.streamCallbacks
5563
+ );
5564
+ if (!completeEvent) {
5565
+ throw new Error("Agent stream ended without a complete event");
5566
+ }
5567
+ sessionResult = {
5568
+ success: completeEvent.success,
5569
+ result: completeEvent.finalOutput || "",
5570
+ iterations: completeEvent.iterations,
5571
+ totalCost: completeEvent.totalCost || 0,
5572
+ totalTokens: completeEvent.totalTokens,
5573
+ stopReason: completeEvent.stopReason,
5574
+ error: completeEvent.error
5575
+ };
5576
+ } else {
5577
+ sessionResult = await this.execute(id, sessionData);
5476
5578
  }
5477
- const { completeEvent, toolMessages: captured } = localToolResult;
5478
- sessionToolMessages = captured;
5479
- sessionResult = {
5480
- success: completeEvent.success,
5481
- result: completeEvent.finalOutput || "",
5482
- iterations: completeEvent.iterations,
5483
- totalCost: completeEvent.totalCost || 0,
5484
- totalTokens: completeEvent.totalTokens,
5485
- stopReason: completeEvent.stopReason,
5486
- error: completeEvent.error
5487
- };
5488
- } else if (useStream && options.streamCallbacks) {
5489
- const completeEvent = await this.executeWithCallbacks(
5490
- id,
5491
- sessionData,
5492
- sessionCallbacks || options.streamCallbacks
5493
- );
5494
- if (!completeEvent) {
5495
- throw new Error("Agent stream ended without a complete event");
5579
+ } finally {
5580
+ if (pendingNativeCompactionEvent) {
5581
+ await this.emitContextCompactionEvent(options.onContextCompaction, {
5582
+ phase: "complete",
5583
+ ...pendingNativeCompactionEvent
5584
+ });
5496
5585
  }
5497
- sessionResult = {
5498
- success: completeEvent.success,
5499
- result: completeEvent.finalOutput || "",
5500
- iterations: completeEvent.iterations,
5501
- totalCost: completeEvent.totalCost || 0,
5502
- totalTokens: completeEvent.totalTokens,
5503
- stopReason: completeEvent.stopReason,
5504
- error: completeEvent.error
5505
- };
5506
- } else {
5507
- sessionResult = await this.execute(id, sessionData);
5508
5586
  }
5509
5587
  const toolTraceSummary = this.buildToolTraceSummary(sessionTrace);
5510
5588
  const effectiveSessionOutput = this.buildEffectiveSessionOutput(
@@ -5591,7 +5669,9 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5591
5669
  }
5592
5670
  }
5593
5671
  if (!state.messages) state.messages = [];
5594
- if (state.messages.length > 0 && messages.length > state.messages.length) {
5672
+ if (this.isCompactHistoryMessageSet(messages)) {
5673
+ state.messages = [...messages];
5674
+ } else if (state.messages.length > 0 && messages.length > state.messages.length) {
5595
5675
  const newMessages = messages.slice(state.messages.length);
5596
5676
  state.messages.push(...newMessages);
5597
5677
  } else {
@@ -5656,41 +5736,379 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5656
5736
  const upper = output.toUpperCase();
5657
5737
  return _AgentsEndpoint.STOP_PHRASES.some((phrase) => upper.includes(phrase.toUpperCase()));
5658
5738
  }
5739
+ resolveReservedOutputTokens(contextLimitTokens) {
5740
+ if (typeof contextLimitTokens !== "number" || !Number.isFinite(contextLimitTokens) || contextLimitTokens <= 0) {
5741
+ return void 0;
5742
+ }
5743
+ return Math.max(8e3, Math.min(64e3, Math.floor(contextLimitTokens * 0.15)));
5744
+ }
5745
+ resolveEffectiveInputBudgetTokens(contextLimitTokens) {
5746
+ if (typeof contextLimitTokens !== "number" || !Number.isFinite(contextLimitTokens) || contextLimitTokens <= 0) {
5747
+ return void 0;
5748
+ }
5749
+ const reservedOutputTokens = this.resolveReservedOutputTokens(contextLimitTokens) ?? 0;
5750
+ return Math.max(1, contextLimitTokens - reservedOutputTokens);
5751
+ }
5752
+ resolveCompactStrategy(strategy, modelId) {
5753
+ if (strategy === "summary_fallback") {
5754
+ return "summary_fallback";
5755
+ }
5756
+ const normalizedModelId = modelId?.trim().toLowerCase() ?? "";
5757
+ const supportsAnthropicNativeCompaction = normalizedModelId.includes("claude") || normalizedModelId.includes("anthropic");
5758
+ if (strategy === "provider_native") {
5759
+ return supportsAnthropicNativeCompaction ? "provider_native" : "summary_fallback";
5760
+ }
5761
+ return supportsAnthropicNativeCompaction ? "provider_native" : "summary_fallback";
5762
+ }
5763
+ estimateTextTokens(text) {
5764
+ if (!text) return 0;
5765
+ return Math.ceil(text.length / 4);
5766
+ }
5767
+ estimateUnknownTokens(value) {
5768
+ if (typeof value === "string") return this.estimateTextTokens(value);
5769
+ try {
5770
+ return this.estimateTextTokens(JSON.stringify(value));
5771
+ } catch {
5772
+ return 0;
5773
+ }
5774
+ }
5775
+ estimateMessageContentTokens(content) {
5776
+ if (typeof content === "string") return this.estimateTextTokens(content);
5777
+ return content.reduce((total, part) => {
5778
+ if (typeof part.text === "string") total += this.estimateTextTokens(part.text);
5779
+ if (typeof part.image === "string") total += 85;
5780
+ if (typeof part.data === "string") {
5781
+ total += Math.min(200, this.estimateTextTokens(part.data));
5782
+ }
5783
+ return total + 4;
5784
+ }, 0);
5785
+ }
5786
+ estimateToolCallTokens(toolCalls) {
5787
+ if (!toolCalls || toolCalls.length === 0) return 0;
5788
+ return toolCalls.reduce(
5789
+ (sum, toolCall) => sum + 12 + this.estimateTextTokens(toolCall.toolName) + this.estimateUnknownTokens(toolCall.args),
5790
+ 0
5791
+ );
5792
+ }
5793
+ estimateToolResultTokens(toolResults) {
5794
+ if (!toolResults || toolResults.length === 0) return 0;
5795
+ return toolResults.reduce(
5796
+ (sum, toolResult) => sum + 12 + this.estimateTextTokens(toolResult.toolName) + this.estimateUnknownTokens(toolResult.result),
5797
+ 0
5798
+ );
5799
+ }
5800
+ estimateMessageTokens(message) {
5801
+ return 6 + this.estimateMessageContentTokens(message.content) + this.estimateToolCallTokens(message.toolCalls) + this.estimateToolResultTokens(message.toolResults);
5802
+ }
5803
+ estimateConversationTokens(messages) {
5804
+ return messages.reduce((sum, message) => sum + this.estimateMessageTokens(message), 0);
5805
+ }
5806
+ estimateConversationBreakdown(messages) {
5807
+ let historyTokens = 0;
5808
+ let toolOutputTokens = 0;
5809
+ for (const message of messages) {
5810
+ const contentTokens = this.estimateMessageContentTokens(message.content);
5811
+ const toolCallTokens = this.estimateToolCallTokens(message.toolCalls);
5812
+ const toolResultTokens = this.estimateToolResultTokens(message.toolResults);
5813
+ const messageTotal = 6 + contentTokens + toolCallTokens + toolResultTokens;
5814
+ if (message.role === "tool") {
5815
+ toolOutputTokens += messageTotal;
5816
+ } else {
5817
+ historyTokens += 6 + contentTokens + toolCallTokens;
5818
+ toolOutputTokens += toolResultTokens;
5819
+ }
5820
+ }
5821
+ return {
5822
+ historyTokens,
5823
+ toolOutputTokens,
5824
+ estimatedInputTokens: historyTokens + toolOutputTokens
5825
+ };
5826
+ }
5827
+ estimateToolDefinitionTokens(localTools, builtinToolSchemas) {
5828
+ const localToolDefinitions = Object.entries(localTools || {}).map(([name, definition]) => ({
5829
+ name,
5830
+ description: definition.description,
5831
+ parametersSchema: definition.parametersSchema
5832
+ }));
5833
+ const payload = [...localToolDefinitions, ...builtinToolSchemas];
5834
+ if (payload.length === 0) return 0;
5835
+ return this.estimateUnknownTokens(payload);
5836
+ }
5837
+ async loadBuiltinToolSchemas(toolIds) {
5838
+ if (!toolIds || toolIds.length === 0) return [];
5839
+ try {
5840
+ const schemas = await this.client.get("/tools/schema", { toolIds });
5841
+ return Array.isArray(schemas) ? schemas : [];
5842
+ } catch {
5843
+ return [];
5844
+ }
5845
+ }
5846
+ buildDefaultCompactInstructions() {
5847
+ return [
5848
+ "Preserve changed files or best candidate paths.",
5849
+ "Preserve verification or test results.",
5850
+ "Preserve the current workflow phase and remaining work.",
5851
+ "Preserve unresolved blockers or follow-up risks.",
5852
+ "Preserve artifact references and offloaded tool outputs."
5853
+ ].join(" ");
5854
+ }
5855
+ resolveCompactInstructions(optionInstructions, agent) {
5856
+ if (typeof optionInstructions === "string" && optionInstructions.trim()) {
5857
+ return optionInstructions.trim();
5858
+ }
5859
+ const config = agent.config;
5860
+ if (!config || typeof config !== "object") {
5861
+ return void 0;
5862
+ }
5863
+ const compactInstructions = config.compactInstructions;
5864
+ return typeof compactInstructions === "string" && compactInstructions.trim() ? compactInstructions.trim() : void 0;
5865
+ }
5866
+ extractArtifactReferences(state) {
5867
+ const references = /* @__PURE__ */ new Set();
5868
+ const offloadPrefix = "[Output saved to ";
5869
+ for (const message of state.messages ?? []) {
5870
+ if (!message.toolResults) continue;
5871
+ for (const toolResult of message.toolResults) {
5872
+ if (typeof toolResult.result !== "string") continue;
5873
+ let startIndex = 0;
5874
+ while (startIndex < toolResult.result.length) {
5875
+ const prefixIndex = toolResult.result.indexOf(offloadPrefix, startIndex);
5876
+ if (prefixIndex === -1) break;
5877
+ const pathStart = prefixIndex + offloadPrefix.length;
5878
+ const closingBracket = toolResult.result.indexOf("]", pathStart);
5879
+ if (closingBracket === -1) break;
5880
+ const bracketContent = toolResult.result.slice(pathStart, closingBracket);
5881
+ const separatorIndex = bracketContent.indexOf(" \u2014 ");
5882
+ const pathText = separatorIndex === -1 ? bracketContent.trim() : bracketContent.slice(0, separatorIndex).trim();
5883
+ if (pathText) {
5884
+ references.add(pathText);
5885
+ }
5886
+ startIndex = closingBracket + 1;
5887
+ }
5888
+ }
5889
+ }
5890
+ if (state.planPath) {
5891
+ references.add(state.planPath);
5892
+ }
5893
+ return [...references].slice(0, 8);
5894
+ }
5895
+ buildContextBudgetBreakdown(details) {
5896
+ const conversationBreakdown = this.estimateConversationBreakdown(details.historyMessages);
5897
+ const currentTurnTokens = this.estimateTextTokens(details.currentTurnContent);
5898
+ const toolDefinitionTokens = this.estimateToolDefinitionTokens(
5899
+ details.localTools,
5900
+ details.builtinToolSchemas
5901
+ );
5902
+ const reservedOutputTokens = this.resolveReservedOutputTokens(details.contextLimitTokens);
5903
+ const effectiveInputBudgetTokens = this.resolveEffectiveInputBudgetTokens(details.contextLimitTokens);
5904
+ const summaryTokens = details.summaryText ? this.estimateTextTokens(
5905
+ `${_AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX}
5906
+
5907
+ ${details.summaryText}
5908
+
5909
+ Do NOT redo any of the above work.`
5910
+ ) : void 0;
5911
+ return {
5912
+ historyTokens: conversationBreakdown.historyTokens,
5913
+ toolOutputTokens: conversationBreakdown.toolOutputTokens,
5914
+ currentTurnTokens,
5915
+ toolDefinitionTokens,
5916
+ ...summaryTokens ? { summaryTokens } : {},
5917
+ ...reservedOutputTokens ? { reservedOutputTokens } : {},
5918
+ ...effectiveInputBudgetTokens ? { effectiveInputBudgetTokens } : {},
5919
+ estimatedInputTokens: conversationBreakdown.estimatedInputTokens + currentTurnTokens + toolDefinitionTokens
5920
+ };
5921
+ }
5922
+ async emitContextCompactionEvent(onContextCompaction, event) {
5923
+ if (!onContextCompaction) return;
5924
+ try {
5925
+ await onContextCompaction(event);
5926
+ } catch {
5927
+ }
5928
+ }
5929
+ async emitContextNotice(onContextNotice, event) {
5930
+ if (!onContextNotice) return;
5931
+ try {
5932
+ await onContextNotice(event);
5933
+ } catch {
5934
+ }
5935
+ }
5936
+ async buildCompactHistoryMessagesWithLifecycle(state, userContent, sessionIndex, details) {
5937
+ const baseEvent = {
5938
+ mode: details.mode,
5939
+ strategy: details.strategy,
5940
+ sessionIndex: sessionIndex + 1,
5941
+ model: details.model,
5942
+ estimatedTokens: details.beforeTokens,
5943
+ thresholdTokens: details.thresholdTokens,
5944
+ contextLimitTokens: details.contextLimitTokens,
5945
+ effectiveInputBudgetTokens: details.effectiveInputBudgetTokens,
5946
+ reservedOutputTokens: details.reservedOutputTokens,
5947
+ beforeTokens: details.beforeTokens,
5948
+ afterTokens: details.afterTokens,
5949
+ breakdown: details.breakdown
5950
+ };
5951
+ await this.emitContextCompactionEvent(details.onContextCompaction, {
5952
+ phase: "start",
5953
+ ...baseEvent
5954
+ });
5955
+ const compactMessages = this.buildCompactHistoryMessages(
5956
+ state,
5957
+ userContent,
5958
+ details.compactInstructions,
5959
+ details.mode
5960
+ );
5961
+ await this.emitContextCompactionEvent(details.onContextCompaction, {
5962
+ phase: "complete",
5963
+ ...baseEvent
5964
+ });
5965
+ return compactMessages;
5966
+ }
5967
+ buildCompactHistoryMessages(state, userContent, compactInstructions, mode = "auto") {
5968
+ const summary = this.generateCompactSummary(state, compactInstructions);
5969
+ const prefix = mode === "forced" ? _AgentsEndpoint.FORCED_COMPACT_SUMMARY_PREFIX : _AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX;
5970
+ return [
5971
+ {
5972
+ role: "system",
5973
+ content: `${prefix}
5974
+
5975
+ ${summary}
5976
+
5977
+ Do NOT redo any of the above work.`
5978
+ },
5979
+ {
5980
+ role: "user",
5981
+ content: userContent
5982
+ }
5983
+ ];
5984
+ }
5985
+ isCompactHistoryMessageSet(messages) {
5986
+ if (messages.length === 0) return false;
5987
+ const firstMessage = messages[0];
5988
+ return firstMessage?.role === "system" && typeof firstMessage.content === "string" && (firstMessage.content.startsWith(_AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX) || firstMessage.content.startsWith(_AgentsEndpoint.FORCED_COMPACT_SUMMARY_PREFIX));
5989
+ }
5659
5990
  /**
5660
5991
  * Generate a compact summary of prior work for continuation context.
5661
5992
  * Used when compact mode is enabled to keep token usage low.
5662
5993
  */
5663
- generateCompactSummary(state) {
5664
- const sessionSummaries = (state.sessions ?? []).map(
5665
- (s) => `- Session ${s.index}: ${s.stopReason} ($${s.cost.toFixed(4)}) -- ${s.outputPreview.slice(0, 100)}`
5994
+ generateCompactSummary(state, compactInstructions) {
5995
+ const recentSessions = (state.sessions ?? []).slice(-5);
5996
+ const sessionSummaries = recentSessions.map(
5997
+ (session) => `- Session ${session.index}: ${session.stopReason} ($${session.cost.toFixed(4)}) ${session.outputPreview.slice(0, 160)}`
5666
5998
  ).join("\n");
5999
+ const candidatePaths = Array.from(
6000
+ new Set(
6001
+ [
6002
+ ...state.bestCandidatePath ? [state.bestCandidatePath] : [],
6003
+ ...state.candidatePaths ?? []
6004
+ ].filter(Boolean)
6005
+ )
6006
+ ).slice(0, 8);
6007
+ const verificationSummary = state.bestCandidateVerified ? "Latest candidate verified." : state.bestCandidateNeedsVerification ? "Latest candidate still needs verification." : state.lastVerificationPassed ? "Latest verification passed." : state.verificationRequired ? "Verification is still required." : "No verification requirement recorded.";
6008
+ const artifactReferences = this.extractArtifactReferences(state);
6009
+ const pendingNextStep = state.lastStopReason === "complete" ? "Confirm nothing else remains before declaring the task complete." : `Continue the ${state.workflowPhase || "research"} phase without redoing prior work.`;
6010
+ const instructions = compactInstructions || this.buildDefaultCompactInstructions();
5667
6011
  return [
5668
- `Task: ${state.taskName}`,
5669
- `Status: ${state.status}`,
5670
- `Workflow phase: ${state.workflowPhase || "research"}`,
5671
- `Sessions completed: ${state.sessionCount}`,
5672
- `Total cost: $${state.totalCost.toFixed(4)}`,
5673
- ...state.planPath ? [`Plan path: ${state.planPath}`] : [],
5674
- ...state.planWritten ? ["Plan written: yes"] : [],
5675
- ...state.bestCandidatePath ? [
5676
- `Best candidate: ${state.bestCandidatePath}`,
5677
- ...state.bestCandidateReason ? [`Candidate reason: ${state.bestCandidateReason}`] : []
5678
- ] : [],
5679
- ...state.bootstrapContext ? ["", state.bootstrapContext] : [],
6012
+ "Task + Phase",
6013
+ `- Task: ${state.taskName}`,
6014
+ `- Status: ${state.status}`,
6015
+ `- Workflow phase: ${state.workflowPhase || "research"}`,
6016
+ `- Sessions completed: ${state.sessionCount}`,
6017
+ `- Total cost: $${state.totalCost.toFixed(4)}`,
6018
+ "",
6019
+ "Completed Work",
6020
+ ...sessionSummaries ? [sessionSummaries] : ["- No completed session summaries recorded yet."],
6021
+ "",
6022
+ "Changed Files / Candidate Paths",
6023
+ ...candidatePaths.length > 0 ? candidatePaths.map((candidatePath) => `- ${candidatePath}`) : ["- No candidate paths recorded yet."],
6024
+ ...state.planPath ? [`- Plan path: ${state.planPath}`] : [],
6025
+ ...state.planWritten ? ["- Planning artifact has been written."] : [],
6026
+ ...state.bestCandidateReason ? [`- Best candidate rationale: ${state.bestCandidateReason}`] : [],
6027
+ "",
6028
+ "Verification Status",
6029
+ `- ${verificationSummary}`,
6030
+ ...state.lastError ? [`- Last error: ${state.lastError}`] : [],
6031
+ "",
6032
+ "Pending Next Step",
6033
+ `- ${pendingNextStep}`,
5680
6034
  "",
5681
- "Session history:",
5682
- sessionSummaries,
6035
+ "Artifact / Tool Output References",
6036
+ ...artifactReferences.length > 0 ? artifactReferences.map((reference) => `- ${reference}`) : ["- No offloaded artifacts recorded."],
5683
6037
  "",
5684
- "Last output (truncated):",
5685
- (state.lastOutput || "").slice(0, 1500)
6038
+ "Compaction Instructions",
6039
+ `- ${instructions}`,
6040
+ "",
6041
+ "Last Output (truncated)",
6042
+ (state.lastOutput || "").slice(0, 1800) || "- No final output recorded yet."
5686
6043
  ].join("\n");
5687
6044
  }
5688
6045
  /**
5689
6046
  * Build messages for a session, injecting progress context for continuation sessions.
5690
6047
  * Optionally accepts continuation context for marathon resume scenarios.
5691
6048
  */
5692
- buildSessionMessages(originalMessage, state, sessionIndex, maxSessions, localToolNames, continuationContext, workflow, builtinToolIds) {
6049
+ async buildSessionMessages(originalMessage, state, sessionIndex, maxSessions, localToolNames, continuationContext, workflow, builtinToolIds, compactionOptions) {
6050
+ const prepared = await this.prepareSessionContext(
6051
+ originalMessage,
6052
+ state,
6053
+ sessionIndex,
6054
+ maxSessions,
6055
+ localToolNames,
6056
+ continuationContext,
6057
+ workflow,
6058
+ builtinToolIds,
6059
+ compactionOptions
6060
+ );
6061
+ return prepared.messages;
6062
+ }
6063
+ async prepareSessionContext(originalMessage, state, sessionIndex, maxSessions, localToolNames, continuationContext, workflow, builtinToolIds, compactionOptions) {
5693
6064
  const wf = workflow ?? defaultWorkflow;
6065
+ const compactInstructions = compactionOptions?.compactInstructions;
6066
+ const resolvedStrategy = continuationContext?.compact ? "summary_fallback" : this.resolveCompactStrategy(compactionOptions?.compactStrategy, compactionOptions?.model);
6067
+ const requestContextManagement = resolvedStrategy === "provider_native" && typeof compactionOptions?.autoCompactTokenThreshold === "number" && compactionOptions.autoCompactTokenThreshold > 0 ? {
6068
+ compactStrategy: resolvedStrategy,
6069
+ ...compactInstructions ? { compactInstructions } : {},
6070
+ nativeCompaction: {
6071
+ provider: "anthropic",
6072
+ thresholdTokens: compactionOptions.autoCompactTokenThreshold
6073
+ }
6074
+ } : void 0;
6075
+ const maybeEmitToolDefinitionWarning = async (breakdown) => {
6076
+ if (sessionIndex !== 0) return;
6077
+ if (typeof compactionOptions?.contextLimitTokens !== "number" || compactionOptions.contextLimitTokens <= 0) {
6078
+ return;
6079
+ }
6080
+ if (breakdown.toolDefinitionTokens <= 0) return;
6081
+ const ratio = breakdown.toolDefinitionTokens / compactionOptions.contextLimitTokens;
6082
+ if (ratio < 0.1) return;
6083
+ await this.emitContextNotice(compactionOptions?.onContextNotice, {
6084
+ kind: "tool_definitions_warning",
6085
+ sessionIndex: sessionIndex + 1,
6086
+ model: compactionOptions?.model,
6087
+ message: `Tool definitions consume about ${(ratio * 100).toFixed(1)}% of the ${compactionOptions.contextLimitTokens.toLocaleString()} token context window. Reduce the enabled tool set if context pressure remains high.`,
6088
+ estimatedTokens: breakdown.toolDefinitionTokens,
6089
+ contextLimitTokens: compactionOptions.contextLimitTokens,
6090
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6091
+ reservedOutputTokens: breakdown.reservedOutputTokens
6092
+ });
6093
+ };
6094
+ const buildNativeCompactionEvent = (mode, breakdown) => {
6095
+ if (resolvedStrategy !== "provider_native" || typeof compactionOptions?.autoCompactTokenThreshold !== "number" || compactionOptions.autoCompactTokenThreshold <= 0 || breakdown.estimatedInputTokens < compactionOptions.autoCompactTokenThreshold) {
6096
+ return void 0;
6097
+ }
6098
+ return {
6099
+ mode,
6100
+ strategy: resolvedStrategy,
6101
+ sessionIndex: sessionIndex + 1,
6102
+ model: compactionOptions?.model,
6103
+ estimatedTokens: breakdown.estimatedInputTokens,
6104
+ thresholdTokens: compactionOptions.autoCompactTokenThreshold,
6105
+ contextLimitTokens: compactionOptions.contextLimitTokens,
6106
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6107
+ reservedOutputTokens: breakdown.reservedOutputTokens,
6108
+ beforeTokens: breakdown.estimatedInputTokens,
6109
+ breakdown
6110
+ };
6111
+ };
5694
6112
  const currentPhase = wf.phases.find((p) => p.name === state.workflowPhase);
5695
6113
  const toolGuidanceLines = currentPhase?.buildToolGuidance(state) ?? [];
5696
6114
  const isDeployWorkflow = wf.name === "deploy";
@@ -5719,25 +6137,16 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5719
6137
  if (continuationContext && sessionIndex === 0) {
5720
6138
  const defaultContinueMessage = "Continue the task. Review your prior work above and proceed with any remaining work. If everything is already complete, respond with TASK_COMPLETE.";
5721
6139
  const userMessage = continuationContext.newUserMessage || defaultContinueMessage;
5722
- if (continuationContext.compact) {
5723
- const summary = this.generateCompactSummary(state);
5724
- const messages2 = [
5725
- {
5726
- role: "system",
5727
- content: `You are continuing a previously completed task. Here is a summary of prior work:
5728
-
5729
- ${summary}
5730
-
5731
- Do NOT redo any of the above work.`
5732
- },
5733
- {
5734
- role: "user",
5735
- content: [userMessage, phaseBlock, toolsBlock, bootstrapBlock, candidateBlock, "", multiSessionInstruction].join("\n")
5736
- }
5737
- ];
5738
- return messages2;
5739
- }
5740
- const messages = [
6140
+ const userContent = [
6141
+ userMessage,
6142
+ phaseBlock,
6143
+ toolsBlock,
6144
+ bootstrapBlock,
6145
+ candidateBlock,
6146
+ "",
6147
+ multiSessionInstruction
6148
+ ].join("\n");
6149
+ const fullHistoryMessages = [
5741
6150
  ...continuationContext.previousMessages,
5742
6151
  {
5743
6152
  role: "system",
@@ -5745,14 +6154,79 @@ Do NOT redo any of the above work.`
5745
6154
  },
5746
6155
  {
5747
6156
  role: "user",
5748
- content: [userMessage, phaseBlock, toolsBlock, bootstrapBlock, candidateBlock, "", multiSessionInstruction].join("\n")
6157
+ content: userContent
5749
6158
  }
5750
6159
  ];
5751
- return messages;
6160
+ const summaryText = this.generateCompactSummary(state, compactInstructions);
6161
+ const breakdown = this.buildContextBudgetBreakdown({
6162
+ historyMessages: continuationContext.previousMessages,
6163
+ currentTurnContent: userContent,
6164
+ localTools: compactionOptions?.localTools,
6165
+ builtinToolSchemas: compactionOptions?.builtinToolSchemas || [],
6166
+ summaryText,
6167
+ contextLimitTokens: compactionOptions?.contextLimitTokens
6168
+ });
6169
+ await maybeEmitToolDefinitionWarning(breakdown);
6170
+ if (continuationContext.compact) {
6171
+ return {
6172
+ messages: await this.buildCompactHistoryMessagesWithLifecycle(
6173
+ state,
6174
+ userContent,
6175
+ sessionIndex,
6176
+ {
6177
+ mode: "forced",
6178
+ strategy: "summary_fallback",
6179
+ model: compactionOptions?.model,
6180
+ beforeTokens: breakdown.estimatedInputTokens,
6181
+ afterTokens: (breakdown.summaryTokens || 0) + breakdown.currentTurnTokens + breakdown.toolDefinitionTokens,
6182
+ thresholdTokens: compactionOptions?.autoCompactTokenThreshold,
6183
+ contextLimitTokens: compactionOptions?.contextLimitTokens,
6184
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6185
+ reservedOutputTokens: breakdown.reservedOutputTokens,
6186
+ breakdown,
6187
+ onContextCompaction: compactionOptions?.onContextCompaction,
6188
+ compactInstructions
6189
+ }
6190
+ ),
6191
+ requestContextManagement
6192
+ };
6193
+ }
6194
+ if (resolvedStrategy === "summary_fallback" && typeof compactionOptions?.autoCompactTokenThreshold === "number" && compactionOptions.autoCompactTokenThreshold > 0 && breakdown.estimatedInputTokens >= compactionOptions.autoCompactTokenThreshold) {
6195
+ return {
6196
+ messages: await this.buildCompactHistoryMessagesWithLifecycle(
6197
+ state,
6198
+ userContent,
6199
+ sessionIndex,
6200
+ {
6201
+ mode: "auto",
6202
+ strategy: "summary_fallback",
6203
+ model: compactionOptions?.model,
6204
+ beforeTokens: breakdown.estimatedInputTokens,
6205
+ afterTokens: (breakdown.summaryTokens || 0) + breakdown.currentTurnTokens + breakdown.toolDefinitionTokens,
6206
+ thresholdTokens: compactionOptions?.autoCompactTokenThreshold,
6207
+ contextLimitTokens: compactionOptions?.contextLimitTokens,
6208
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6209
+ reservedOutputTokens: breakdown.reservedOutputTokens,
6210
+ breakdown,
6211
+ onContextCompaction: compactionOptions?.onContextCompaction,
6212
+ compactInstructions
6213
+ }
6214
+ ),
6215
+ requestContextManagement
6216
+ };
6217
+ }
6218
+ return {
6219
+ messages: fullHistoryMessages,
6220
+ requestContextManagement,
6221
+ pendingNativeCompactionEvent: buildNativeCompactionEvent("auto", breakdown)
6222
+ };
5752
6223
  }
5753
6224
  if (sessionIndex === 0) {
5754
6225
  const content2 = [originalMessage, phaseBlock, toolsBlock, bootstrapBlock, candidateBlock, "", multiSessionInstruction].join("\n");
5755
- return [{ role: "user", content: content2 }];
6226
+ return {
6227
+ messages: [{ role: "user", content: content2 }],
6228
+ requestContextManagement
6229
+ };
5756
6230
  }
5757
6231
  const recentSessions = state.sessions.slice(-5);
5758
6232
  const progressSummary = recentSessions.map(
@@ -5786,10 +6260,49 @@ Do NOT redo any of the above work.`
5786
6260
  ...historyMessages.slice(-MAX_HISTORY_MESSAGES)
5787
6261
  ];
5788
6262
  }
5789
- return [
6263
+ const summaryText = this.generateCompactSummary(state, compactInstructions);
6264
+ const breakdown = this.buildContextBudgetBreakdown({
6265
+ historyMessages,
6266
+ currentTurnContent: continuationContent,
6267
+ localTools: compactionOptions?.localTools,
6268
+ builtinToolSchemas: compactionOptions?.builtinToolSchemas || [],
6269
+ summaryText,
6270
+ contextLimitTokens: compactionOptions?.contextLimitTokens
6271
+ });
6272
+ await maybeEmitToolDefinitionWarning(breakdown);
6273
+ const messages = [
5790
6274
  ...historyMessages,
5791
6275
  { role: "user", content: continuationContent }
5792
6276
  ];
6277
+ if (resolvedStrategy === "summary_fallback" && typeof compactionOptions?.autoCompactTokenThreshold === "number" && compactionOptions.autoCompactTokenThreshold > 0 && breakdown.estimatedInputTokens >= compactionOptions.autoCompactTokenThreshold) {
6278
+ return {
6279
+ messages: await this.buildCompactHistoryMessagesWithLifecycle(
6280
+ state,
6281
+ continuationContent,
6282
+ sessionIndex,
6283
+ {
6284
+ mode: "auto",
6285
+ strategy: "summary_fallback",
6286
+ model: compactionOptions?.model,
6287
+ beforeTokens: breakdown.estimatedInputTokens,
6288
+ afterTokens: (breakdown.summaryTokens || 0) + breakdown.currentTurnTokens + breakdown.toolDefinitionTokens,
6289
+ thresholdTokens: compactionOptions?.autoCompactTokenThreshold,
6290
+ contextLimitTokens: compactionOptions?.contextLimitTokens,
6291
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6292
+ reservedOutputTokens: breakdown.reservedOutputTokens,
6293
+ breakdown,
6294
+ onContextCompaction: compactionOptions?.onContextCompaction,
6295
+ compactInstructions
6296
+ }
6297
+ ),
6298
+ requestContextManagement
6299
+ };
6300
+ }
6301
+ return {
6302
+ messages,
6303
+ requestContextManagement,
6304
+ pendingNativeCompactionEvent: buildNativeCompactionEvent("auto", breakdown)
6305
+ };
5793
6306
  }
5794
6307
  const recoveryMessage = this.buildStuckTurnRecoveryMessage(state, wf);
5795
6308
  const content = [
@@ -5809,7 +6322,10 @@ Do NOT redo any of the above work.`
5809
6322
  "",
5810
6323
  "Continue where you left off. Do not redo previous work. If the task is already complete, respond with TASK_COMPLETE."
5811
6324
  ].join("\n");
5812
- return [{ role: "user", content }];
6325
+ return {
6326
+ messages: [{ role: "user", content }],
6327
+ requestContextManagement
6328
+ };
5813
6329
  }
5814
6330
  /**
5815
6331
  * Upsert a record to sync long-task progress to the dashboard.
@@ -5859,6 +6375,8 @@ Do NOT redo any of the above work.`
5859
6375
  }
5860
6376
  }
5861
6377
  };
6378
+ _AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX = "You are continuing a long-running task. Here is a compact summary of prior work:";
6379
+ _AgentsEndpoint.FORCED_COMPACT_SUMMARY_PREFIX = "You are continuing a previously completed task. Here is a summary of prior work:";
5862
6380
  /** Stop phrases that indicate the agent considers its task complete. */
5863
6381
  _AgentsEndpoint.STOP_PHRASES = [
5864
6382
  "DONE:",