pum-agent 0.2.29-beta.1 → 0.2.30-beta.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/README.md CHANGED
@@ -23,6 +23,10 @@ pum
23
23
  PUM opens the login panel automatically on the first start. Press `?` on an
24
24
  empty prompt to see every control.
25
25
 
26
+ Select a model in `Ctrl+P`. GPT-6 Astra is listed for OpenAI and Codex;
27
+ using it requires access on your provider account. Press `r` in the model
28
+ list to refresh providers that support catalog discovery.
29
+
26
30
  > [!WARNING]
27
31
  > PUM can read, write, and delete files. Check mode adds deterministic policy
28
32
  > checks, and supported hosts can enforce native Bash isolation, but the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pum-agent",
3
- "version": "0.2.29-beta.1",
3
+ "version": "0.2.30-beta.1",
4
4
  "description": "A compact terminal coding agent powered by pi and OpenTUI.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -50,8 +50,9 @@
50
50
  "pack:check": "bun run scripts/validate-pack.ts"
51
51
  },
52
52
  "dependencies": {
53
- "@earendil-works/pi-ai": "^0.84.1",
54
- "@earendil-works/pi-coding-agent": "^0.84.1",
53
+ "@earendil-works/pi-ai": "^0.85.0",
54
+ "@earendil-works/pi-coding-agent": "^0.85.0",
55
+ "@earendil-works/pi-server": "^0.85.0",
55
56
  "@opentui/core": "^0.5.1",
56
57
  "@opentui/react": "^0.5.1",
57
58
  "react": "^19.2.8",
package/src/app.tsx CHANGED
@@ -1041,6 +1041,8 @@ export function App({
1041
1041
  }));
1042
1042
  const [modelQuery, setModelQuery] = useState("");
1043
1043
  const [modelSearchFocused, setModelSearchFocused] = useState(false);
1044
+ const [modelRefreshing, setModelRefreshing] = useState(false);
1045
+ const [modelRevision, setModelRevision] = useState(0);
1044
1046
  const [, setAgentRevision] = useState(0);
1045
1047
  const [triggersOpen, setTriggersOpen] = useState(false);
1046
1048
  const [processTab, setProcessTab] = useState<ProcessTab>("triggers");
@@ -1321,7 +1323,7 @@ export function App({
1321
1323
  modelRuntime.getAvailableSnapshot(),
1322
1324
  modelQuery,
1323
1325
  (providerId) => (modelRuntime as any).getProvider?.(providerId)?.name ?? "",
1324
- ), [modelRuntime, modelId, modelQuery, loginPage]);
1326
+ ), [modelRuntime, modelId, modelQuery, loginPage, modelRevision]);
1325
1327
 
1326
1328
  const inputRef = useRef<TextareaRenderable>(null);
1327
1329
  const inputModeRef = useRef(false);
@@ -1334,6 +1336,7 @@ export function App({
1334
1336
  const processTabRef = useRef<ProcessTab>("triggers");
1335
1337
  const settingsPageRef = useRef(page);
1336
1338
  const settingsSearchFocusedRef = useRef(settingsSearchFocused);
1339
+ const modelRefreshInFlightRef = useRef(false);
1337
1340
  const focusInputAfterSwitch = useRef(false);
1338
1341
  const activeAgentIdRef = useRef<string | null>(null);
1339
1342
  const agentSelectorCursorRef = useRef(0);
@@ -2870,6 +2873,25 @@ export function App({
2870
2873
  void loadManagedProviders();
2871
2874
  }, [commandInput, managedProviders.length]);
2872
2875
 
2876
+ const refreshModels = async () => {
2877
+ if (modelRefreshInFlightRef.current) return;
2878
+ modelRefreshInFlightRef.current = true;
2879
+ setModelRefreshing(true);
2880
+ try {
2881
+ const result = await modelRuntime.refresh({ allowNetwork: true, force: true });
2882
+ if (result.errors.size > 0) {
2883
+ const details = [...result.errors].map(([provider, error]) => `${provider}: ${error.message}`).join("; ");
2884
+ append({ kind: "text", role: "error", text: `model refresh completed with errors: ${details}` });
2885
+ }
2886
+ } catch (error) {
2887
+ append({ kind: "text", role: "error", text: `model refresh failed: ${String(error)}` });
2888
+ } finally {
2889
+ modelRefreshInFlightRef.current = false;
2890
+ setModelRefreshing(false);
2891
+ setModelRevision((revision) => revision + 1);
2892
+ }
2893
+ };
2894
+
2873
2895
  const selectModel = (model: Model<any>) => {
2874
2896
  settingsPageRef.current = "main";
2875
2897
  setPage("main");
@@ -5284,6 +5306,11 @@ export function App({
5284
5306
  }
5285
5307
  return;
5286
5308
  }
5309
+ if (!key.ctrl && !key.meta && !key.option && printableKey === "r") {
5310
+ key.stopPropagation();
5311
+ void refreshModels();
5312
+ return;
5313
+ }
5287
5314
  if (isModelSearchShortcut(key, modelSearchFocused)) {
5288
5315
  key.stopPropagation();
5289
5316
  setModelSearchFocused(true);
@@ -6170,6 +6197,7 @@ export function App({
6170
6197
  models={visibleModels}
6171
6198
  modelQuery={modelQuery}
6172
6199
  modelSearchFocused={modelSearchFocused}
6200
+ modelRefreshing={modelRefreshing}
6173
6201
  onSearchChange={updateSettingsQuery}
6174
6202
  onModelSearchChange={setModelQuery}
6175
6203
  onSelectModel={selectModel}
package/src/headless.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  SessionManager,
7
7
  } from "@earendil-works/pi-coding-agent";
8
8
  import { mkdirSync } from "node:fs";
9
+ import { installModelCatalogFallbacks } from "./model-catalog";
9
10
  import { AGENT_DIR, AUTH_PATH, MODELS_PATH, sessionDir } from "./config";
10
11
  import { createMemoryExtension, MEMORY_EDIT_TOOL_NAME, MEMORY_READ_TOOL_NAME } from "./memory";
11
12
  import { checkPathsForProject, loadSettings } from "./settings";
@@ -148,6 +149,7 @@ async function runPromptSession(
148
149
  authPath: AUTH_PATH,
149
150
  modelsPath: MODELS_PATH,
150
151
  });
152
+ installModelCatalogFallbacks(modelRuntime);
151
153
  if ((await modelRuntime.getAvailable()).length === 0) {
152
154
  process.stderr.write("pum: no provider is available. Run 'pum login' first.\n");
153
155
  return 1;
package/src/main.tsx CHANGED
@@ -15,6 +15,7 @@ import { createMemoryExtension } from "./memory";
15
15
  import { checkPathsForProject, loadSettings } from "./settings";
16
16
  import { setBashOutputSettingsIfPresent } from "./bash-output";
17
17
  import { installWebSearch, webSearch } from "./web-search";
18
+ import { installModelCatalogFallbacks } from "./model-catalog";
18
19
  import { identityExtension } from "./identity";
19
20
  import { setWritingStyle, writingStyleExtension } from "./writing-style";
20
21
  import { checkModePromptExtension, setSandboxModeSource } from "./check-mode-prompt";
@@ -97,6 +98,7 @@ export async function start(
97
98
  authPath: AUTH_PATH,
98
99
  modelsPath: MODELS_PATH,
99
100
  });
101
+ installModelCatalogFallbacks(modelRuntime);
100
102
  const loginRequired = options.login || (await modelRuntime.getAvailable()).length === 0;
101
103
 
102
104
  const settings = loadSettings();
@@ -0,0 +1,47 @@
1
+ import type { Model, Provider } from "@earendil-works/pi-ai";
2
+ import type { ModelRuntime } from "@earendil-works/pi-coding-agent";
3
+
4
+ const ASTRA_ID = "gpt-6-astra";
5
+
6
+ /** Temporary catalog fallback until pi includes Astra. Never replace upstream metadata. */
7
+ export function withAstraModel(base: Provider): Provider {
8
+ if (base.id !== "openai" && base.id !== "openai-codex") return base;
9
+ if (base.getModels().some((model) => model.id === ASTRA_ID)) return base;
10
+ const codex = base.id === "openai-codex";
11
+ // https://developers.openai.com/api/docs/models/gpt-6-astra
12
+ const astra: Model<any> = {
13
+ id: ASTRA_ID,
14
+ name: "GPT-6 Astra",
15
+ provider: base.id,
16
+ api: codex ? "openai-codex-responses" : "openai-responses",
17
+ baseUrl: base.baseUrl ?? (codex ? "https://chatgpt.com/backend-api" : "https://api.openai.com/v1"),
18
+ reasoning: true,
19
+ input: ["text", "image"],
20
+ // Keep pi's conservative Codex context budget until that transport publishes one.
21
+ contextWindow: codex ? 272000 : 1050000,
22
+ maxTokens: 128000,
23
+ cost: {
24
+ input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5,
25
+ tiers: [{ inputTokensAbove: 272000, input: 20, output: 75, cacheRead: 2, cacheWrite: 25 }],
26
+ },
27
+ thinkingLevelMap: {
28
+ off: null, minimal: null, low: "low", medium: "medium",
29
+ high: "high", xhigh: "xhigh", max: "max",
30
+ },
31
+ };
32
+ const wrapped: Provider = Object.create(base);
33
+ wrapped.getModels = () => {
34
+ const models = base.getModels();
35
+ return models.some((model) => model.id === ASTRA_ID) ? models : [...models, astra];
36
+ };
37
+ return wrapped;
38
+ }
39
+
40
+ export function installModelCatalogFallbacks(runtime: ModelRuntime): void {
41
+ for (const id of ["openai", "openai-codex"]) {
42
+ const base = runtime.getProvider(id);
43
+ if (!base) continue;
44
+ const provider = withAstraModel(base);
45
+ if (provider !== base) runtime.registerNativeProvider(provider);
46
+ }
47
+ }
@@ -203,6 +203,7 @@ export type PopupProps = {
203
203
  models: readonly Model<any>[];
204
204
  modelQuery?: string;
205
205
  modelSearchFocused?: boolean;
206
+ modelRefreshing?: boolean;
206
207
  onSearchChange: (value: string) => void;
207
208
  onModelSearchChange?: (value: string) => void;
208
209
  onSelectModel: (model: Model<any>) => void;
@@ -226,6 +227,7 @@ export function SettingsPopup({
226
227
  models,
227
228
  modelQuery = "",
228
229
  modelSearchFocused = false,
230
+ modelRefreshing = false,
229
231
  onSearchChange,
230
232
  onModelSearchChange = () => {},
231
233
  onSelectModel,
@@ -378,7 +380,11 @@ export function SettingsPopup({
378
380
  />}
379
381
  </box> : null}
380
382
  {layout.footerHeight ? <text
381
- content="/ search ↑↓ move enter select esc back"
383
+ content={modelRefreshing
384
+ ? layout.narrow ? "Refreshing models… esc" : "Refreshing models… esc back"
385
+ : layout.narrow
386
+ ? "/ search ↑↓ enter select r refresh esc"
387
+ : "/ search ↑↓ move enter select r refresh esc back"}
382
388
  fg={theme.dim}
383
389
  bg={theme.popupBg}
384
390
  wrapMode="none"