picocode-core 0.9.195 → 0.9.196

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "picocode-core",
3
- "version": "0.9.195",
3
+ "version": "0.9.196",
4
4
  "description": "The agent runtime behind pico: sessions, tools, subagents, MCP, memory, and model access",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@modelcontextprotocol/sdk": "^1.12.0",
28
- "@prsm/ai": "^1.9.0",
28
+ "@prsm/ai": "^1.10.0",
29
29
  "diff": "^7.0.0",
30
30
  "globby": "^16.2.4",
31
31
  "proper-lockfile": "^4.1.2",
package/src/agent.js CHANGED
@@ -58,10 +58,11 @@ export function compactionHistory(history, prompt) {
58
58
  return hydrateImages([...history.filter((m) => m.role !== 'system'), { role: 'user', content: prompt }])
59
59
  }
60
60
 
61
- export async function compactHistory({ history, modelName, auth, prompt, signal, onStream }) {
61
+ export async function compactHistory({ history, modelName, speed, auth, prompt, signal, onStream }) {
62
62
  const out = await compose(
63
63
  model({
64
64
  model: modelName,
65
+ ...(speed && { speed }),
65
66
  ...(auth?.apiKey && { apiKey: auth.apiKey }),
66
67
  ...(auth?.headers && { headers: auth.headers }),
67
68
  }),
@@ -102,7 +103,7 @@ function estimateTokens(value) {
102
103
  return Math.ceil(JSON.stringify(value ?? '').length / 4)
103
104
  }
104
105
 
105
- export async function runTurn({ history, tools, recorder, modelName, effort, auth, system, signal, onStream, beforeRequest, stallMs = STALL_MS }) {
106
+ export async function runTurn({ history, tools, recorder, modelName, effort, speed, auth, system, signal, onStream, beforeRequest, stallMs = STALL_MS }) {
106
107
  const collected = []
107
108
  let roundText = ''
108
109
  let usageSeen = null
@@ -201,6 +202,7 @@ export async function runTurn({ history, tools, recorder, modelName, effort, aut
201
202
  return { ...request, history: [...request.history, ...messages] }
202
203
  },
203
204
  ...(effort && { effort }),
205
+ ...(speed && { speed }),
204
206
  ...(auth?.apiKey && { apiKey: auth.apiKey }),
205
207
  ...(auth?.headers && { headers: auth.headers }),
206
208
  })({ ...ctx, history: hydratedHistory, abortSignal: internal.signal })
@@ -18,6 +18,7 @@ export function mapCodexModels(entries) {
18
18
  desc: `${m.description || m.label || m.slug} · via ChatGPT plan`,
19
19
  price: null,
20
20
  effort: true,
21
+ speed: (m.service_tiers || []).some(tier => ['priority', 'fast'].includes(tier.id)) || (m.additional_speed_tiers || []).includes('fast'),
21
22
  context: m.context_window || null,
22
23
  }))
23
24
  }
@@ -27,7 +28,7 @@ export async function loadCodexModels(credentials, { force = false, fetcher = fe
27
28
  try {
28
29
  cached = JSON.parse(await readFile(cacheFile(), 'utf-8'))
29
30
  } catch {}
30
- if (!force && cached && Date.now() - cached.at < TTL) return mapCodexModels(cached.models)
31
+ if (!force && cached && Date.now() - cached.at < TTL && cached.models.every(model => Array.isArray(model.service_tiers) || Array.isArray(model.additional_speed_tiers))) return mapCodexModels(cached.models)
31
32
  if (!credentials) return mapCodexModels(cached?.models || [])
32
33
 
33
34
  try {
@@ -42,6 +43,8 @@ export async function loadCodexModels(credentials, { force = false, fetcher = fe
42
43
  const data = await response.json()
43
44
  const models = (data.models || []).map((m) => ({
44
45
  slug: m.slug,
46
+ service_tiers: m.service_tiers || [],
47
+ additional_speed_tiers: m.additional_speed_tiers || [],
45
48
  description: m.description,
46
49
  label: m.label || m.display_name,
47
50
  context_window: m.context_window || m.max_context_window || null,
package/src/controller.js CHANGED
@@ -36,6 +36,11 @@ import { MAX_DELIBERATION_ROUNDS } from './deliberation.js'
36
36
  import { buildUserContent, finalizeUserContent, inputTextFromContent, mediaTypeFor, stashImages } from './attachments.js'
37
37
  import { settlePending } from './pending.js'
38
38
 
39
+ export const SPEED_LEVELS = [
40
+ { key: 'standard', usage: '1x usage' },
41
+ { key: 'fast', usage: '2.5x usage' },
42
+ ]
43
+
39
44
  export const EFFORT_LEVELS = [
40
45
  { key: null, desc: 'let the provider decide how much to think' },
41
46
  { key: 'low', desc: 'quick answers, minimal thinking' },
@@ -114,6 +119,8 @@ export function createController({ boot, run = runTurn, peerDirectory } = {}) {
114
119
  defaultModel: boot.initialModel,
115
120
  effort: boot.initialEffort,
116
121
  defaultEffort: boot.initialEffort,
122
+ speedDefaults: { ...boot.speedDefaults },
123
+ speed: boot.speedDefaults?.[boot.initialModel.name] === 'fast' ? 'fast' : 'standard',
117
124
  busy: false,
118
125
  compacting: false,
119
126
  compactOutcome: null,
@@ -293,6 +300,9 @@ export function createController({ boot, run = runTurn, peerDirectory } = {}) {
293
300
  // by file needs to hear about it
294
301
  emit('session', state.session)
295
302
  }
303
+ if (!state.events.some(event => event.type === 'speed')) {
304
+ state.events.push(makeEvent('speed', { to: state.speed }))
305
+ }
296
306
  while (state.persisted < state.events.length) {
297
307
  state.session.append(state.events[state.persisted])
298
308
  state.persisted++
@@ -350,6 +360,7 @@ export function createController({ boot, run = runTurn, peerDirectory } = {}) {
350
360
  recorder,
351
361
  modelName: worker.name,
352
362
  effort: worker.effort ? 'low' : null,
363
+ ...(worker.provider === 'codex' && { speed: 'standard' }),
353
364
  auth,
354
365
  system: context.system,
355
366
  signal,
@@ -421,6 +432,7 @@ export function createController({ boot, run = runTurn, peerDirectory } = {}) {
421
432
  recorder: toolset.recorder,
422
433
  modelName: roleWorkers[role].name,
423
434
  effort: roleWorkers[role].effort ? 'low' : null,
435
+ ...(roleWorkers[role].provider === 'codex' && { speed: 'standard' }),
424
436
  auth: roleAuths[role],
425
437
  system: context.system,
426
438
  signal: workerSignal,
@@ -606,6 +618,7 @@ export function createController({ boot, run = runTurn, peerDirectory } = {}) {
606
618
  const raw = await compactHistory({
607
619
  history: current.providerHistory,
608
620
  modelName: state.model.name,
621
+ ...(state.model.provider === 'codex' && { speed: speedApplies() ? state.speed : 'standard' }),
609
622
  auth,
610
623
  prompt: compactionPrompt(instructions),
611
624
  signal: controller.signal,
@@ -828,6 +841,7 @@ export function createController({ boot, run = runTurn, peerDirectory } = {}) {
828
841
  recorder,
829
842
  modelName: state.model.name,
830
843
  effort: effortApplies() ? state.effort ?? 'auto' : null,
844
+ ...(state.model.provider === 'codex' && { speed: speedApplies() ? state.speed : 'standard' }),
831
845
  auth,
832
846
  system: context.system,
833
847
  signal: controller.signal,
@@ -946,7 +960,7 @@ export function createController({ boot, run = runTurn, peerDirectory } = {}) {
946
960
  set({ queued: [...state.expedited, ...state.queued], expedited: [] })
947
961
  }
948
962
 
949
- function resetConversation({ model, effort } = {}) {
963
+ function resetConversation({ model, effort, speed } = {}) {
950
964
  state.session = null
951
965
  state.events = []
952
966
  state.persisted = 0
@@ -958,6 +972,7 @@ export function createController({ boot, run = runTurn, peerDirectory } = {}) {
958
972
  state.peerPaused = false
959
973
  state.model = model ?? state.defaultModel
960
974
  state.effort = effort === undefined ? state.defaultEffort : effort
975
+ state.speed = speed ?? defaultSpeed(state.model)
961
976
  }
962
977
 
963
978
  async function newSession() {
@@ -1192,6 +1207,7 @@ export function createController({ boot, run = runTurn, peerDirectory } = {}) {
1192
1207
  reDerive()
1193
1208
  restoreModelFromSession()
1194
1209
  state.effort = state.derived.effort === undefined ? state.defaultEffort : state.derived.effort
1210
+ state.speed = state.derived.speed ?? 'standard'
1195
1211
  state.sent = userEntries(state.derived).map((e) => ({ text: recallText(e), at: header.createdAt }))
1196
1212
  changed()
1197
1213
  emit('session', state.session)
@@ -1319,6 +1335,8 @@ export function createController({ boot, run = runTurn, peerDirectory } = {}) {
1319
1335
  }
1320
1336
  persist(makeEvent('model_switch', { from: state.model.name, to: pick.name }))
1321
1337
  state.model = pick
1338
+ state.speed = defaultSpeed(pick)
1339
+ persist(makeEvent('speed', { to: state.speed }))
1322
1340
  if (asDefault) {
1323
1341
  state.defaultModel = pick
1324
1342
  writeConfig({ defaultModel: pick.name }).catch(() => {})
@@ -1335,6 +1353,23 @@ export function createController({ boot, run = runTurn, peerDirectory } = {}) {
1335
1353
  switchModel(pick, { note: adhoc ? ' (not in catalog, pricing unknown)' : '' })
1336
1354
  }
1337
1355
 
1356
+ const defaultSpeed = model => state.speedDefaults[model.name] === 'fast' ? 'fast' : 'standard'
1357
+ const speedApplies = () => state.model.provider === 'codex' && !!state.model.speed
1358
+
1359
+ function applySpeed(next, { asDefault = false } = {}) {
1360
+ if (!speedApplies()) return flash(`${state.model.name} does not support Fast mode`)
1361
+ if (!SPEED_LEVELS.some(level => level.key === next)) return flash('usage: /speed <standard|fast>')
1362
+ persist(makeEvent('speed', { to: next }))
1363
+ state.speed = next
1364
+ if (asDefault) {
1365
+ state.speedDefaults[state.model.name] = next
1366
+ writeConfig({ speedDefaults: { [state.model.name]: next } }).catch(error => flash(`speed default failed: ${errorText(error, 80)}`))
1367
+ }
1368
+ changed()
1369
+ flash(`speed: ${next}`)
1370
+ }
1371
+
1372
+ const speedCommand = arg => applySpeed(arg.toLowerCase())
1338
1373
  const effortApplies = () => !!state.model.effort
1339
1374
 
1340
1375
  function setEffort(next, { asDefault = false } = {}) {
@@ -1714,6 +1749,9 @@ export function createController({ boot, run = runTurn, peerDirectory } = {}) {
1714
1749
  resolveModel,
1715
1750
  switchModel,
1716
1751
  switchModelByName,
1752
+ speedApplies,
1753
+ applySpeed,
1754
+ speedCommand,
1717
1755
  effortApplies,
1718
1756
  setEffort,
1719
1757
  setEffortByName,
package/src/derive.js CHANGED
@@ -196,6 +196,7 @@ export function deriveState(events) {
196
196
  historyEventIds: [],
197
197
  model: null,
198
198
  effort: undefined,
199
+ speed: undefined,
199
200
  usage: spentUsage,
200
201
  usageByModel: spentUsageByModel,
201
202
  usageActive: emptyUsage(),
@@ -310,6 +311,9 @@ export function deriveState(events) {
310
311
  case 'model_switch':
311
312
  state.model = event.data.to
312
313
  break
314
+ case 'speed':
315
+ state.speed = event.data.to
316
+ break
313
317
  case 'effort':
314
318
  state.effort = event.data.to
315
319
  break