picocode-core 0.9.181 → 0.9.183
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-transcript.js +6 -2
- package/src/agent.js +31 -1
- package/src/controller.js +13 -2
- package/src/tools/recorder.js +10 -2
package/package.json
CHANGED
package/src/agent-transcript.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defaultTitle } from './tools/recorder.js'
|
|
1
|
+
import { defaultTitle, toolResultOutput } from './tools/recorder.js'
|
|
2
2
|
|
|
3
3
|
function parseCallArgs(call) {
|
|
4
4
|
try {
|
|
@@ -30,7 +30,11 @@ function settleTool(tools, event) {
|
|
|
30
30
|
if (!item) return
|
|
31
31
|
item.status = event.type === 'tool_error' ? 'error' : 'done'
|
|
32
32
|
item.error = event.error ? String(event.error) : null
|
|
33
|
-
item.fullOutput =
|
|
33
|
+
item.fullOutput = toolResultOutput(item.name, event.result)
|
|
34
|
+
if (item.name === 'bash') {
|
|
35
|
+
item.exitCode = event.result?.exitCode
|
|
36
|
+
item.background = event.result?.status === 'running'
|
|
37
|
+
}
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
// a deliberation reads as turns: each turn owns the tool runs its
|
package/src/agent.js
CHANGED
|
@@ -98,6 +98,10 @@ export async function summarizeText({ text, modelName, auth }) {
|
|
|
98
98
|
return getText(out.lastResponse?.content || '').trim()
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
function estimateTokens(value) {
|
|
102
|
+
return Math.ceil(JSON.stringify(value ?? '').length / 4)
|
|
103
|
+
}
|
|
104
|
+
|
|
101
105
|
export async function runTurn({ history, tools, recorder, modelName, effort, auth, system, signal, onStream, stallMs = STALL_MS }) {
|
|
102
106
|
const collected = []
|
|
103
107
|
let roundText = ''
|
|
@@ -108,6 +112,20 @@ export async function runTurn({ history, tools, recorder, modelName, effort, aut
|
|
|
108
112
|
// of the current context as actually sent
|
|
109
113
|
let cumulativePrompt = 0
|
|
110
114
|
let lastPromptTokens = 0
|
|
115
|
+
let estimatedOutputChars = 0
|
|
116
|
+
let estimatedPromptTokens = 0
|
|
117
|
+
const estimatedUsage = () => {
|
|
118
|
+
const exact = usageSeen || {}
|
|
119
|
+
const completionTokens = (exact.completionTokens || 0) + Math.ceil(estimatedOutputChars / 4)
|
|
120
|
+
const prompt = (exact.promptTokens || 0) + estimatedPromptTokens
|
|
121
|
+
return {
|
|
122
|
+
promptTokens: prompt,
|
|
123
|
+
completionTokens,
|
|
124
|
+
totalTokens: prompt + completionTokens,
|
|
125
|
+
cachedTokens: exact.cachedTokens || 0,
|
|
126
|
+
thoughtTokens: exact.thoughtTokens || 0,
|
|
127
|
+
}
|
|
128
|
+
}
|
|
111
129
|
|
|
112
130
|
const internal = new AbortController()
|
|
113
131
|
const onUserAbort = () => internal.abort()
|
|
@@ -137,8 +155,15 @@ export async function runTurn({ history, tools, recorder, modelName, effort, aut
|
|
|
137
155
|
arm()
|
|
138
156
|
if (event.type === 'content') {
|
|
139
157
|
roundText += event.content
|
|
158
|
+
estimatedOutputChars += String(event.content ?? '').length
|
|
159
|
+
onStream?.({ type: 'usage_estimate', usage: estimatedUsage() })
|
|
160
|
+
} else if (event.type === 'thinking') {
|
|
161
|
+
estimatedOutputChars += String(event.content ?? '').length
|
|
162
|
+
onStream?.({ type: 'usage_estimate', usage: estimatedUsage() })
|
|
140
163
|
} else if (event.type === 'tool_calls_ready') {
|
|
141
164
|
collected.push({ role: 'assistant', content: roundText, tool_calls: event.calls })
|
|
165
|
+
estimatedOutputChars += JSON.stringify(event.calls ?? []).length
|
|
166
|
+
onStream?.({ type: 'usage_estimate', usage: estimatedUsage() })
|
|
142
167
|
roundText = ''
|
|
143
168
|
} else if (event.type === 'tool_complete') {
|
|
144
169
|
collected.push({ role: 'tool', tool_call_id: event.call.id, content: JSON.stringify(event.result) })
|
|
@@ -149,6 +174,8 @@ export async function runTurn({ history, tools, recorder, modelName, effort, aut
|
|
|
149
174
|
const prompt = event.usage?.promptTokens || 0
|
|
150
175
|
lastPromptTokens = Math.max(0, prompt - cumulativePrompt)
|
|
151
176
|
cumulativePrompt = prompt
|
|
177
|
+
estimatedPromptTokens = 0
|
|
178
|
+
estimatedOutputChars = 0
|
|
152
179
|
}
|
|
153
180
|
onStream?.(event)
|
|
154
181
|
}
|
|
@@ -158,12 +185,15 @@ export async function runTurn({ history, tools, recorder, modelName, effort, aut
|
|
|
158
185
|
scope(
|
|
159
186
|
{ inherit: Inherit.Conversation, system, tools, until: noToolsCalled(), stream },
|
|
160
187
|
async (ctx) => {
|
|
188
|
+
const hydratedHistory = await hydrateImages(ctx.history)
|
|
189
|
+
estimatedPromptTokens = estimateTokens({ system, history: hydratedHistory, tools })
|
|
190
|
+
onStream?.({ type: 'usage_estimate', usage: estimatedUsage() })
|
|
161
191
|
const out = await model({
|
|
162
192
|
model: modelName,
|
|
163
193
|
...(effort && { effort }),
|
|
164
194
|
...(auth?.apiKey && { apiKey: auth.apiKey }),
|
|
165
195
|
...(auth?.headers && { headers: auth.headers }),
|
|
166
|
-
})({ ...ctx, history:
|
|
196
|
+
})({ ...ctx, history: hydratedHistory, abortSignal: internal.signal })
|
|
167
197
|
// Prepared bytes and omission notices belong only to the request, not
|
|
168
198
|
// the conversation returned by the provider or subsequent tool rounds.
|
|
169
199
|
return { ...out, history: [...ctx.history, ...out.history.slice(ctx.history.length)] }
|
package/src/controller.js
CHANGED
|
@@ -119,6 +119,7 @@ export function createController({ boot }) {
|
|
|
119
119
|
startedAt: 0,
|
|
120
120
|
overlay: [],
|
|
121
121
|
streaming: null,
|
|
122
|
+
liveUsage: null,
|
|
122
123
|
queued: [],
|
|
123
124
|
expedited: [],
|
|
124
125
|
views: [],
|
|
@@ -545,6 +546,15 @@ export function createController({ boot }) {
|
|
|
545
546
|
set({ turnPhase: 'thinking' })
|
|
546
547
|
} else if (event.type === 'content') {
|
|
547
548
|
set({ turnPhase: 'responding', streaming: (state.streaming || '') + event.content })
|
|
549
|
+
} else if (event.type === 'usage' || event.type === 'usage_estimate') {
|
|
550
|
+
const base = state.derived.usageActive
|
|
551
|
+
set({ liveUsage: {
|
|
552
|
+
promptTokens: base.promptTokens + (event.usage.promptTokens || 0),
|
|
553
|
+
completionTokens: base.completionTokens + (event.usage.completionTokens || 0),
|
|
554
|
+
totalTokens: base.totalTokens + (event.usage.totalTokens || 0),
|
|
555
|
+
cachedTokens: base.cachedTokens + (event.usage.cachedTokens || 0),
|
|
556
|
+
thoughtTokens: base.thoughtTokens + (event.usage.thoughtTokens || 0),
|
|
557
|
+
} })
|
|
548
558
|
} else if (event.type === 'tool_calls_ready') {
|
|
549
559
|
set({ turnPhase: 'tools' })
|
|
550
560
|
updateOverlay((items) => {
|
|
@@ -594,7 +604,7 @@ export function createController({ boot }) {
|
|
|
594
604
|
const researchAgentLimit = nextResearchAgentLimit || null
|
|
595
605
|
nextResearchAgentLimit = null
|
|
596
606
|
emit('turn', 'start')
|
|
597
|
-
set({ busy: true, turnPhase: 'responding', startedAt: Date.now() })
|
|
607
|
+
set({ busy: true, turnPhase: 'responding', startedAt: Date.now(), liveUsage: null })
|
|
598
608
|
|
|
599
609
|
const { auth, ok } = await codexAuth()
|
|
600
610
|
if (!ok) {
|
|
@@ -643,7 +653,7 @@ export function createController({ boot }) {
|
|
|
643
653
|
})
|
|
644
654
|
} catch (err) {
|
|
645
655
|
abort = null
|
|
646
|
-
set({ overlay: [], streaming: null, turnPhase: 'idle', busy: false })
|
|
656
|
+
set({ overlay: [], streaming: null, liveUsage: null, turnPhase: 'idle', busy: false })
|
|
647
657
|
flash(`error: ${errorText(err, 120)}`)
|
|
648
658
|
return
|
|
649
659
|
}
|
|
@@ -665,6 +675,7 @@ export function createController({ boot }) {
|
|
|
665
675
|
state.turnPhase = 'idle'
|
|
666
676
|
state.busy = false
|
|
667
677
|
reDerive()
|
|
678
|
+
set({ liveUsage: null })
|
|
668
679
|
boot.git.refresh()
|
|
669
680
|
if (result.stalled) {
|
|
670
681
|
flash('model stalled · turn interrupted')
|
package/src/tools/recorder.js
CHANGED
|
@@ -22,6 +22,14 @@ export function defaultTitle(name, args = {}) {
|
|
|
22
22
|
return raw.length > 60 ? raw.slice(0, 60) + '…' : raw
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export function toolResultOutput(name, result) {
|
|
26
|
+
if (result === undefined) return null
|
|
27
|
+
if (name === 'bash' && result && typeof result === 'object') {
|
|
28
|
+
return [result.stdout, result.stderr].filter(Boolean).join('\n')
|
|
29
|
+
}
|
|
30
|
+
return typeof result === 'string' ? result : JSON.stringify(result, null, 2)
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
export function createRecorder(onChange) {
|
|
26
34
|
return {
|
|
27
35
|
currentCall: null,
|
|
@@ -67,9 +75,9 @@ export function recorded(recorder, name, fn) {
|
|
|
67
75
|
recorder.begin(name, args)
|
|
68
76
|
try {
|
|
69
77
|
const result = await fn(args)
|
|
70
|
-
if (
|
|
78
|
+
if (recorder.pending?.fullOutput == null && result !== undefined) {
|
|
71
79
|
recorder.extra({
|
|
72
|
-
fullOutput:
|
|
80
|
+
fullOutput: toolResultOutput(name, result),
|
|
73
81
|
})
|
|
74
82
|
}
|
|
75
83
|
recorder.done()
|