picocode-core 0.9.173 → 0.9.175
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/package.json +1 -1
- package/src/agent-context.js +39 -0
- package/src/controller.js +40 -37
- package/src/derive.js +14 -1
- package/src/tool-trimming.js +138 -0
package/package.json
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { createContextTracker } from './context.js'
|
|
3
|
+
import { buildSystemPrompt } from './system-prompt.js'
|
|
4
|
+
import { memoryIndex } from './memory.js'
|
|
5
|
+
|
|
6
|
+
export async function createAgentContext(boot, { userTools = [], isolated = false, instructions = '' } = {}) {
|
|
7
|
+
const files = [...boot.startupContext.files]
|
|
8
|
+
if (isolated) {
|
|
9
|
+
const known = new Set(files.map((file) => file.path))
|
|
10
|
+
for (const path of boot.tracker.loaded) {
|
|
11
|
+
if (known.has(path)) continue
|
|
12
|
+
const content = await readFile(path, 'utf8').catch(() => null)
|
|
13
|
+
if (content !== null) files.push({ path, content })
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const tracker = isolated
|
|
17
|
+
? createContextTracker({ stopDir: boot.startupContext.stopDir, loaded: new Set(files.map((file) => file.path)) })
|
|
18
|
+
: boot.tracker
|
|
19
|
+
const system = buildSystemPrompt({
|
|
20
|
+
cwd: boot.cwd,
|
|
21
|
+
contextFiles: files,
|
|
22
|
+
skills: boot.skills.list(),
|
|
23
|
+
memoryIndexText: memoryIndex(await boot.memory.list().catch(() => []), boot.root),
|
|
24
|
+
})
|
|
25
|
+
return {
|
|
26
|
+
system: instructions ? `${system}\n\n${instructions}` : system,
|
|
27
|
+
tools: {
|
|
28
|
+
cwd: boot.cwd,
|
|
29
|
+
env: boot.env,
|
|
30
|
+
tracker,
|
|
31
|
+
skills: boot.skills,
|
|
32
|
+
memory: boot.memory,
|
|
33
|
+
shells: boot.shells,
|
|
34
|
+
hostTools: (boot.hostTools ?? []).filter((tool) => !isolated || !tool.name.startsWith('browser_')),
|
|
35
|
+
userTools,
|
|
36
|
+
mcpTools: boot.mcp.tools(),
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/controller.js
CHANGED
|
@@ -3,7 +3,7 @@ import { writeFile } from 'node:fs/promises'
|
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
import { makeEvent } from './events.js'
|
|
5
5
|
import { createSession, createEphemeralSession, forkSession, openSession, loadSession, listSessions, deleteSession, appendSessionEvent, onSessionWriteError } from './session.js'
|
|
6
|
-
import {
|
|
6
|
+
import { createAgentContext } from './agent-context.js'
|
|
7
7
|
import { deriveState, userEntries, rewindStats } from './derive.js'
|
|
8
8
|
import { appendPrompt } from './history.js'
|
|
9
9
|
import { runTurn, summarizeText, compactHistory, compactProgress } from './agent.js'
|
|
@@ -11,6 +11,7 @@ import { createAgentManager } from './agents.js'
|
|
|
11
11
|
import { runDeliberation, validateDeliberation } from './deliberation.js'
|
|
12
12
|
import { deliberationsFromEvents } from './deliberation-history.js'
|
|
13
13
|
import { compactionPrompt, formatCompactSummary, summarySections, compactionKeepFrom } from './compaction.js'
|
|
14
|
+
import { completedToolCalls, TOOL_TRIM_VERSION } from './tool-trimming.js'
|
|
14
15
|
import { createToolset } from './tools/index.js'
|
|
15
16
|
import { defaultTitle } from './tools/recorder.js'
|
|
16
17
|
import { scanUserTools } from './user-tools.js'
|
|
@@ -53,7 +54,6 @@ export const SESSION_COLORS = {
|
|
|
53
54
|
gray: '#9ca3af',
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
const WORKER_TOOLS = ['read', 'write', 'edit', 'bash', 'glob', 'grep', 'shell_output', 'shell_kill']
|
|
57
57
|
const AGENT_TOOLS = ['agent_plan', 'agent_start', 'agent_list', 'agent_collect', 'agent_cancel']
|
|
58
58
|
const CONTEXT_COLORS = ['#67b7ff', '#c792ea', '#f7c66a', '#f78c6c', '#6be795']
|
|
59
59
|
|
|
@@ -202,23 +202,16 @@ export function createController({ boot }) {
|
|
|
202
202
|
const sessionId = state.session?.id
|
|
203
203
|
if (!sessionId) throw new Error('worker requires an active session')
|
|
204
204
|
const scratchpad = ensureDir(agentScratchDir(boot.root, sessionId, agent.id))
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
const requestedTools = agent.tools?.length ? agent.tools.filter((name) => availableNames.includes(name)) : availableNames
|
|
205
|
+
const scan = await refreshProjectIndexes()
|
|
206
|
+
const context = await createAgentContext(boot, { userTools: scan.tools, isolated: true, instructions: workerSystemPrompt(scratchpad) })
|
|
208
207
|
const { tools, recorder } = createToolset({
|
|
209
|
-
|
|
208
|
+
...context.tools,
|
|
210
209
|
env: { ...boot.env, PICO_SCRATCHPAD: scratchpad },
|
|
211
|
-
// a private tracker seeded from the main one: a worker may read an
|
|
212
|
-
// AGENTS.md the main agent has not seen, and consuming it from the
|
|
213
|
-
// shared set would mean the main agent never receives it
|
|
214
|
-
tracker: createContextTracker({ stopDir: boot.startupContext.stopDir, loaded: new Set(boot.tracker.loaded) }),
|
|
215
|
-
shells: boot.shells,
|
|
216
210
|
sessionId,
|
|
217
211
|
sessionFile: state.session?.file,
|
|
218
212
|
signal,
|
|
219
213
|
maxToolCalls: 30,
|
|
220
|
-
allowNames:
|
|
221
|
-
mcpTools,
|
|
214
|
+
allowNames: agent.tools?.length ? agent.tools : undefined,
|
|
222
215
|
})
|
|
223
216
|
return runTurn({
|
|
224
217
|
history: [{ role: 'user', content: agent.prompt }],
|
|
@@ -227,7 +220,7 @@ export function createController({ boot }) {
|
|
|
227
220
|
modelName: worker.name,
|
|
228
221
|
effort: worker.effort ? 'low' : null,
|
|
229
222
|
auth,
|
|
230
|
-
system:
|
|
223
|
+
system: context.system,
|
|
231
224
|
signal,
|
|
232
225
|
onStream,
|
|
233
226
|
})
|
|
@@ -279,18 +272,16 @@ export function createController({ boot }) {
|
|
|
279
272
|
|
|
280
273
|
const runWorker = async ({ history, role, tools: enabled = true, onStream }) => {
|
|
281
274
|
const scratchpad = ensureDir(agentScratchDir(boot.root, sessionId, `deliberation-${id}-${role}`))
|
|
282
|
-
const
|
|
275
|
+
const scan = await refreshProjectIndexes()
|
|
276
|
+
const context = await createAgentContext(boot, { userTools: scan.tools, isolated: true, instructions: participantSystemPrompt(scratchpad) })
|
|
283
277
|
const toolset = createToolset({
|
|
284
|
-
|
|
278
|
+
...context.tools,
|
|
285
279
|
env: { ...boot.env, PICO_SCRATCHPAD: scratchpad },
|
|
286
|
-
tracker: createContextTracker({ stopDir: boot.startupContext.stopDir, loaded: new Set(boot.tracker.loaded) }),
|
|
287
|
-
shells: boot.shells,
|
|
288
280
|
sessionId,
|
|
289
281
|
sessionFile: state.session?.file,
|
|
290
282
|
signal,
|
|
291
283
|
maxToolCalls: 30,
|
|
292
|
-
allowNames: enabled ?
|
|
293
|
-
mcpTools,
|
|
284
|
+
allowNames: enabled ? undefined : [],
|
|
294
285
|
})
|
|
295
286
|
return runTurn({
|
|
296
287
|
history,
|
|
@@ -299,7 +290,7 @@ export function createController({ boot }) {
|
|
|
299
290
|
modelName: roleWorkers[role].name,
|
|
300
291
|
effort: roleWorkers[role].effort ? 'low' : null,
|
|
301
292
|
auth: roleAuths[role],
|
|
302
|
-
system:
|
|
293
|
+
system: context.system,
|
|
303
294
|
signal,
|
|
304
295
|
onStream,
|
|
305
296
|
})
|
|
@@ -609,24 +600,16 @@ export function createController({ boot }) {
|
|
|
609
600
|
const { tracker } = boot
|
|
610
601
|
const loadedBefore = new Set(tracker.loaded)
|
|
611
602
|
const userToolScan = await refreshProjectIndexes()
|
|
612
|
-
const
|
|
603
|
+
const context = await createAgentContext(boot, { userTools: userToolScan.tools })
|
|
613
604
|
const { tools, recorder } = createToolset({
|
|
614
|
-
|
|
615
|
-
env: boot.env,
|
|
616
|
-
hostTools: boot.hostTools ?? [],
|
|
617
|
-
tracker,
|
|
618
|
-
skills: freshSkills,
|
|
619
|
-
shells: boot.shells,
|
|
605
|
+
...context.tools,
|
|
620
606
|
sessionId: state.session?.id,
|
|
621
607
|
sessionFile: state.session?.file,
|
|
622
608
|
wakeups: boot.wakeups,
|
|
623
|
-
memory: boot.memory,
|
|
624
609
|
agents: boot.researchModel ? agents : null,
|
|
625
610
|
deliberations: boot.deliberationModel || (boot.proposerModel && boot.reviewerModel) ? deliberations : null,
|
|
626
611
|
onAgentsCollected: discardCollectedAgentNotes,
|
|
627
612
|
askUser,
|
|
628
|
-
mcpTools: boot.mcp.tools(),
|
|
629
|
-
userTools: userToolScan.tools,
|
|
630
613
|
signal: controller.signal,
|
|
631
614
|
maxAgentStarts: researchAgentLimit ? 100 : undefined,
|
|
632
615
|
requireAgentPlan: !!researchAgentLimit,
|
|
@@ -645,12 +628,7 @@ export function createController({ boot }) {
|
|
|
645
628
|
modelName: state.model.name,
|
|
646
629
|
effort: effortApplies() ? state.effort ?? 'auto' : null,
|
|
647
630
|
auth,
|
|
648
|
-
system:
|
|
649
|
-
cwd: boot.cwd,
|
|
650
|
-
contextFiles: boot.startupContext.files,
|
|
651
|
-
skills: freshSkills.list(),
|
|
652
|
-
memoryIndexText: memoryIndex(await boot.memory.list().catch(() => []), boot.root),
|
|
653
|
-
}),
|
|
631
|
+
system: context.system,
|
|
654
632
|
signal: controller.signal,
|
|
655
633
|
onStream: streamHandler(recorder, controller),
|
|
656
634
|
})
|
|
@@ -847,6 +825,29 @@ export function createController({ boot }) {
|
|
|
847
825
|
flash(value ? `session color: ${names[values.indexOf(value)] || value}` : 'session color cleared')
|
|
848
826
|
}
|
|
849
827
|
|
|
828
|
+
function changeToolTrims(type, callIds) {
|
|
829
|
+
if (state.busy || state.compacting) return flash('error: finish or interrupt the current turn first')
|
|
830
|
+
if (!Array.isArray(callIds) || !callIds.length || callIds.some((id) => typeof id !== 'string' || !id)) {
|
|
831
|
+
return flash('error: tool call IDs must be a non-empty array of strings')
|
|
832
|
+
}
|
|
833
|
+
const ids = [...new Set(callIds)]
|
|
834
|
+
const completed = completedToolCalls(state.derived.providerHistory)
|
|
835
|
+
const unavailable = ids.filter((id) => !completed.has(id))
|
|
836
|
+
if (unavailable.length) return flash(`error: tool calls are not completed in current context: ${unavailable.join(', ')}`)
|
|
837
|
+
persist(makeEvent(type, { callIds: ids, version: TOOL_TRIM_VERSION }))
|
|
838
|
+
ensureSession()
|
|
839
|
+
reDerive()
|
|
840
|
+
return true
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function trimTools(callIds) {
|
|
844
|
+
return changeToolTrims('tool_trim', callIds)
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function restoreTools(callIds) {
|
|
848
|
+
return changeToolTrims('tool_restore', callIds)
|
|
849
|
+
}
|
|
850
|
+
|
|
850
851
|
function clear() {
|
|
851
852
|
if (state.busy) return flash('finish or interrupt the current turn first')
|
|
852
853
|
persist(makeEvent('clear', {}))
|
|
@@ -1366,6 +1367,8 @@ export function createController({ boot }) {
|
|
|
1366
1367
|
send,
|
|
1367
1368
|
interrupt,
|
|
1368
1369
|
compact,
|
|
1370
|
+
trimTools,
|
|
1371
|
+
restoreTools,
|
|
1369
1372
|
answerQuestion,
|
|
1370
1373
|
cancelQuestion,
|
|
1371
1374
|
recallPending,
|
package/src/derive.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { continuationMessage } from './compaction.js'
|
|
2
2
|
import { applySteering } from './steer.js'
|
|
3
|
+
import { annotateToolContext, applyToolTrims } from './tool-trimming.js'
|
|
3
4
|
|
|
4
5
|
const DROPPING_MODES = ['both', 'chat', 'summary']
|
|
5
6
|
|
|
@@ -178,6 +179,7 @@ export function deriveState(events) {
|
|
|
178
179
|
loadedContext: new Set(),
|
|
179
180
|
toolItems: new Map(),
|
|
180
181
|
latestCompactIndex,
|
|
182
|
+
trimmedToolIds: new Set(),
|
|
181
183
|
}
|
|
182
184
|
|
|
183
185
|
for (const event of effectiveEvents) {
|
|
@@ -256,11 +258,20 @@ export function deriveState(events) {
|
|
|
256
258
|
state.lastPromptTokens = 0
|
|
257
259
|
break
|
|
258
260
|
}
|
|
261
|
+
case 'tool_trim':
|
|
262
|
+
for (const callId of event.data.callIds || []) state.trimmedToolIds.add(callId)
|
|
263
|
+
state.lastPromptTokens = 0
|
|
264
|
+
break
|
|
265
|
+
case 'tool_restore':
|
|
266
|
+
for (const callId of event.data.callIds || []) state.trimmedToolIds.delete(callId)
|
|
267
|
+
state.lastPromptTokens = 0
|
|
268
|
+
break
|
|
259
269
|
case 'clear':
|
|
260
270
|
state.transcript = []
|
|
261
271
|
state.providerHistory = []
|
|
262
272
|
state.historyEventIds = []
|
|
263
273
|
state.toolItems = new Map()
|
|
274
|
+
state.trimmedToolIds = new Set()
|
|
264
275
|
state.lastPromptTokens = 0
|
|
265
276
|
break
|
|
266
277
|
case 'context_file':
|
|
@@ -287,7 +298,9 @@ export function deriveState(events) {
|
|
|
287
298
|
}
|
|
288
299
|
}
|
|
289
300
|
|
|
290
|
-
|
|
301
|
+
const effectiveHistory = elideStaleToolResults(state.providerHistory)
|
|
302
|
+
state.providerHistory = applyToolTrims(effectiveHistory, state.trimmedToolIds)
|
|
303
|
+
annotateToolContext(state, state.providerHistory, state.trimmedToolIds)
|
|
291
304
|
return state
|
|
292
305
|
}
|
|
293
306
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const ALGORITHM = 'tool-trim-v1'
|
|
2
|
+
const ARGUMENT_CHARS = 8000
|
|
3
|
+
const RESULT_CHARS = 12000
|
|
4
|
+
const STRING_CHARS = 6000
|
|
5
|
+
const ARRAY_ITEMS = 40
|
|
6
|
+
const OBJECT_KEYS = 80
|
|
7
|
+
const HEAD_RATIO = 0.6
|
|
8
|
+
|
|
9
|
+
function truncateText(value, limit, label = 'content') {
|
|
10
|
+
const text = String(value ?? '')
|
|
11
|
+
if (text.length <= limit) return text
|
|
12
|
+
const marker = `\n[${label} omitted: ${text.length - limit} or more chars]\n`
|
|
13
|
+
const available = Math.max(0, limit - marker.length)
|
|
14
|
+
const head = Math.floor(available * HEAD_RATIO)
|
|
15
|
+
return text.slice(0, head) + marker + text.slice(text.length - (available - head))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function boundedValue(value, depth = 0) {
|
|
19
|
+
if (typeof value === 'string') return truncateText(value, STRING_CHARS)
|
|
20
|
+
if (value === null || typeof value !== 'object') return value
|
|
21
|
+
if (depth >= 8) return '[nested value omitted]'
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
if (value.length <= ARRAY_ITEMS) return value.map((item) => boundedValue(item, depth + 1))
|
|
24
|
+
const head = Math.ceil(ARRAY_ITEMS * HEAD_RATIO)
|
|
25
|
+
const tail = ARRAY_ITEMS - head
|
|
26
|
+
return [
|
|
27
|
+
...value.slice(0, head).map((item) => boundedValue(item, depth + 1)),
|
|
28
|
+
`[${value.length - ARRAY_ITEMS} array items omitted]`,
|
|
29
|
+
...value.slice(-tail).map((item) => boundedValue(item, depth + 1)),
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
const entries = Object.entries(value)
|
|
33
|
+
const kept = entries.length <= OBJECT_KEYS
|
|
34
|
+
? entries
|
|
35
|
+
: [...entries.slice(0, Math.ceil(OBJECT_KEYS * HEAD_RATIO)), ['_trimmed', `${entries.length - OBJECT_KEYS} object fields omitted`], ...entries.slice(-(OBJECT_KEYS - Math.ceil(OBJECT_KEYS * HEAD_RATIO)))]
|
|
36
|
+
return Object.fromEntries(kept.map(([key, item]) => [key, boundedValue(item, depth + 1)]))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function importantArguments(value) {
|
|
40
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
|
|
41
|
+
return Object.fromEntries(['description', 'name', 'outcome'].filter((key) => key in value).map((key) => [key, boundedValue(value[key])]))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function trimArguments(raw) {
|
|
45
|
+
let parsed
|
|
46
|
+
try {
|
|
47
|
+
parsed = JSON.parse(raw)
|
|
48
|
+
} catch {
|
|
49
|
+
parsed = { _raw: String(raw ?? '') }
|
|
50
|
+
}
|
|
51
|
+
let bounded = boundedValue(parsed)
|
|
52
|
+
let encoded = JSON.stringify(bounded)
|
|
53
|
+
if (encoded.length <= ARGUMENT_CHARS) return encoded
|
|
54
|
+
const important = importantArguments(parsed)
|
|
55
|
+
let previewLimit = ARGUMENT_CHARS
|
|
56
|
+
while (previewLimit > 0) {
|
|
57
|
+
encoded = JSON.stringify({ ...important, _trimmed: truncateText(JSON.stringify(parsed), previewLimit, 'arguments') })
|
|
58
|
+
if (encoded.length <= ARGUMENT_CHARS) return encoded
|
|
59
|
+
previewLimit = Math.floor(previewLimit / 2)
|
|
60
|
+
}
|
|
61
|
+
return JSON.stringify({ _trimmed: truncateText(JSON.stringify(parsed), 1000, 'arguments') })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isImagePart(part) {
|
|
65
|
+
return part && typeof part === 'object' && (part.type === 'image' || part.type === 'image_url' || part.type === 'input_image' || part.source?.type === 'base64')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function trimResult(content) {
|
|
69
|
+
if (!Array.isArray(content)) return truncateText(content, RESULT_CHARS, 'tool result')
|
|
70
|
+
const safe = content.map((part) => isImagePart(part)
|
|
71
|
+
? { type: 'text', text: '[tool image omitted to save context]' }
|
|
72
|
+
: boundedValue(part))
|
|
73
|
+
const encoded = JSON.stringify(safe)
|
|
74
|
+
return encoded.length <= RESULT_CHARS
|
|
75
|
+
? safe
|
|
76
|
+
: [{ type: 'text', text: truncateText(encoded, RESULT_CHARS, 'tool result') }]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function callsIn(history) {
|
|
80
|
+
const calls = new Map()
|
|
81
|
+
history.forEach((message, index) => {
|
|
82
|
+
for (const call of message.tool_calls || []) calls.set(call.id, { call, callIndex: index, result: null, resultIndex: -1 })
|
|
83
|
+
if (message.role === 'tool') {
|
|
84
|
+
const entry = calls.get(message.tool_call_id)
|
|
85
|
+
if (entry) Object.assign(entry, { result: message, resultIndex: index })
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
return calls
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function completedToolCalls(history) {
|
|
92
|
+
return new Map([...callsIn(history)].filter(([, entry]) => entry.result))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function applyToolTrims(history, trimmedIds) {
|
|
96
|
+
if (!trimmedIds.size) return history
|
|
97
|
+
const completed = completedToolCalls(history)
|
|
98
|
+
const callMessages = new Map()
|
|
99
|
+
const resultMessages = new Map()
|
|
100
|
+
for (const id of trimmedIds) {
|
|
101
|
+
const entry = completed.get(id)
|
|
102
|
+
if (!entry) continue
|
|
103
|
+
let callMessage = callMessages.get(entry.callIndex)
|
|
104
|
+
if (!callMessage) {
|
|
105
|
+
callMessage = { ...history[entry.callIndex], tool_calls: history[entry.callIndex].tool_calls.map((call) => ({ ...call, function: { ...call.function } })) }
|
|
106
|
+
callMessages.set(entry.callIndex, callMessage)
|
|
107
|
+
}
|
|
108
|
+
const call = callMessage.tool_calls.find((candidate) => candidate.id === id)
|
|
109
|
+
call.function.arguments = trimArguments(call.function.arguments)
|
|
110
|
+
resultMessages.set(entry.resultIndex, { ...entry.result, content: trimResult(entry.result.content) })
|
|
111
|
+
}
|
|
112
|
+
return history.map((message, index) => callMessages.get(index) || resultMessages.get(index) || message)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function resultText(content) {
|
|
116
|
+
if (typeof content === 'string') return content
|
|
117
|
+
if (Array.isArray(content)) return content.filter((part) => part.type === 'text').map((part) => part.text ?? '').join('\n')
|
|
118
|
+
return JSON.stringify(content ?? '')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function annotateToolContext(state, effectiveHistory, trimmedIds) {
|
|
122
|
+
const completed = completedToolCalls(effectiveHistory)
|
|
123
|
+
for (const item of state.toolItems.values()) {
|
|
124
|
+
const entry = completed.get(item.callId)
|
|
125
|
+
item.contextAvailable = !!entry
|
|
126
|
+
item.contextTrimmed = !!entry && trimmedIds.has(item.callId)
|
|
127
|
+
item.contextTokens = entry
|
|
128
|
+
? Math.ceil((String(entry.call.function.arguments ?? '').length + resultText(entry.result.content).length) / 4)
|
|
129
|
+
: 0
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const TOOL_TRIM_VERSION = {
|
|
134
|
+
algorithm: ALGORITHM,
|
|
135
|
+
argumentChars: ARGUMENT_CHARS,
|
|
136
|
+
resultChars: RESULT_CHARS,
|
|
137
|
+
stringChars: STRING_CHARS,
|
|
138
|
+
}
|