koneck 2.25.60 → 2.25.61
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/dist/config-store.d.ts +8 -0
- package/dist/config-store.d.ts.map +1 -1
- package/dist/config-store.js +23 -2
- package/dist/config-store.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/endpoints.d.ts +72 -0
- package/dist/endpoints.d.ts.map +1 -0
- package/dist/endpoints.js +148 -0
- package/dist/endpoints.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +183 -17
- package/dist/ink-chat.js.map +1 -1
- package/package.json +1 -1
package/dist/ink-chat.js
CHANGED
|
@@ -8,6 +8,8 @@ import { generateSessionId, saveSession, listSessions, loadSession, relativeAge,
|
|
|
8
8
|
import { loadMemory } from './memory.js';
|
|
9
9
|
import { loadKoneckConfig, saveKoneckConfig, setConfigKey, configKeyDescriptions, CONFIG_FILE, CONFIG_SCHEMA, stepValue, displayValue } from './config-store.js';
|
|
10
10
|
import { PROVIDERS, resolveProvider, getApiKey, addUserProvider, userProvidersPath } from './providers.js';
|
|
11
|
+
import { resolveEndpoint, normaliseEndpoint, withEndpoint, savedEndpoints } from './endpoints.js';
|
|
12
|
+
import { loadProjectConfig, loadGlobalConfig } from './config.js';
|
|
11
13
|
import { listCheckpoints, revertCheckpoint, snapshotTree, filesTouchedBy } from './checkpoint.js';
|
|
12
14
|
import { fileDiff, diffSummary } from './diff-view.js';
|
|
13
15
|
import { listWorkspaceEntries, filterEntries, activeReference } from './workspace-files.js';
|
|
@@ -498,6 +500,7 @@ const COMMANDS = [
|
|
|
498
500
|
{ cmd: '/help', desc: 'Show help' },
|
|
499
501
|
{ cmd: '/models', desc: 'Browse and switch models for the connected provider' },
|
|
500
502
|
{ cmd: '/connect', desc: 'Connect a provider, or declare a new one with a URL' },
|
|
503
|
+
{ cmd: '/endpoint', desc: "Show or change this provider's URL, e.g. a remote Ollama" },
|
|
501
504
|
{ cmd: '/key', desc: 'Enter an API key for the current provider' },
|
|
502
505
|
{ cmd: '/model', desc: 'Show or set the model by name' },
|
|
503
506
|
{ cmd: '/provider', desc: 'Show or switch provider' },
|
|
@@ -706,7 +709,7 @@ export function pickerLabelWidth(labels, descs, width) {
|
|
|
706
709
|
*/
|
|
707
710
|
const COMMANDS_THAT_MUST_WAIT = new Set([
|
|
708
711
|
'/clear', '/reset', '/new', // would wipe the history the turn is using
|
|
709
|
-
'/model', '/provider', '/connect', '/key', // rebuild the session
|
|
712
|
+
'/model', '/provider', '/connect', '/key', '/endpoint', '/baseurl', // rebuild the session
|
|
710
713
|
'/resume', '/fork', // replace it
|
|
711
714
|
'/revert', // undo files the turn may still be writing
|
|
712
715
|
'/init', '/setup',
|
|
@@ -730,6 +733,9 @@ Slash commands:
|
|
|
730
733
|
/connect [provider] Connect a provider (picker when no name given)
|
|
731
734
|
/connect <name> <url> Declare a provider KONECK does not ship with, e.g.
|
|
732
735
|
/connect openrouter https://openrouter.ai/api/v1
|
|
736
|
+
/endpoint Show the endpoint in force and where it came from
|
|
737
|
+
/endpoint <url> Point this provider somewhere else, e.g.
|
|
738
|
+
/endpoint http://192.168.1.50:11434/v1
|
|
733
739
|
/key Enter an API key for the current provider
|
|
734
740
|
/status Provider, model, mode, tokens, cost, CWD
|
|
735
741
|
/clear /reset /new Clear conversation history
|
|
@@ -1186,6 +1192,28 @@ function App({ config: initialConfig }) {
|
|
|
1186
1192
|
setConfigMaxWidth(typeof c.maxWidth === 'number' && c.maxWidth >= 40 ? c.maxWidth : undefined);
|
|
1187
1193
|
});
|
|
1188
1194
|
}, []);
|
|
1195
|
+
/**
|
|
1196
|
+
* The config layers other than the store, kept so an endpoint can be resolved and, more to the
|
|
1197
|
+
* point, explained. "It still sticks to the already set ollama" is a question about which file
|
|
1198
|
+
* won, and the answer is not available from the store alone.
|
|
1199
|
+
*/
|
|
1200
|
+
const [outerLayers, setOuterLayers] = useState({ project: {}, global: {} });
|
|
1201
|
+
useEffect(() => {
|
|
1202
|
+
void Promise.all([loadProjectConfig(cfg.cwd), loadGlobalConfig()])
|
|
1203
|
+
.then(([project, global]) => setOuterLayers({ project, global }))
|
|
1204
|
+
.catch(() => { });
|
|
1205
|
+
}, [cfg.cwd]);
|
|
1206
|
+
/** Nearest first, which is the order resolveEndpoint reads them in. */
|
|
1207
|
+
const endpointLayers = (store = stored) => [outerLayers.project, store, outerLayers.global];
|
|
1208
|
+
/** The endpoint in force for a provider, with where it came from. */
|
|
1209
|
+
function endpointFor(provider, store) {
|
|
1210
|
+
const def = PROVIDERS[provider.trim().toLowerCase()];
|
|
1211
|
+
return resolveEndpoint(provider, endpointLayers(store), {
|
|
1212
|
+
fallback: def?.baseURL ?? '',
|
|
1213
|
+
baseURLEnv: def?.baseURLEnv,
|
|
1214
|
+
fallbackIsUserDeclared: def?.userDefined === true,
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1189
1217
|
useEffect(() => {
|
|
1190
1218
|
const onResize = () => {
|
|
1191
1219
|
setTermCols(process.stdout.columns || 80);
|
|
@@ -1529,7 +1557,58 @@ function App({ config: initialConfig }) {
|
|
|
1529
1557
|
* Persisting alone would leave the running session on the old value until restart, which is the
|
|
1530
1558
|
* behaviour that made the old `/config` feel inert even when it had worked.
|
|
1531
1559
|
*/
|
|
1560
|
+
/**
|
|
1561
|
+
* What a setting is worth right now, wherever the value came from.
|
|
1562
|
+
*
|
|
1563
|
+
* The store is only one of three files. Reading a row from it alone showed "(not set)" beside a
|
|
1564
|
+
* session that was plainly configured, and — with endpoints saved per provider — would show
|
|
1565
|
+
* nothing at all for an endpoint that is in force.
|
|
1566
|
+
*/
|
|
1567
|
+
function liveSetting(key) {
|
|
1568
|
+
switch (key) {
|
|
1569
|
+
case 'provider': return cfg.provider;
|
|
1570
|
+
case 'model': return cfg.model;
|
|
1571
|
+
case 'baseURL': return endpointFor(cfg.provider).url;
|
|
1572
|
+
case 'effort': return effort;
|
|
1573
|
+
case 'requireApproval': return cfg.requireApproval;
|
|
1574
|
+
case 'maxTurns': return cfg.maxTurns;
|
|
1575
|
+
case 'agentConcurrency': return cfg.agentConcurrency;
|
|
1576
|
+
case 'maxWidth': return configMaxWidth;
|
|
1577
|
+
case 'budget': return cfg.budget;
|
|
1578
|
+
default: return stored[key];
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
/** The text an edit box opens on: what the row is showing. */
|
|
1582
|
+
function settingText(field) {
|
|
1583
|
+
const key = String(field.key);
|
|
1584
|
+
const value = key === 'baseURL' || stored[key] === undefined ? liveSetting(key) : stored[key];
|
|
1585
|
+
return value === undefined || value === null ? '' : String(value);
|
|
1586
|
+
}
|
|
1532
1587
|
async function applySetting(field, value) {
|
|
1588
|
+
// The endpoint row is not a plain setting: it is one provider's address, so it is saved
|
|
1589
|
+
// against the provider in force rather than into a single slot every provider shares.
|
|
1590
|
+
if (field.key === 'baseURL') {
|
|
1591
|
+
let url;
|
|
1592
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
1593
|
+
const checked = normaliseEndpoint(value);
|
|
1594
|
+
if (!checked.url) {
|
|
1595
|
+
addSystem(checked.problem ?? `"${value}" is not a URL.`);
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
url = checked.url;
|
|
1599
|
+
if (checked.note)
|
|
1600
|
+
addSystem(checked.note);
|
|
1601
|
+
}
|
|
1602
|
+
const saved = withEndpoint(await loadKoneckConfig(), cfg.provider, url);
|
|
1603
|
+
await saveKoneckConfig(saved);
|
|
1604
|
+
setStored(saved);
|
|
1605
|
+
const now = endpointFor(cfg.provider, saved);
|
|
1606
|
+
const merged = { ...cfg, baseURL: now.url };
|
|
1607
|
+
setCfg(merged);
|
|
1608
|
+
await resetSession(merged);
|
|
1609
|
+
addSystem(`**${cfg.provider}** → ${now.url} (${now.source})`);
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1533
1612
|
const next = { ...stored, [field.key]: value };
|
|
1534
1613
|
if (value === undefined)
|
|
1535
1614
|
delete next[field.key];
|
|
@@ -1542,9 +1621,21 @@ function App({ config: initialConfig }) {
|
|
|
1542
1621
|
case 'maxWidth':
|
|
1543
1622
|
setConfigMaxWidth(typeof value === 'number' && value >= 40 ? value : undefined);
|
|
1544
1623
|
break;
|
|
1624
|
+
case 'provider': {
|
|
1625
|
+
// Changing the provider here has to move the endpoint with it, or the new provider is
|
|
1626
|
+
// addressed at the old one's URL — the same fault /provider had.
|
|
1627
|
+
const name = typeof value === 'string' && value.trim() !== '' ? value.trim() : cfg.provider;
|
|
1628
|
+
const def = PROVIDERS[name.toLowerCase()];
|
|
1629
|
+
const merged = {
|
|
1630
|
+
...cfg, provider: name,
|
|
1631
|
+
model: def?.defaultModel ?? cfg.model,
|
|
1632
|
+
baseURL: endpointFor(name, next).url,
|
|
1633
|
+
};
|
|
1634
|
+
setCfg(merged);
|
|
1635
|
+
await resetSession(merged);
|
|
1636
|
+
break;
|
|
1637
|
+
}
|
|
1545
1638
|
case 'model':
|
|
1546
|
-
case 'provider':
|
|
1547
|
-
case 'baseURL':
|
|
1548
1639
|
case 'maxTurns':
|
|
1549
1640
|
case 'requireApproval':
|
|
1550
1641
|
case 'agentConcurrency':
|
|
@@ -1745,7 +1836,11 @@ function App({ config: initialConfig }) {
|
|
|
1745
1836
|
}
|
|
1746
1837
|
if (kind === 'provider') {
|
|
1747
1838
|
const def = PROVIDERS[item.value];
|
|
1748
|
-
|
|
1839
|
+
// The endpoint saved for the provider being switched to, not its built-in address. Passing
|
|
1840
|
+
// it into resolveProvider also settles the credential question honestly: a remote Ollama is
|
|
1841
|
+
// not loopback and does need a key, whatever the provider is called.
|
|
1842
|
+
const endpoint = endpointFor(item.value);
|
|
1843
|
+
const resolved = resolveProvider(item.value, endpoint.url);
|
|
1749
1844
|
const next = {
|
|
1750
1845
|
...cfg, provider: item.value,
|
|
1751
1846
|
model: def?.defaultModel ?? cfg.model,
|
|
@@ -1763,7 +1858,11 @@ function App({ config: initialConfig }) {
|
|
|
1763
1858
|
}
|
|
1764
1859
|
if (usable) {
|
|
1765
1860
|
await resetSession(next);
|
|
1766
|
-
|
|
1861
|
+
const savedCfg = { ...(await loadKoneckConfig()), provider: item.value, model: next.model };
|
|
1862
|
+
await saveKoneckConfig(savedCfg);
|
|
1863
|
+
setStored(savedCfg);
|
|
1864
|
+
addSystem(`Provider → ${item.value} · model → ${next.model}\n` +
|
|
1865
|
+
`Endpoint ${resolved.baseURL} (${endpoint.source}) · session reset`);
|
|
1767
1866
|
return;
|
|
1768
1867
|
}
|
|
1769
1868
|
setCfg(next);
|
|
@@ -1853,10 +1952,74 @@ function App({ config: initialConfig }) {
|
|
|
1853
1952
|
addSystem(`Current provider: ${cfg.provider}\nAvailable: ${Object.keys(PROVIDERS).join(', ')}\nUsage: /provider <name>`);
|
|
1854
1953
|
return;
|
|
1855
1954
|
}
|
|
1856
|
-
const
|
|
1857
|
-
const
|
|
1955
|
+
const name = arg.trim().toLowerCase();
|
|
1956
|
+
const def = PROVIDERS[name];
|
|
1957
|
+
if (!def) {
|
|
1958
|
+
// Switching to a name that does not resolve used to reset the session anyway, leaving it
|
|
1959
|
+
// pointed at nothing and failing on the next message instead of on the command.
|
|
1960
|
+
addSystem(`Unknown provider "${arg}". Known: ${Object.keys(PROVIDERS).join(', ')}\n` +
|
|
1961
|
+
`Declare one: /connect ${name} https://endpoint/v1`);
|
|
1962
|
+
return;
|
|
1963
|
+
}
|
|
1964
|
+
// The endpoint is the provider's own, not a literal default: whatever was saved for it,
|
|
1965
|
+
// or its environment variable, still applies. Naming a provider used to overwrite the
|
|
1966
|
+
// address with PROVIDERS[name].baseURL, which is how a configured Ollama kept coming back
|
|
1967
|
+
// as localhost.
|
|
1968
|
+
const endpoint = endpointFor(name);
|
|
1969
|
+
const newCfg = { ...cfg, provider: name, model: def.defaultModel ?? cfg.model, baseURL: endpoint.url };
|
|
1858
1970
|
await resetSession(newCfg);
|
|
1859
|
-
|
|
1971
|
+
// Persisted, or the switch is undone by the next start and the old provider wins again.
|
|
1972
|
+
const savedCfg = { ...(await loadKoneckConfig()), provider: name, model: newCfg.model };
|
|
1973
|
+
await saveKoneckConfig(savedCfg);
|
|
1974
|
+
setStored(savedCfg);
|
|
1975
|
+
addSystem(`Provider → ${name} model → ${newCfg.model}\n` +
|
|
1976
|
+
`Endpoint ${endpoint.url} (${endpoint.source})\n` +
|
|
1977
|
+
`Session reset. /endpoint <url> changes the address.`);
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
case '/url':
|
|
1981
|
+
case '/baseurl':
|
|
1982
|
+
case '/endpoint': {
|
|
1983
|
+
const provider = cfg.provider;
|
|
1984
|
+
const current = endpointFor(provider);
|
|
1985
|
+
if (!arg.trim()) {
|
|
1986
|
+
// No argument is the diagnostic. The complaint that produced this command was about a
|
|
1987
|
+
// change that appeared not to take, and the only useful reply names the file that won.
|
|
1988
|
+
const all = savedEndpoints(endpointLayers());
|
|
1989
|
+
const others = Object.entries(all).filter(([name]) => name !== provider.toLowerCase());
|
|
1990
|
+
addSystem(`**${provider}** → ${current.url}\n` +
|
|
1991
|
+
`Source: ${current.source}\n\n` +
|
|
1992
|
+
(others.length
|
|
1993
|
+
? 'Also saved:\n' + others.map(([n, u]) => ` ${n.padEnd(12)} ${u}`).join('\n') + '\n\n'
|
|
1994
|
+
: '') +
|
|
1995
|
+
`Change it: /endpoint http://192.168.1.50:11434/v1\n` +
|
|
1996
|
+
`Undo it: /endpoint default`);
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
const wanted = arg.trim();
|
|
2000
|
+
if (/^(default|reset|clear|unset)$/i.test(wanted)) {
|
|
2001
|
+
const saved = withEndpoint(await loadKoneckConfig(), provider, undefined);
|
|
2002
|
+
await saveKoneckConfig(saved);
|
|
2003
|
+
setStored(saved);
|
|
2004
|
+
const back = endpointFor(provider, saved);
|
|
2005
|
+
await resetSession({ ...cfg, baseURL: back.url });
|
|
2006
|
+
addSystem(`**${provider}** → ${back.url} (${back.source})\nSession reset.`);
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
const { url, problem, note } = normaliseEndpoint(wanted);
|
|
2010
|
+
if (!url) {
|
|
2011
|
+
addSystem(`${problem}\nUsage: /endpoint http://host:port/v1`);
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
// Saved against the provider, not as a global address: setting Ollama's URL used to move
|
|
2015
|
+
// OmniRoute's too, and one of the two was then wrong whichever way round it was left.
|
|
2016
|
+
const saved = withEndpoint(await loadKoneckConfig(), provider, url);
|
|
2017
|
+
await saveKoneckConfig(saved);
|
|
2018
|
+
setStored(saved);
|
|
2019
|
+
await resetSession({ ...cfg, baseURL: url });
|
|
2020
|
+
addSystem(`**${provider}** → ${url}\n` +
|
|
2021
|
+
`Saved for ${provider} in ${CONFIG_FILE}; session reset.` +
|
|
2022
|
+
(note ? `\n${note}` : ''));
|
|
1860
2023
|
return;
|
|
1861
2024
|
}
|
|
1862
2025
|
case '/mode':
|
|
@@ -2757,7 +2920,10 @@ function App({ config: initialConfig }) {
|
|
|
2757
2920
|
void applySetting(field, stepValue(field, stored[field.key], 1));
|
|
2758
2921
|
}
|
|
2759
2922
|
else {
|
|
2760
|
-
|
|
2923
|
+
// Opened on the current value rather than on an empty box. Editing a URL by retyping
|
|
2924
|
+
// all of it is not editing, and an empty box next to a value that is plainly set
|
|
2925
|
+
// reads as "this cannot be changed".
|
|
2926
|
+
setEditing({ key: String(field.key), value: settingText(field) });
|
|
2761
2927
|
}
|
|
2762
2928
|
return;
|
|
2763
2929
|
}
|
|
@@ -3268,23 +3434,23 @@ function App({ config: initialConfig }) {
|
|
|
3268
3434
|
// What is actually in force, not just what this file happens to hold. provider and
|
|
3269
3435
|
// baseURL commonly come from ~/.koneckrc, and showing those as "(not set)" made a
|
|
3270
3436
|
// configured session look unconfigured.
|
|
3271
|
-
const
|
|
3272
|
-
provider: cfg.provider, model: cfg.model, baseURL: cfg.baseURL,
|
|
3273
|
-
effort, requireApproval: cfg.requireApproval, maxTurns: cfg.maxTurns,
|
|
3274
|
-
agentConcurrency: cfg.agentConcurrency, maxWidth: configMaxWidth, budget: cfg.budget,
|
|
3275
|
-
};
|
|
3437
|
+
const endpoint = endpointFor(cfg.provider);
|
|
3276
3438
|
const labelW = Math.max(...CONFIG_SCHEMA.map(f => f.label.length));
|
|
3277
3439
|
for (const [i, f] of CONFIG_SCHEMA.entries()) {
|
|
3278
3440
|
const selected = i === settingRow;
|
|
3279
3441
|
const beingTyped = editing?.key === String(f.key);
|
|
3280
|
-
|
|
3281
|
-
|
|
3442
|
+
// An endpoint always has a source worth naming, so it never reads as unset or as
|
|
3443
|
+
// merely inherited: the question it answers is which file won.
|
|
3444
|
+
const isEndpoint = f.key === 'baseURL';
|
|
3445
|
+
const here = !isEndpoint && stored[f.key] !== undefined;
|
|
3446
|
+
const value = here ? stored[f.key] : liveSetting(String(f.key));
|
|
3282
3447
|
const raw = f.key === 'maxTurns' && (value === 0 || value === undefined)
|
|
3283
3448
|
? 'no limit'
|
|
3284
3449
|
: displayValue(value);
|
|
3285
3450
|
const shown = beingTyped
|
|
3286
3451
|
? `${editing.value}_`
|
|
3287
|
-
:
|
|
3452
|
+
: isEndpoint ? `${raw} (${endpoint.source})`
|
|
3453
|
+
: raw + (here || value === undefined ? '' : ' (inherited)');
|
|
3288
3454
|
// The marker is what makes the selection legible without colour, which a panel line
|
|
3289
3455
|
// drawn as one string cannot carry per-segment.
|
|
3290
3456
|
const mark = selected ? '>' : ' ';
|