@oh-my-pi/pi-coding-agent 16.2.11 → 16.2.12

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.
Files changed (87) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/cli.js +3005 -2848
  3. package/dist/types/cli/bench-cli.d.ts +0 -5
  4. package/dist/types/cli/dry-balance-cli.d.ts +0 -5
  5. package/dist/types/cli/models-cli.d.ts +1 -1
  6. package/dist/types/config/inline-tool-descriptors-mode.d.ts +1 -2
  7. package/dist/types/config/model-registry.d.ts +1 -23
  8. package/dist/types/config/model-resolver.d.ts +10 -15
  9. package/dist/types/config/models-config-schema.d.ts +0 -6
  10. package/dist/types/config/models-config.d.ts +0 -6
  11. package/dist/types/config/settings-schema.d.ts +31 -1
  12. package/dist/types/eval/py/__tests__/runner-shell-output.test.d.ts +1 -0
  13. package/dist/types/extensibility/extensions/runner.d.ts +15 -0
  14. package/dist/types/lsp/client.d.ts +9 -3
  15. package/dist/types/modes/controllers/tool-args-reveal.d.ts +0 -5
  16. package/dist/types/task/spawn-policy.d.ts +17 -0
  17. package/dist/types/task/spawn-policy.test.d.ts +1 -0
  18. package/dist/types/task/types.d.ts +8 -2
  19. package/dist/types/tools/__tests__/eval-description.test.d.ts +1 -0
  20. package/dist/types/tools/browser/cmux/cmux-tab.d.ts +2 -2
  21. package/dist/types/tools/browser/registry.d.ts +2 -0
  22. package/dist/types/tools/browser/run-cancellation.d.ts +4 -0
  23. package/dist/types/tools/browser/tab-supervisor.d.ts +30 -1
  24. package/dist/types/tools/eval.d.ts +3 -6
  25. package/dist/types/tools/write.d.ts +1 -1
  26. package/dist/types/utils/image-vision-fallback.d.ts +1 -1
  27. package/package.json +12 -12
  28. package/src/advisor/__tests__/advisor.test.ts +1 -1
  29. package/src/auto-thinking/classifier.ts +1 -1
  30. package/src/cli/bench-cli.ts +3 -12
  31. package/src/cli/dry-balance-cli.ts +1 -6
  32. package/src/cli/models-cli.ts +3 -81
  33. package/src/commands/models.ts +1 -2
  34. package/src/commit/model-selection.ts +4 -4
  35. package/src/config/inline-tool-descriptors-mode.ts +1 -2
  36. package/src/config/model-discovery.ts +32 -6
  37. package/src/config/model-registry.ts +11 -225
  38. package/src/config/model-resolver.ts +23 -135
  39. package/src/config/models-config-schema.ts +0 -22
  40. package/src/config/prompt-templates.ts +20 -0
  41. package/src/config/settings-schema.ts +35 -1
  42. package/src/config/settings.ts +60 -24
  43. package/src/eval/__tests__/agent-bridge.test.ts +17 -3
  44. package/src/eval/agent-bridge.ts +7 -9
  45. package/src/eval/completion-bridge.ts +1 -1
  46. package/src/eval/py/__tests__/runner-shell-output.test.ts +157 -0
  47. package/src/eval/py/runner.py +169 -18
  48. package/src/extensibility/extensions/model-api.ts +1 -3
  49. package/src/extensibility/extensions/runner.ts +62 -5
  50. package/src/lsp/client.ts +162 -45
  51. package/src/lsp/index.ts +31 -21
  52. package/src/main.ts +0 -1
  53. package/src/mcp/transports/http.ts +19 -17
  54. package/src/mcp/transports/stdio.test.ts +51 -1
  55. package/src/mcp/transports/stdio.ts +19 -9
  56. package/src/memories/index.ts +0 -1
  57. package/src/mnemopi/backend.ts +1 -1
  58. package/src/modes/components/model-selector.ts +32 -186
  59. package/src/modes/controllers/event-controller.ts +8 -39
  60. package/src/modes/controllers/selector-controller.ts +0 -1
  61. package/src/modes/controllers/tool-args-reveal.ts +0 -12
  62. package/src/prompts/system/subagent-system-prompt.md +2 -2
  63. package/src/prompts/system/tool-call-loop-redirect.md +8 -0
  64. package/src/prompts/tools/eval.md +2 -2
  65. package/src/prompts/tools/task.md +1 -1
  66. package/src/sdk.ts +2 -9
  67. package/src/session/agent-session.ts +89 -8
  68. package/src/session/session-context.ts +2 -1
  69. package/src/session/session-manager.ts +2 -1
  70. package/src/session/unexpected-stop-classifier.ts +1 -1
  71. package/src/task/index.ts +23 -27
  72. package/src/task/spawn-policy.test.ts +63 -0
  73. package/src/task/spawn-policy.ts +58 -0
  74. package/src/task/types.ts +77 -6
  75. package/src/tools/__tests__/eval-description.test.ts +19 -0
  76. package/src/tools/browser/cmux/cmux-tab.ts +66 -24
  77. package/src/tools/browser/registry.ts +25 -0
  78. package/src/tools/browser/run-cancellation.ts +47 -0
  79. package/src/tools/browser/tab-supervisor.ts +120 -7
  80. package/src/tools/browser/tab-worker.ts +22 -10
  81. package/src/tools/browser.ts +1 -0
  82. package/src/tools/eval.ts +15 -10
  83. package/src/tools/inspect-image.ts +1 -1
  84. package/src/tools/write.ts +49 -9
  85. package/src/utils/commit-message-generator.ts +0 -1
  86. package/src/utils/image-vision-fallback.ts +2 -3
  87. package/src/utils/title-generator.ts +1 -1
@@ -14,12 +14,12 @@ import type {
14
14
  SimpleStreamOptions,
15
15
  } from "@oh-my-pi/pi-ai";
16
16
  import { resolveModelServiceTier, streamSimple } from "@oh-my-pi/pi-ai";
17
- import { buildModelProviderPriorityRank, type CanonicalModelVariant } from "@oh-my-pi/pi-catalog/identity";
17
+ import { buildModelProviderPriorityRank } from "@oh-my-pi/pi-catalog/identity";
18
18
  import { replaceTabs, truncateToWidth } from "@oh-my-pi/pi-tui";
19
19
  import { formatDuration, getProjectDir } from "@oh-my-pi/pi-utils";
20
20
  import chalk from "chalk";
21
21
  import type { ApiKeyResolverModel } from "../config/api-key-resolver";
22
- import { type CanonicalModelQueryOptions, ModelRegistry } from "../config/model-registry";
22
+ import { ModelRegistry } from "../config/model-registry";
23
23
  import {
24
24
  formatModelSelectorValue,
25
25
  formatModelString,
@@ -55,9 +55,6 @@ export interface BenchModelRegistry {
55
55
  getAll(): Model<Api>[];
56
56
  getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined>;
57
57
  resolver(model: ApiKeyResolverModel, sessionId?: string): ApiKeyResolver;
58
- resolveCanonicalModel?(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined;
59
- getCanonicalVariants?(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[];
60
- getCanonicalId?(model: Model<Api>): string | undefined;
61
58
  hasConfiguredAuth?(model: Model<Api>): boolean;
62
59
  }
63
60
 
@@ -440,13 +437,7 @@ function resolveAuthenticatedAlternative(
440
437
  seen.add(key);
441
438
  if (modelRegistry.hasConfiguredAuth?.(candidate)) authenticated.push(candidate);
442
439
  };
443
- // Canonical variants link the same logical model across providers even when
444
- // ids differ (e.g. fireworks `gpt-oss-20b` <-> openrouter `openai/gpt-oss-20b`).
445
- const canonicalId = modelRegistry.getCanonicalId?.(model);
446
- if (canonicalId) {
447
- for (const variant of modelRegistry.getCanonicalVariants?.(canonicalId) ?? []) consider(variant.model);
448
- }
449
- // Same-id fallback for entries outside the canonical index.
440
+ // Same-id fallback for equivalent entries under providers with configured auth.
450
441
  for (const candidate of modelRegistry.getAll()) {
451
442
  if (candidate.id === model.id) consider(candidate);
452
443
  }
@@ -12,11 +12,10 @@ import type {
12
12
  SimpleStreamOptions,
13
13
  } from "@oh-my-pi/pi-ai";
14
14
  import { streamSimple } from "@oh-my-pi/pi-ai";
15
- import type { CanonicalModelVariant } from "@oh-my-pi/pi-catalog/identity";
16
15
  import { replaceTabs, truncateToWidth } from "@oh-my-pi/pi-tui";
17
16
  import { formatDuration, getProjectDir } from "@oh-my-pi/pi-utils";
18
17
  import chalk from "chalk";
19
- import { type CanonicalModelQueryOptions, ModelRegistry } from "../config/model-registry";
18
+ import { ModelRegistry } from "../config/model-registry";
20
19
  import {
21
20
  formatModelString,
22
21
  getModelMatchPreferences,
@@ -74,9 +73,6 @@ export interface DryBalanceModelRegistry {
74
73
  getAll(): Model<Api>[];
75
74
  getAvailable(): Model<Api>[];
76
75
  getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined>;
77
- getCanonicalVariants(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[];
78
- resolveCanonicalModel?(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined;
79
- getCanonicalId?(model: Model<Api>): string | undefined;
80
76
  }
81
77
 
82
78
  export interface DryBalanceRuntime {
@@ -566,7 +562,6 @@ async function resolveDryBalanceModel(
566
562
  const defaultRoleSpec = resolveModelRoleValue(settings?.getModelRole("default"), allowedModels, {
567
563
  settings,
568
564
  matchPreferences: preferences,
569
- modelRegistry,
570
565
  });
571
566
  if (defaultRoleSpec.model) {
572
567
  return { model: defaultRoleSpec.model, warning: defaultRoleSpec.warning };
@@ -2,7 +2,7 @@
2
2
  * `omp models` — list, search, and refresh available models.
3
3
  *
4
4
  * Subcommands:
5
- * - `ls` (default): list every available model with a canonical + provider view.
5
+ * - `ls` (default): list every available model grouped by provider.
6
6
  * - `find <substring>`: list models whose provider, id, or name contains the substring.
7
7
  * - `refresh`: force an online catalog re-fetch (ignoring the model cache TTL),
8
8
  * then list. This is the supported replacement for `rm -rf ~/.omp/models.db`
@@ -21,7 +21,7 @@ import { discoverAndLoadExtensions, loadExtensions } from "../extensibility/exte
21
21
  import { discoverAuthStorage } from "../sdk";
22
22
  import { EventBus } from "../utils/event-bus";
23
23
 
24
- export type ModelsAction = "ls" | "find" | "refresh" | "canonical";
24
+ export type ModelsAction = "ls" | "find" | "refresh";
25
25
 
26
26
  export interface ModelsCommandArgs {
27
27
  action: ModelsAction;
@@ -48,7 +48,6 @@ const KNOWN_ACTIONS: Record<string, ModelsAction> = {
48
48
  list: "ls",
49
49
  find: "find",
50
50
  refresh: "refresh",
51
- canonical: "canonical",
52
51
  };
53
52
 
54
53
  /** Resolve the two positional args into an action + filter (provider names fall through to `ls`). */
@@ -77,22 +76,10 @@ interface ModelJson {
77
76
  cost: Model<Api>["cost"];
78
77
  }
79
78
 
80
- interface CanonicalJson {
81
- id: string;
82
- selected: string;
83
- variants: number;
84
- contextWindow: number | null;
85
- maxTokens: number | null;
86
- }
87
-
88
79
  interface ModelsJson {
89
80
  models: ModelJson[];
90
81
  }
91
82
 
92
- interface CanonicalModelsJson {
93
- canonical: CanonicalJson[];
94
- }
95
-
96
83
  function writeLine(line = ""): void {
97
84
  process.stdout.write(`${line}\n`);
98
85
  }
@@ -253,67 +240,6 @@ function renderProviderModels(
253
240
  }
254
241
  }
255
242
 
256
- /** `omp models canonical`: the coalesced canonical view (one row per canonical id). */
257
- function renderCanonicalModels(modelRegistry: ModelRegistry, pattern: string | undefined, json: boolean): void {
258
- const selections = modelRegistry.getCanonicalModelSelections({ availableOnly: true });
259
- const needle = pattern?.toLowerCase();
260
- const filtered = needle
261
- ? selections.filter(
262
- ({ record, model }) =>
263
- record.id.toLowerCase().includes(needle) ||
264
- `${model.provider}/${model.id}`.toLowerCase().includes(needle),
265
- )
266
- : selections;
267
-
268
- if (json) {
269
- const output: CanonicalModelsJson = {
270
- canonical: filtered
271
- .map(({ record, model }) => ({
272
- id: record.id,
273
- selected: `${model.provider}/${model.id}`,
274
- variants: record.variants.length,
275
- contextWindow: model.contextWindow,
276
- maxTokens: model.maxTokens,
277
- }))
278
- .sort((left, right) => left.id.localeCompare(right.id)),
279
- };
280
- writeLine(JSON.stringify(output));
281
- return;
282
- }
283
-
284
- if (selections.length === 0) {
285
- writeLine("No models available. Set API keys in environment variables.");
286
- return;
287
- }
288
- if (filtered.length === 0) {
289
- writeLine(`No canonical models matching "${pattern}"`);
290
- return;
291
- }
292
-
293
- const rows = filtered
294
- .slice()
295
- .sort((left, right) => left.record.id.localeCompare(right.record.id))
296
- .map(({ record, model }) => [
297
- record.id,
298
- `${model.provider}/${model.id}`,
299
- String(record.variants.length),
300
- formatLimit(model.contextWindow),
301
- formatLimit(model.maxTokens),
302
- ]);
303
- for (const line of boxTable(
304
- [
305
- { header: "canonical" },
306
- { header: "selected" },
307
- { header: "variants", align: "right" },
308
- { header: "context", align: "right" },
309
- { header: "max-out", align: "right" },
310
- ],
311
- rows,
312
- )) {
313
- writeLine(line);
314
- }
315
- }
316
-
317
243
  /**
318
244
  * Options for {@link runModelsListing}: render the catalog from a caller-supplied
319
245
  * registry. Loads extensions (CLI `-e` paths and configured `settings.extensions`)
@@ -376,11 +302,7 @@ export async function runModelsListing(options: RunModelsListingOptions): Promis
376
302
  // Discover runtime (extension) provider catalogs now that they are registered.
377
303
  await modelRegistry.refreshRuntimeProviders(action === "refresh" ? "online" : "online-if-uncached");
378
304
 
379
- if (action === "canonical") {
380
- renderCanonicalModels(modelRegistry, pattern, json);
381
- } else {
382
- renderProviderModels(modelRegistry, action, pattern, json);
383
- }
305
+ renderProviderModels(modelRegistry, action, pattern, json);
384
306
  }
385
307
 
386
308
  /**
@@ -10,7 +10,7 @@ export default class Models extends Command {
10
10
 
11
11
  static args = {
12
12
  action: Args.string({
13
- description: "ls (default) | find | refresh | canonical | <provider>",
13
+ description: "ls (default) | find | refresh | <provider>",
14
14
  required: false,
15
15
  }),
16
16
  pattern: Args.string({
@@ -40,7 +40,6 @@ export default class Models extends Command {
40
40
  `# List one provider's models (any provider name works)\n ${APP_NAME} models openai-codex`,
41
41
  `# Find models by substring\n ${APP_NAME} models find minimax`,
42
42
  `# Force a fresh catalog fetch (replaces rm -rf ~/.omp/models.db)\n ${APP_NAME} models refresh`,
43
- `# Show the coalesced canonical model view\n ${APP_NAME} models canonical`,
44
43
  `# Machine-readable output\n ${APP_NAME} models --json`,
45
44
  ];
46
45
 
@@ -36,8 +36,8 @@ export async function resolvePrimaryModel(
36
36
  const available = modelRegistry.getAvailable();
37
37
  const matchPreferences = getModelMatchPreferences(settings);
38
38
  const resolved = override
39
- ? resolveModelRoleValue(override, available, { settings, matchPreferences, modelRegistry })
40
- : resolveRoleSelection(["commit", "smol", ...MODEL_ROLE_IDS], settings, available, modelRegistry);
39
+ ? resolveModelRoleValue(override, available, { settings, matchPreferences })
40
+ : resolveRoleSelection(["commit", "smol", ...MODEL_ROLE_IDS], settings, available);
41
41
  const model = resolved?.model;
42
42
  if (!model) {
43
43
  throw new Error("No model available for commit generation");
@@ -60,7 +60,7 @@ export async function resolveSmolModel(
60
60
  fallbackApiKey: ApiKey,
61
61
  ): Promise<ResolvedCommitModel> {
62
62
  const available = modelRegistry.getAvailable();
63
- const resolvedSmol = resolveRoleSelection(["smol"], settings, available, modelRegistry);
63
+ const resolvedSmol = resolveRoleSelection(["smol"], settings, available);
64
64
  if (resolvedSmol?.model) {
65
65
  const apiKey = await modelRegistry.getApiKey(resolvedSmol.model);
66
66
  if (apiKey) {
@@ -74,7 +74,7 @@ export async function resolveSmolModel(
74
74
 
75
75
  const matchPreferences = getModelMatchPreferences(settings);
76
76
  for (const pattern of MODEL_PRIO.smol) {
77
- const candidate = parseModelPattern(pattern, available, matchPreferences, { modelRegistry }).model;
77
+ const candidate = parseModelPattern(pattern, available, matchPreferences).model;
78
78
  if (!candidate) continue;
79
79
  const apiKey = await modelRegistry.getApiKey(candidate);
80
80
  if (apiKey) {
@@ -9,8 +9,7 @@ import { modelFamilyToken } from "@oh-my-pi/pi-catalog/identity";
9
9
  * Gemini benefits from descriptors in-prompt; other providers keep them in the
10
10
  * tool schemas. `on`/`off` are explicit user overrides.
11
11
  *
12
- * @param modelId Canonical model id (e.g. `gemini-3-pro`); resolve aliases via
13
- * `ModelRegistry.getCanonicalId` before calling so `auto` classifies correctly.
12
+ * @param modelId Model id (e.g. `gemini-3-pro`) used to classify `auto`.
14
13
  */
15
14
  export function shouldInlineToolDescriptors(
16
15
  setting: "auto" | "on" | "off" | undefined,
@@ -19,7 +19,7 @@ import {
19
19
  OPENAI_COMPAT_DISCOVERY_DEFAULT_CONTEXT_WINDOW,
20
20
  OPENAI_COMPAT_DISCOVERY_DEFAULT_MAX_TOKENS,
21
21
  } from "@oh-my-pi/pi-catalog/provider-models/openai-compat";
22
- import type { ModelSpec } from "@oh-my-pi/pi-catalog/types";
22
+ import type { ModelSpec, OpenAICompat } from "@oh-my-pi/pi-catalog/types";
23
23
  import { isRecord } from "@oh-my-pi/pi-utils";
24
24
  import type { ProviderDiscovery } from "./models-config-schema";
25
25
 
@@ -585,34 +585,60 @@ export async function discoverOpenAIModelsList(
585
585
  ? await withAuth(apiKey, key => attempt({ ...baseHeaders, Authorization: `Bearer ${key}` }))
586
586
  : await attempt(baseHeaders);
587
587
  const models = payload.data ?? [];
588
+ const references = getBundledModelReferenceIndex();
588
589
  const discovered: Model<Api>[] = [];
589
590
  for (const item of models) {
590
591
  const id = item.id;
591
592
  if (!id) continue;
592
593
  const nativeMetadataForModel = nativeMetadata?.get(id);
594
+ // Thin OpenAI-compatible proxies frequently omit `context_length`/
595
+ // `max_model_len` on `/v1/models`, leaving discovered models pinned at
596
+ // the 128K default even when the underlying model is e.g. a proxied
597
+ // Claude with a 1M window. Resolve the id against the bundled catalog
598
+ // (same pattern as `discoverProxyModels` and `discoverLiteLLMModels`) so
599
+ // intrinsic metadata — context/output limits, display name, modality,
600
+ // reasoning support — flows through when the provider is silent. Local
601
+ // runtime state and provider-reported values still win; proxy-specific
602
+ // headers/baseUrl/cost stay local.
603
+ const reference = resolveModelReference(id, references) as ModelSpec<Api> | undefined;
604
+ const referenceCompat = reference?.compat as OpenAICompat | undefined;
593
605
  const contextWindow =
594
606
  toPositiveNumberOrUndefined(item.max_model_len) ??
595
607
  toPositiveNumberOrUndefined(item.context_length) ??
596
608
  nativeMetadataForModel?.contextWindow ??
609
+ reference?.contextWindow ??
597
610
  DISCOVERY_DEFAULT_CONTEXT_WINDOW;
598
611
  discovered.push(
599
612
  buildModel({
600
613
  id,
601
- name: id,
614
+ name: reference?.name ?? id,
602
615
  api: providerConfig.api,
603
616
  provider: providerConfig.provider,
604
617
  baseUrl,
605
- reasoning: false,
606
- input: nativeMetadataForModel?.input ?? ["text"],
618
+ reasoning: reference?.reasoning ?? false,
619
+ thinking: reference?.thinking,
620
+ input: nativeMetadataForModel?.input ?? reference?.input ?? ["text"],
607
621
  ...(providerConfig.discovery.type === "lm-studio" ? { imageInputDecoder: "stb" as const } : {}),
622
+ // Proxy/gateway pricing is provider-specific and rarely matches
623
+ // upstream bundled catalogs, so keep costs local-unknown even
624
+ // when we successfully recover the upstream model identity.
608
625
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
609
626
  contextWindow,
610
- maxTokens: Math.min(contextWindow, discoveryDefaultMaxTokens(providerConfig.api)),
627
+ // Cap the reference's output limit at the discovered context
628
+ // window so an ID collision with a larger bundled model can
629
+ // never request more tokens than the local runtime advertises.
630
+ maxTokens: Math.min(reference?.maxTokens ?? discoveryDefaultMaxTokens(providerConfig.api), contextWindow),
611
631
  headers,
612
632
  compat: {
613
633
  supportsStore: false,
614
634
  supportsDeveloperRole: false,
615
- supportsReasoningEffort: false,
635
+ supportsReasoningEffort: referenceCompat?.supportsReasoningEffort ?? false,
636
+ ...(referenceCompat?.reasoningEffortMap
637
+ ? { reasoningEffortMap: referenceCompat.reasoningEffortMap }
638
+ : {}),
639
+ ...(referenceCompat?.omitReasoningEffort !== undefined
640
+ ? { omitReasoningEffort: referenceCompat.omitReasoningEffort }
641
+ : {}),
616
642
  },
617
643
  } as ModelSpec<Api>),
618
644
  );