koneck 2.25.65 → 2.25.67
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 +25 -4
- 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/context-limit.d.ts +28 -0
- package/dist/context-limit.d.ts.map +1 -1
- package/dist/context-limit.js +43 -0
- package/dist/context-limit.js.map +1 -1
- package/dist/endpoints.d.ts +29 -1
- package/dist/endpoints.d.ts.map +1 -1
- package/dist/endpoints.js +69 -1
- package/dist/endpoints.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +55 -15
- package/dist/engine.js.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +95 -28
- package/dist/ink-chat.js.map +1 -1
- package/dist/providers.d.ts +24 -0
- package/dist/providers.d.ts.map +1 -1
- package/dist/providers.js +73 -10
- package/dist/providers.js.map +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/ink-chat.js
CHANGED
|
@@ -2,13 +2,13 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import React, { useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { Box, Static, Text, render, useApp, useInput, useStdin } from 'ink';
|
|
4
4
|
import { execa } from 'execa';
|
|
5
|
-
import { createAgentSession, buildClient, resolveAgentConcurrency, toolsUnsupportedNotice
|
|
5
|
+
import { createAgentSession, buildClient, resolveAgentConcurrency, toolsUnsupportedNotice } from './engine.js';
|
|
6
6
|
import { estimateCost, formatCost } from './pricing.js';
|
|
7
7
|
import { generateSessionId, saveSession, listSessions, loadSession, relativeAge, renameSession, forkSession, archiveSession, findSession, } from './session.js';
|
|
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
|
-
import { PROVIDERS, resolveProvider, getApiKey, addUserProvider, userProvidersPath } from './providers.js';
|
|
11
|
-
import { resolveEndpoint, normaliseEndpoint, withEndpoint, savedEndpoints, shortSource } from './endpoints.js';
|
|
10
|
+
import { PROVIDERS, resolveProvider, getApiKey, addUserProvider, userProvidersPath, needsNoCredential, servesLocally } from './providers.js';
|
|
11
|
+
import { resolveEndpoint, normaliseEndpoint, withEndpoint, savedEndpoints, shortSource, resolveModelChoice, withModel } from './endpoints.js';
|
|
12
12
|
import { loadProjectConfig, loadGlobalConfig } from './config.js';
|
|
13
13
|
import { listCheckpoints, revertCheckpoint, snapshotTree, filesTouchedBy } from './checkpoint.js';
|
|
14
14
|
import { fileDiff, diffSummary } from './diff-view.js';
|
|
@@ -810,6 +810,8 @@ function App({ config: initialConfig, clearFrame }) {
|
|
|
810
810
|
const incompleteRef = useRef(null);
|
|
811
811
|
/** The model already reported as unable to call tools, so the notice is not repeated. */
|
|
812
812
|
const toolsWarnedRef = useRef(null);
|
|
813
|
+
/** The crowded-window notice is about the setup, so it is said once per session. */
|
|
814
|
+
const crowdedWarnedRef = useRef(false);
|
|
813
815
|
/**
|
|
814
816
|
* The id to save under, mirrored in a ref.
|
|
815
817
|
*
|
|
@@ -1031,16 +1033,25 @@ function App({ config: initialConfig, clearFrame }) {
|
|
|
1031
1033
|
toolsWarnedRef.current = model;
|
|
1032
1034
|
addSystem(toolsUnsupportedNotice(model));
|
|
1033
1035
|
},
|
|
1036
|
+
onCrowdedWindow: (_fixed, _limit, advice) => {
|
|
1037
|
+
// Once per session: it is a property of the setup, not of the turn.
|
|
1038
|
+
if (crowdedWarnedRef.current)
|
|
1039
|
+
return;
|
|
1040
|
+
crowdedWarnedRef.current = true;
|
|
1041
|
+
addSystem(advice);
|
|
1042
|
+
},
|
|
1034
1043
|
onLooping: (copies) => {
|
|
1035
1044
|
// The loop itself is the symptom. On a local model the cause is almost always the window:
|
|
1036
1045
|
// Ollama serves 4096 tokens unless told otherwise, whatever the model was trained for, and
|
|
1037
1046
|
// a conversation that no longer fits is exactly what makes a model start repeating.
|
|
1038
|
-
|
|
1047
|
+
// Judged by the endpoint, not the provider's name: a LAN Ollama reached through a slot
|
|
1048
|
+
// called "omni" is still Ollama, and asking about the name missed exactly that setup.
|
|
1049
|
+
const local = servesLocally(cfg.provider, endpointFor(cfg.provider).url);
|
|
1039
1050
|
addSystem(`${cfg.model} repeated the same text ${copies} times, so the reply was cut short.` +
|
|
1040
1051
|
(local
|
|
1041
|
-
? `\n\nThis is usually the context window rather than the model. Ollama serves
|
|
1042
|
-
`tokens by default however large the model is — set ` +
|
|
1043
|
-
|
|
1052
|
+
? `\n\nThis is usually the context window rather than the model. Ollama serves 4,096 ` +
|
|
1053
|
+
`tokens by default however large the model is — set \`OLLAMA_CONTEXT_LENGTH=32768\` ` +
|
|
1054
|
+
`where Ollama runs, restart it, then \`/context\` to confirm.`
|
|
1044
1055
|
: ''));
|
|
1045
1056
|
},
|
|
1046
1057
|
onThinking: (text, totalChars) => {
|
|
@@ -1250,6 +1261,23 @@ function App({ config: initialConfig, clearFrame }) {
|
|
|
1250
1261
|
}, [cfg.cwd]);
|
|
1251
1262
|
/** Nearest first, which is the order resolveEndpoint reads them in. */
|
|
1252
1263
|
const endpointLayers = (store = stored) => [outerLayers.project, store, outerLayers.global];
|
|
1264
|
+
/**
|
|
1265
|
+
* The model in force for a provider, with where the choice came from.
|
|
1266
|
+
*
|
|
1267
|
+
* A model belongs to a provider: qwen3-coder:30b means nothing to Anthropic. Reported as
|
|
1268
|
+
* "koneck keeps reverting the model to codellama" — which is the Ollama definition's built-in
|
|
1269
|
+
* default, reached because nothing else was ever saved against ollama.
|
|
1270
|
+
*/
|
|
1271
|
+
function modelFor(provider, store) {
|
|
1272
|
+
const def = PROVIDERS[provider.trim().toLowerCase()];
|
|
1273
|
+
return resolveModelChoice(provider, endpointLayers(store), def?.defaultModel ?? 'auto');
|
|
1274
|
+
}
|
|
1275
|
+
/** Remembers a model against the provider it belongs to. */
|
|
1276
|
+
async function rememberModel(provider, model) {
|
|
1277
|
+
const saved = withModel(await loadKoneckConfig(), provider, model);
|
|
1278
|
+
await saveKoneckConfig(saved);
|
|
1279
|
+
setStored(saved);
|
|
1280
|
+
}
|
|
1253
1281
|
/** The endpoint in force for a provider, with where it came from. */
|
|
1254
1282
|
function endpointFor(provider, store) {
|
|
1255
1283
|
const def = PROVIDERS[provider.trim().toLowerCase()];
|
|
@@ -1654,10 +1682,25 @@ function App({ config: initialConfig, clearFrame }) {
|
|
|
1654
1682
|
/** The text an edit box opens on: what the row is showing. */
|
|
1655
1683
|
function settingText(field) {
|
|
1656
1684
|
const key = String(field.key);
|
|
1657
|
-
const value = key === 'baseURL' ||
|
|
1685
|
+
const value = key === 'baseURL' || key === 'model' || stored[key] === undefined
|
|
1686
|
+
? liveSetting(key) : stored[key];
|
|
1658
1687
|
return value === undefined || value === null ? '' : String(value);
|
|
1659
1688
|
}
|
|
1660
1689
|
async function applySetting(field, value) {
|
|
1690
|
+
// The model row is one provider's model, so it is saved against the provider in force. In the
|
|
1691
|
+
// single shared slot, choosing a model for Ollama also chose it for Anthropic.
|
|
1692
|
+
if (field.key === 'model') {
|
|
1693
|
+
const name = typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
|
|
1694
|
+
const saved = withModel(await loadKoneckConfig(), cfg.provider, name);
|
|
1695
|
+
await saveKoneckConfig(saved);
|
|
1696
|
+
setStored(saved);
|
|
1697
|
+
const now = modelFor(cfg.provider, saved);
|
|
1698
|
+
const merged = { ...cfg, model: now.model };
|
|
1699
|
+
setCfg(merged);
|
|
1700
|
+
await resetSession(merged);
|
|
1701
|
+
addSystem(`**${cfg.provider}** → ${now.model} (${now.source})`);
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1661
1704
|
// The endpoint row is not a plain setting: it is one provider's address, so it is saved
|
|
1662
1705
|
// against the provider in force rather than into a single slot every provider shares.
|
|
1663
1706
|
if (field.key === 'baseURL') {
|
|
@@ -1695,20 +1738,19 @@ function App({ config: initialConfig, clearFrame }) {
|
|
|
1695
1738
|
setConfigMaxWidth(typeof value === 'number' && value >= 40 ? value : undefined);
|
|
1696
1739
|
break;
|
|
1697
1740
|
case 'provider': {
|
|
1698
|
-
// Changing the provider here has to move the endpoint with it, or the new
|
|
1699
|
-
// addressed at the old one's URL
|
|
1741
|
+
// Changing the provider here has to move the endpoint and the model with it, or the new
|
|
1742
|
+
// provider is addressed at the old one's URL and asked for the old one's model — the same
|
|
1743
|
+
// fault /provider had, in a second place.
|
|
1700
1744
|
const name = typeof value === 'string' && value.trim() !== '' ? value.trim() : cfg.provider;
|
|
1701
|
-
const def = PROVIDERS[name.toLowerCase()];
|
|
1702
1745
|
const merged = {
|
|
1703
1746
|
...cfg, provider: name,
|
|
1704
|
-
model:
|
|
1747
|
+
model: resolveModelChoice(name, endpointLayers(next), PROVIDERS[name.toLowerCase()]?.defaultModel ?? 'auto').model,
|
|
1705
1748
|
baseURL: endpointFor(name, next).url,
|
|
1706
1749
|
};
|
|
1707
1750
|
setCfg(merged);
|
|
1708
1751
|
await resetSession(merged);
|
|
1709
1752
|
break;
|
|
1710
1753
|
}
|
|
1711
|
-
case 'model':
|
|
1712
1754
|
case 'maxTurns':
|
|
1713
1755
|
case 'requireApproval':
|
|
1714
1756
|
case 'agentConcurrency':
|
|
@@ -1799,12 +1841,16 @@ function App({ config: initialConfig, clearFrame }) {
|
|
|
1799
1841
|
async function fetchModels() {
|
|
1800
1842
|
const infos = await loadModelCatalog();
|
|
1801
1843
|
const statuses = readModelStatus();
|
|
1844
|
+
const selfHosted = needsNoCredential(resolveProvider(cfg.provider, endpointFor(cfg.provider).url));
|
|
1802
1845
|
return [...infos]
|
|
1803
1846
|
.sort((a, b) => a.id.localeCompare(b.id))
|
|
1804
1847
|
.map(m => {
|
|
1805
1848
|
const st = statuses[`${cfg.provider}:${m.id}`];
|
|
1806
1849
|
const parts = [
|
|
1807
|
-
|
|
1850
|
+
// "paid" is a claim about somebody's billing, and a model on your own hardware has no
|
|
1851
|
+
// billing. The catalogue's free flag describes a gateway's tiers; it means nothing for
|
|
1852
|
+
// a runtime you are running, and calling qwen3-coder on a LAN box "paid" is just wrong.
|
|
1853
|
+
selfHosted ? 'local' : m.free ? 'free' : 'paid',
|
|
1808
1854
|
...(m.contextLength ? [`${humanTokens(m.contextLength)} ctx`] : []),
|
|
1809
1855
|
...(m.toolCalling === false ? ['no tools'] : []),
|
|
1810
1856
|
...(st && st.state !== 'ok' ? [stateLabel(st.state)] : st ? ['ready'] : []),
|
|
@@ -1904,7 +1950,10 @@ function App({ config: initialConfig, clearFrame }) {
|
|
|
1904
1950
|
if (kind === 'model') {
|
|
1905
1951
|
const next = { ...cfg, model: item.value };
|
|
1906
1952
|
await resetSession(next);
|
|
1907
|
-
|
|
1953
|
+
// Saved against the provider. Choosing a model used to change the running session and
|
|
1954
|
+
// nothing else, so it lasted until the next provider switch or the next start.
|
|
1955
|
+
await rememberModel(cfg.provider, item.value);
|
|
1956
|
+
addSystem(`Model → ${item.value} · remembered for ${cfg.provider} (session reset)`);
|
|
1908
1957
|
return;
|
|
1909
1958
|
}
|
|
1910
1959
|
if (kind === 'provider') {
|
|
@@ -1914,9 +1963,12 @@ function App({ config: initialConfig, clearFrame }) {
|
|
|
1914
1963
|
// not loopback and does need a key, whatever the provider is called.
|
|
1915
1964
|
const endpoint = endpointFor(item.value);
|
|
1916
1965
|
const resolved = resolveProvider(item.value, endpoint.url);
|
|
1966
|
+
// Whichever model was last used with this provider, not its built-in default. The old line
|
|
1967
|
+
// reached for PROVIDERS[name].defaultModel — `codellama` for Ollama — and wrote it to disk.
|
|
1968
|
+
const chosen = modelFor(item.value);
|
|
1917
1969
|
const next = {
|
|
1918
1970
|
...cfg, provider: item.value,
|
|
1919
|
-
model:
|
|
1971
|
+
model: chosen.model,
|
|
1920
1972
|
baseURL: resolved.baseURL,
|
|
1921
1973
|
apiKey: undefined, // a key for the previous provider must not carry over
|
|
1922
1974
|
};
|
|
@@ -1931,10 +1983,13 @@ function App({ config: initialConfig, clearFrame }) {
|
|
|
1931
1983
|
}
|
|
1932
1984
|
if (usable) {
|
|
1933
1985
|
await resetSession(next);
|
|
1934
|
-
|
|
1986
|
+
// The provider is persisted; the model is not, because it is already stored against the
|
|
1987
|
+
// provider it belongs to. Writing it into the shared `model` key is what made a switch to
|
|
1988
|
+
// ollama leave `codellama` on disk for everything else to inherit.
|
|
1989
|
+
const savedCfg = { ...(await loadKoneckConfig()), provider: item.value };
|
|
1935
1990
|
await saveKoneckConfig(savedCfg);
|
|
1936
1991
|
setStored(savedCfg);
|
|
1937
|
-
addSystem(`Provider → ${item.value} · model → ${next.model}\n` +
|
|
1992
|
+
addSystem(`Provider → ${item.value} · model → ${next.model} (${chosen.source})\n` +
|
|
1938
1993
|
`Endpoint ${resolved.baseURL} (${endpoint.source}) · session reset`);
|
|
1939
1994
|
return;
|
|
1940
1995
|
}
|
|
@@ -2012,12 +2067,17 @@ function App({ config: initialConfig, clearFrame }) {
|
|
|
2012
2067
|
}
|
|
2013
2068
|
case '/model': {
|
|
2014
2069
|
if (!arg) {
|
|
2015
|
-
|
|
2070
|
+
const now = modelFor(cfg.provider);
|
|
2071
|
+
addSystem(`Current model: ${cfg.model} (${now.source})\n` +
|
|
2072
|
+
`Usage: /model <name> — remembered for ${cfg.provider}`);
|
|
2016
2073
|
return;
|
|
2017
2074
|
}
|
|
2018
2075
|
const newCfg = { ...cfg, model: arg };
|
|
2019
2076
|
await resetSession(newCfg);
|
|
2020
|
-
|
|
2077
|
+
// Persisted against the provider. It used to change the running session only, so the
|
|
2078
|
+
// next start or the next provider switch put the old model back.
|
|
2079
|
+
await rememberModel(cfg.provider, arg);
|
|
2080
|
+
addSystem(`Model → ${arg} · remembered for ${cfg.provider} (session reset)`);
|
|
2021
2081
|
return;
|
|
2022
2082
|
}
|
|
2023
2083
|
case '/provider': {
|
|
@@ -2039,15 +2099,19 @@ function App({ config: initialConfig, clearFrame }) {
|
|
|
2039
2099
|
// address with PROVIDERS[name].baseURL, which is how a configured Ollama kept coming back
|
|
2040
2100
|
// as localhost.
|
|
2041
2101
|
const endpoint = endpointFor(name);
|
|
2042
|
-
|
|
2102
|
+
// Whichever model was last used with this provider. Forcing def.defaultModel here — and
|
|
2103
|
+
// then writing it to disk — is what "keeps reverting the model to codellama" was.
|
|
2104
|
+
const chosen = modelFor(name);
|
|
2105
|
+
const newCfg = { ...cfg, provider: name, model: chosen.model, baseURL: endpoint.url };
|
|
2043
2106
|
await resetSession(newCfg);
|
|
2044
|
-
//
|
|
2045
|
-
|
|
2107
|
+
// The provider is persisted, or the switch is undone by the next start. The model is not:
|
|
2108
|
+
// it already lives against the provider it belongs to.
|
|
2109
|
+
const savedCfg = { ...(await loadKoneckConfig()), provider: name };
|
|
2046
2110
|
await saveKoneckConfig(savedCfg);
|
|
2047
2111
|
setStored(savedCfg);
|
|
2048
|
-
addSystem(`Provider → ${name} model → ${
|
|
2112
|
+
addSystem(`Provider → ${name} model → ${chosen.model} (${chosen.source})\n` +
|
|
2049
2113
|
`Endpoint ${endpoint.url} (${endpoint.source})\n` +
|
|
2050
|
-
`Session reset. /endpoint <url> changes the address.`);
|
|
2114
|
+
`Session reset. /endpoint <url> changes the address, /model <name> the model.`);
|
|
2051
2115
|
return;
|
|
2052
2116
|
}
|
|
2053
2117
|
case '/redraw': {
|
|
@@ -3550,7 +3614,8 @@ function App({ config: initialConfig, clearFrame }) {
|
|
|
3550
3614
|
// An endpoint always has a source worth naming, so it never reads as unset or as
|
|
3551
3615
|
// merely inherited: the question it answers is which file won.
|
|
3552
3616
|
const isEndpoint = f.key === 'baseURL';
|
|
3553
|
-
const
|
|
3617
|
+
const isModel = f.key === 'model';
|
|
3618
|
+
const here = !isEndpoint && !isModel && stored[f.key] !== undefined;
|
|
3554
3619
|
const value = here ? stored[f.key] : liveSetting(String(f.key));
|
|
3555
3620
|
const raw = f.key === 'maxTurns' && (value === 0 || value === undefined)
|
|
3556
3621
|
? 'no limit'
|
|
@@ -3558,7 +3623,8 @@ function App({ config: initialConfig, clearFrame }) {
|
|
|
3558
3623
|
const shown = beingTyped
|
|
3559
3624
|
? `${editing.value}_`
|
|
3560
3625
|
: isEndpoint ? `${raw} (${shortSource(endpoint.source)})`
|
|
3561
|
-
:
|
|
3626
|
+
: isModel ? `${raw} (${shortSource(modelFor(cfg.provider).source)})`
|
|
3627
|
+
: raw + (here || value === undefined ? '' : ' (inherited)');
|
|
3562
3628
|
// The marker is what makes the selection legible without colour, which a panel line
|
|
3563
3629
|
// drawn as one string cannot carry per-segment.
|
|
3564
3630
|
const mark = selected ? '>' : ' ';
|
|
@@ -3643,7 +3709,8 @@ function App({ config: initialConfig, clearFrame }) {
|
|
|
3643
3709
|
const st = statuses[`${cfg.provider}:${cfg.model}`];
|
|
3644
3710
|
lines.push(`**In use** ${cfg.model}`);
|
|
3645
3711
|
if (current) {
|
|
3646
|
-
|
|
3712
|
+
const hosted = needsNoCredential(resolveProvider(cfg.provider, endpointFor(cfg.provider).url));
|
|
3713
|
+
lines.push(` ${hosted ? 'local' : current.free ? 'free' : 'paid'}` +
|
|
3647
3714
|
(current.contextLength ? ` · ${humanTokens(current.contextLength)} context` : '') +
|
|
3648
3715
|
(current.maxOutput ? ` · ${humanTokens(current.maxOutput)} max out` : '') +
|
|
3649
3716
|
(current.toolCalling === false ? ' · no tool calling' : ''));
|