( commands: Command[], contextWindowTokens?: number, )
| 68 | const MIN_DESC_LENGTH = 20 |
| 69 | |
| 70 | export function formatCommandsWithinBudget( |
| 71 | commands: Command[], |
| 72 | contextWindowTokens?: number, |
| 73 | ): string { |
| 74 | if (commands.length === 0) return '' |
| 75 | |
| 76 | const budget = getCharBudget(contextWindowTokens) |
| 77 | |
| 78 | // Try full descriptions first |
| 79 | const fullEntries = commands.map(cmd => ({ |
| 80 | cmd, |
| 81 | full: formatCommandDescription(cmd), |
| 82 | })) |
| 83 | // join('\n') produces N-1 newlines for N entries |
| 84 | const fullTotal = |
| 85 | fullEntries.reduce((sum, e) => sum + stringWidth(e.full), 0) + |
| 86 | (fullEntries.length - 1) |
| 87 | |
| 88 | if (fullTotal <= budget) { |
| 89 | return fullEntries.map(e => e.full).join('\n') |
| 90 | } |
| 91 | |
| 92 | // Partition into bundled (never truncated) and rest |
| 93 | const bundledIndices = new Set<number>() |
| 94 | const restCommands: Command[] = [] |
| 95 | for (let i = 0; i < commands.length; i++) { |
| 96 | const cmd = commands[i]! |
| 97 | if (cmd.type === 'prompt' && cmd.source === 'bundled') { |
| 98 | bundledIndices.add(i) |
| 99 | } else { |
| 100 | restCommands.push(cmd) |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | // Compute space used by bundled skills (full descriptions, always preserved) |
| 105 | const bundledChars = fullEntries.reduce( |
| 106 | (sum, e, i) => |
| 107 | bundledIndices.has(i) ? sum + stringWidth(e.full) + 1 : sum, |
| 108 | 0, |
| 109 | ) |
| 110 | const remainingBudget = budget - bundledChars |
| 111 | |
| 112 | // Calculate max description length for non-bundled commands |
| 113 | if (restCommands.length === 0) { |
| 114 | return fullEntries.map(e => e.full).join('\n') |
| 115 | } |
| 116 | |
| 117 | const restNameOverhead = |
| 118 | restCommands.reduce((sum, cmd) => sum + stringWidth(cmd.name) + 4, 0) + |
| 119 | (restCommands.length - 1) |
| 120 | const availableForDescs = remainingBudget - restNameOverhead |
| 121 | const maxDescLen = Math.floor(availableForDescs / restCommands.length) |
| 122 | |
| 123 | if (maxDescLen < MIN_DESC_LENGTH) { |
| 124 | // Extreme case: non-bundled go names-only, bundled keep descriptions |
| 125 | if (process.env.USER_TYPE === 'ant') { |
| 126 | logEvent('tengu_skill_descriptions_truncated', { |
| 127 | skill_count: commands.length, |
no test coverage detected