kritya 0.8.48-beta → 0.8.49-beta

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.
@@ -3,7 +3,7 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { createRequire } from "node:module";
5
5
  import { BUILTIN_PROVIDERS, CONFIG_DIR, CONFIG_FILE, legacyGlobalModel, loadConfig, loadDotEnv, privacyModeFor, resolveProvider, } from "../config/config.js";
6
- import { DEFAULT_MODEL, contextWindowFor } from "../config/models.js";
6
+ import { defaultModelFor, contextWindowFor } from "../config/models.js";
7
7
  import { retentionDaysFor } from "../config/retention.js";
8
8
  import { assertJsonWithinLimits } from "../config/jsonSafety.js";
9
9
  import { isGitRepo } from "../agent/worktree.js";
@@ -144,7 +144,7 @@ function providerSection(config, provider) {
144
144
  else {
145
145
  checks.push(fail(`no API key resolved for provider "${provider.name}"`));
146
146
  }
147
- const model = resolveEffectiveModel(provider.name, [config.providers?.[provider.name]?.model, legacyGlobalModel(config, provider.name)], provider.name === "switchyard" ? SWITCHYARD_ROUTE_ID : DEFAULT_MODEL);
147
+ const model = resolveEffectiveModel(provider.name, [config.providers?.[provider.name]?.model, legacyGlobalModel(config, provider.name)], provider.name === "switchyard" ? SWITCHYARD_ROUTE_ID : defaultModelFor(provider.name));
148
148
  checks.push(ok(`model: ${model} (context ${contextWindowFor(model, config).toLocaleString("en-US")})`));
149
149
  // A pinned provider name with no matching entry is a silent misconfiguration:
150
150
  // resolveProvider falls back to NVIDIA's URL, so requests go somewhere the
@@ -1,3 +1,7 @@
1
+ /** The provider a `ModelInfo` belongs to, defaulting the historical NVIDIA catalog. */
2
+ export function providerOfModel(m) {
3
+ return m.provider ?? "nvidia";
4
+ }
1
5
  /** Fallback context window when neither config nor the registry knows the model. */
2
6
  export const DEFAULT_CONTEXT_WINDOW = 120_000;
3
7
  /**
@@ -63,8 +67,53 @@ export const CURATED_MODELS = [
63
67
  note: "fast + cheap",
64
68
  contextWindow: 1_048_576,
65
69
  },
70
+ // Groq (https://console.groq.com/docs/models). Context windows verified
71
+ // against the docs table; both are 131,072.
72
+ {
73
+ id: "openai/gpt-oss-120b",
74
+ label: "GPT-OSS 120B",
75
+ provider: "groq",
76
+ note: "default",
77
+ contextWindow: 131_072,
78
+ },
79
+ {
80
+ id: "llama-3.3-70b-versatile",
81
+ label: "Llama 3.3 70B Versatile",
82
+ provider: "groq",
83
+ contextWindow: 131_072,
84
+ },
66
85
  ];
86
+ /** Models registered for one provider, in registry order. */
87
+ export function modelsForProvider(provider) {
88
+ return CURATED_MODELS.filter((m) => providerOfModel(m) === provider);
89
+ }
90
+ /**
91
+ * The provider a curated model belongs to, or undefined for a model this
92
+ * registry doesn't know (custom models, arbitrary `-m` ids).
93
+ */
94
+ export function curatedProviderFor(modelId) {
95
+ const m = CURATED_MODELS.find((x) => x.id === modelId);
96
+ return m ? providerOfModel(m) : undefined;
97
+ }
98
+ /** The curated default model for a provider, if it has one. */
99
+ export function defaultModelForProvider(provider) {
100
+ const models = modelsForProvider(provider);
101
+ if (models.length === 0)
102
+ return undefined;
103
+ // Prefer an entry flagged `note: "default"`, else the first registered.
104
+ return (models.find((m) => m.note === "default") ?? models[0]).id;
105
+ }
67
106
  export const DEFAULT_MODEL = CURATED_MODELS[0].id;
107
+ /**
108
+ * The default model id for a given provider. Providers with a curated list use
109
+ * their own default, so `-p groq` with no `-m` sends a Groq model id rather
110
+ * than the NVIDIA default (which the Groq API would reject). Providers with no
111
+ * curated models keep the historical behaviour of falling back to the NVIDIA
112
+ * default, since those are callers passing an arbitrary provider/model pair.
113
+ */
114
+ export function defaultModelFor(provider) {
115
+ return defaultModelForProvider(provider) ?? DEFAULT_MODEL;
116
+ }
68
117
  /**
69
118
  * Context window for a model. Explicit config.contextWindow always wins; then
70
119
  * the curated registry; then the default. Custom models can carry a window via
package/dist/engine.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { Agent } from "./agent/loop.js";
3
3
  import { CONFIG_DIR, legacyGlobalModel, loadConfig, loadDotEnv, privacyModeFor, resolveProvider, } from "./config/config.js";
4
- import { DEFAULT_MODEL, contextWindowFor } from "./config/models.js";
4
+ import { defaultModelFor, contextWindowFor } from "./config/models.js";
5
5
  import { PermissionManager } from "./permissions/permissions.js";
6
6
  import { loadRules } from "./permissions/rules.js";
7
7
  import { ProviderClient } from "./provider/client.js";
@@ -41,7 +41,7 @@ export async function createEngineSession(dir, opts = {}) {
41
41
  throw new Error(`No API key found for provider "${provider.name}". Set it via env var, a .env file, or ~/.kritya/config.json.`);
42
42
  }
43
43
  const providerDefaultModel = config.providers?.[provider.name]?.model;
44
- let currentModel = resolveEffectiveModel(provider.name, [opts.model, providerDefaultModel, legacyGlobalModel(config, provider.name)], provider.name === "switchyard" ? SWITCHYARD_ROUTE_ID : DEFAULT_MODEL);
44
+ let currentModel = resolveEffectiveModel(provider.name, [opts.model, providerDefaultModel, legacyGlobalModel(config, provider.name)], provider.name === "switchyard" ? SWITCHYARD_ROUTE_ID : defaultModelFor(provider.name));
45
45
  const sampling = {
46
46
  temperature: provider.temperature,
47
47
  topP: provider.topP,
package/dist/headless.js CHANGED
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import { Agent } from "./agent/loop.js";
3
3
  import { KillSwitchError } from "./agent/killSwitch.js";
4
4
  import { CONFIG_DIR, legacyGlobalModel, listProviders, loadConfig, loadDotEnv, privacyModeFor, resolveProvider, } from "./config/config.js";
5
- import { DEFAULT_MODEL, contextWindowFor } from "./config/models.js";
5
+ import { defaultModelFor, contextWindowFor } from "./config/models.js";
6
6
  import { PermissionManager } from "./permissions/permissions.js";
7
7
  import { loadRules } from "./permissions/rules.js";
8
8
  import { ProviderClient, RetryExhaustedError, friendlyProviderErrorHint, } from "./provider/client.js";
@@ -125,7 +125,7 @@ export async function runHeadless(args) {
125
125
  });
126
126
  }
127
127
  const providerDefaultModel = config.providers?.[provider.name]?.model;
128
- const model = resolveEffectiveModel(provider.name, [args.model, providerDefaultModel, legacyGlobalModel(config, provider.name)], provider.name === "switchyard" ? SWITCHYARD_ROUTE_ID : DEFAULT_MODEL);
128
+ const model = resolveEffectiveModel(provider.name, [args.model, providerDefaultModel, legacyGlobalModel(config, provider.name)], provider.name === "switchyard" ? SWITCHYARD_ROUTE_ID : defaultModelFor(provider.name));
129
129
  const staleModelWarning = staleSwitchyardModelWarning(provider.name, model, args.model);
130
130
  if (staleModelWarning)
131
131
  process.stderr.write(`${staleModelWarning}\n`);
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import path from "node:path";
5
5
  import { render } from "ink";
6
6
  import { Agent } from "./agent/loop.js";
7
7
  import { CONFIG_DIR, appendGlobalEnvVar, legacyGlobalModel, loadConfig, loadDotEnv, privacyModeFor, resolveProvider, } from "./config/config.js";
8
- import { DEFAULT_MODEL, contextWindowFor } from "./config/models.js";
8
+ import { defaultModelFor, contextWindowFor } from "./config/models.js";
9
9
  import { PermissionManager } from "./permissions/permissions.js";
10
10
  import { loadRules } from "./permissions/rules.js";
11
11
  import { ProviderClient } from "./provider/client.js";
@@ -404,7 +404,7 @@ async function main() {
404
404
  }
405
405
  const providerDefaultModel = config.providers?.[provider.name]?.model;
406
406
  const modelRef = {
407
- current: resolveEffectiveModel(provider.name, [args.model, providerDefaultModel, legacyGlobalModel(config, provider.name)], provider.name === "switchyard" ? SWITCHYARD_ROUTE_ID : DEFAULT_MODEL),
407
+ current: resolveEffectiveModel(provider.name, [args.model, providerDefaultModel, legacyGlobalModel(config, provider.name)], provider.name === "switchyard" ? SWITCHYARD_ROUTE_ID : defaultModelFor(provider.name)),
408
408
  };
409
409
  const staleModelWarning = staleSwitchyardModelWarning(provider.name, modelRef.current, args.model);
410
410
  if (staleModelWarning)
package/dist/ui/App.js CHANGED
@@ -721,7 +721,7 @@ export function App({ agent, workspace, modelRef, providerRef, config, resumedCo
721
721
  }
722
722
  addItem({ kind: "user", text: `${text} (queued)` });
723
723
  void expandMentions(text).then((t) => agent.queueSteer(t));
724
- }, placeholder: "steer the agent\u2026 (Enter to queue)" })] })] })), phase === "permission" && permission && (_jsx(PermissionPrompt, { toolName: permission.toolName, summary: permission.summary, diff: permission.diff, warning: permission.warning, onDecision: onPermissionDecision })), phase === "elicitation" && elicitation && (_jsx(ElicitationPrompt, { message: elicitation.message, fields: elicitation.fields, onDecision: onElicitationDecision })), phase === "confirmMode" && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, children: [_jsx(Text, { bold: true, color: "green", children: "Switch to accept-edits mode?" }), _jsx(Text, { children: "File writes and edits will auto-approve without asking." }), _jsx(Text, { dimColor: true, children: "Destructive shell commands (rm -rf, force-push, etc.) always still ask, in every mode." }), _jsx(Text, { dimColor: true, children: "Shift+Tab again moves to dry-run mode; once more back to normal." }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: "green", children: "Yes (y)" }), " \u00B7 No (n/Esc)"] }) })] })), phase === "confirmBypassMode" && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 1, children: [_jsx(Text, { bold: true, color: "red", children: "Switch to auto mode? (sandbox-gated)" }), _jsx(Text, { children: "EVERY tool call will auto-approve, including destructive shell commands (rm -rf, force-push, etc.) \u2014 nothing will ask first." }), _jsx(Text, { dimColor: true, children: "The sandbox \u2014 not a human \u2014 is what contains any damage this mode causes." }), _jsx(Text, { dimColor: true, children: "Shift+Tab again goes back to normal." }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: "red", children: "Yes (y)" }), " \u00B7 No (n/Esc)"] }) })] })), phase === "model" && (_jsx(ModelPicker, { current: model, customModels: config.customModels ?? [], onSelect: (id) => {
724
+ }, placeholder: "steer the agent\u2026 (Enter to queue)" })] })] })), phase === "permission" && permission && (_jsx(PermissionPrompt, { toolName: permission.toolName, summary: permission.summary, diff: permission.diff, warning: permission.warning, onDecision: onPermissionDecision })), phase === "elicitation" && elicitation && (_jsx(ElicitationPrompt, { message: elicitation.message, fields: elicitation.fields, onDecision: onElicitationDecision })), phase === "confirmMode" && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, children: [_jsx(Text, { bold: true, color: "green", children: "Switch to accept-edits mode?" }), _jsx(Text, { children: "File writes and edits will auto-approve without asking." }), _jsx(Text, { dimColor: true, children: "Destructive shell commands (rm -rf, force-push, etc.) always still ask, in every mode." }), _jsx(Text, { dimColor: true, children: "Shift+Tab again moves to dry-run mode; once more back to normal." }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: "green", children: "Yes (y)" }), " \u00B7 No (n/Esc)"] }) })] })), phase === "confirmBypassMode" && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 1, children: [_jsx(Text, { bold: true, color: "red", children: "Switch to auto mode? (sandbox-gated)" }), _jsx(Text, { children: "EVERY tool call will auto-approve, including destructive shell commands (rm -rf, force-push, etc.) \u2014 nothing will ask first." }), _jsx(Text, { dimColor: true, children: "The sandbox \u2014 not a human \u2014 is what contains any damage this mode causes." }), _jsx(Text, { dimColor: true, children: "Shift+Tab again goes back to normal." }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: "red", children: "Yes (y)" }), " \u00B7 No (n/Esc)"] }) })] })), phase === "model" && (_jsx(ModelPicker, { current: model, provider: provider, customModels: config.customModels ?? [], onSelect: (id) => {
725
725
  setPhase("input");
726
726
  setModelEverywhere(id);
727
727
  }, onCancel: () => setPhase("input") })), phase === "resume" && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, children: [_jsxs(Text, { bold: true, color: "magenta", children: ["Resume a session", " ", _jsx(Text, { dimColor: true, children: "(type to search \u00B7 Esc for a fresh one \u00B7 -r <name> to skip this)" })] }), resumeFilter ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "filter: " }), resumeFilter] })) : null, _jsx(SelectList, { items: (resumeSessions ?? [])
@@ -1,13 +1,26 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
- import { CURATED_MODELS } from "../config/models.js";
3
+ import { CURATED_MODELS, providerOfModel } from "../config/models.js";
4
4
  import { SelectList } from "./SelectList.js";
5
- export function ModelPicker({ current, customModels, onSelect, onCancel, }) {
5
+ /** Provider label for a hint, e.g. `groq` — what the user would pass to `-p`. */
6
+ function providerHint(m) {
7
+ return providerOfModel(m);
8
+ }
9
+ export function ModelPicker({ current, provider, customModels, onSelect, onCancel, }) {
10
+ // Show the active provider's models first and unlabelled, then any other
11
+ // providers' under a `provider/` prefix — so a Groq user sees Groq models
12
+ // at the top, and an NVIDIA user still sees the rest without the picker
13
+ // silently hiding them or lying about which provider they belong to.
14
+ const own = CURATED_MODELS.filter((m) => providerOfModel(m) === provider);
15
+ const others = CURATED_MODELS.filter((m) => providerOfModel(m) !== provider);
16
+ // Fall back to the whole list when the active provider has no curated
17
+ // models, so the picker is never empty.
18
+ const listed = own.length > 0 ? [...own, ...others] : CURATED_MODELS;
6
19
  const items = [
7
- ...CURATED_MODELS.map((m) => ({
20
+ ...listed.map((m) => ({
8
21
  label: m.label + (m.id === current ? " (current)" : ""),
9
22
  value: m.id,
10
- hint: m.note ? `${m.id} · ${m.note}` : m.id,
23
+ hint: `${m.id} · ${providerHint(m)}${m.note ? ` · ${m.note}` : ""}`,
11
24
  })),
12
25
  ...customModels.map((m) => ({
13
26
  label: (m.label ?? m.id) + (m.id === current ? " (current)" : ""),
@@ -15,5 +28,5 @@ export function ModelPicker({ current, customModels, onSelect, onCancel, }) {
15
28
  hint: `${m.id} · custom`,
16
29
  })),
17
30
  ];
18
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, children: [_jsxs(Text, { bold: true, color: "magenta", children: ["Select model", " ", _jsx(Text, { dimColor: true, children: "(Esc to cancel, or /model <id> for any NVIDIA model)" })] }), _jsx(SelectList, { items: items, onSelect: onSelect, onCancel: onCancel })] }));
31
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, children: [_jsxs(Text, { bold: true, color: "magenta", children: ["Select model", " ", _jsxs(Text, { dimColor: true, children: ["(Esc to cancel, or /model <id> for any ", provider, " model)"] })] }), _jsx(SelectList, { items: items, onSelect: onSelect, onCancel: onCancel })] }));
19
32
  }
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import path from "node:path";
3
3
  import { gitBranch } from "../git/git.js";
4
4
  import { listProviders, resolveProvider, saveConfig, saveProviderModel, } from "../config/config.js";
5
- import { DEFAULT_MODEL, contextWindowFor, displayModelId } from "../config/models.js";
5
+ import { defaultModelFor, contextWindowFor, displayModelId } from "../config/models.js";
6
6
  import { ProviderClient, RetryExhaustedError, friendlyProviderErrorHint, } from "../provider/client.js";
7
7
  import { createSwitchyardClient } from "../provider/switchyardClient.js";
8
8
  import { SWITCHYARD_ROUTE_ID, resolveEffectiveModel } from "../provider/switchyardSidecar.js";
@@ -382,7 +382,7 @@ export function useAgent({ agent, workspace, modelRef, providerRef, config, uiBr
382
382
  providerDefaultModel,
383
383
  name === "switchyard" ? SWITCHYARD_ROUTE_ID : undefined,
384
384
  modelRef.current,
385
- ], name === "switchyard" ? SWITCHYARD_ROUTE_ID : DEFAULT_MODEL);
385
+ ], name === "switchyard" ? SWITCHYARD_ROUTE_ID : defaultModelFor(name));
386
386
  let note = `Switched provider to ${name} — conversation history kept.`;
387
387
  if (targetModel !== modelRef.current) {
388
388
  setModelEverywhere(targetModel);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kritya",
3
- "version": "0.8.48-beta",
3
+ "version": "0.8.49-beta",
4
4
  "description": "Kritya — Superintelligence at your command line",
5
5
  "type": "module",
6
6
  "publishConfig": {