Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/user/tui-and-sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ The TUI handles `/new`, `/resume`, `/sessions`, `/close`, `/model`, `/effort`, `

The other commands listed above are available only while the session is idle. `/agents` toggles the agent roster without starting a model turn. `/new` closes the current session and starts a fresh persisted session. It clears the visible transcript but does not delete or alter the previous session, which remains resumable by its ID. Text following `/new` becomes the new session's first prompt. `/resume <session-id>` closes the current session, resumes the requested durable session, and replays its transcript; selecting the already-active ID is a no-op. `/sessions` opens a visible newest-first selector for the same workspace. Up and Down move, Enter uses the existing resume flow, `R` opens an inline rename field, and Esc cancels renaming or closes the dialog. Submit an empty rename and confirm to clear the custom name. After a save, the picker remains open on the selected session and refreshes its displayed name. `/close` closes the current session and exits the TUI.

`/model` opens the model selector on a provider list; Enter on a provider shows that provider's models. Typing searches only the current page, and the search clears when you move between pages. Esc returns from the model list to the provider list, and closes the selector from the provider list. `/effort` opens the advertised ACP reasoning-effort selector; `/effort default|low|medium|high` selects directly. In either dialog, Tab toggles saving the selection to `~/.kit/config.toml`, Enter selects, and Esc closes. Saving `default` removes top-level `reasoning_effort`; other values update it without replacing unrelated TOML. New sessions start from the resolved CLI/TOML default. Durable sessions remember their reasoning-effort selection, including an explicit provider `default`, and restore it on load or resume even if the process default has changed. Older transcripts without a recorded effort continue to use the process default. Forks inherit the source session鈥檚 current effort. Saving to TOML changes the default for future sessions; it is not required to retain the effort of the current session. Model selection still starts from the resolved CLI/TOML default on reload. Reasoning-effort records require a Kit version that understands transcript schema 6; older versions reject these records rather than silently discard the saved selection.
`/model` opens the model selector on a provider list; Enter on a provider shows that provider's models. Once the session is open, providers that Kit can sign in to but that have no credentials yet are listed greyed out below the signed-in ones. Enter on one starts its terminal sign-in, the same flow as `/login <method>`, and is available only while the session is idle; after a successful sign-in, the selector reopens on that provider's models with the same Tab setting. Typing searches only the current page, and the search clears when you move between pages. Esc returns from the model list to the provider list, and closes the selector from the provider list. `/effort` opens the advertised ACP reasoning-effort selector; `/effort default|low|medium|high` selects directly. In either dialog, Tab toggles saving the selection to `~/.kit/config.toml`, Enter selects, and Esc closes. Saving `default` removes top-level `reasoning_effort`; other values update it without replacing unrelated TOML. New sessions start from the resolved CLI/TOML default. Durable sessions remember their reasoning-effort selection, including an explicit provider `default`, and restore it on load or resume even if the process default has changed. Older transcripts without a recorded effort continue to use the process default. Forks inherit the source session鈥檚 current effort. Saving to TOML changes the default for future sessions; it is not required to retain the effort of the current session. Model selection still starts from the resolved CLI/TOML default on reload. Reasoning-effort records require a Kit version that understands transcript schema 6; older versions reject these records rather than silently discard the saved selection.

Before changing models, Kit compares the latest provider-reported transcript occupancy with the target model's advertised context window. It adds a 20% tokenizer margin (`ceil(tokens 脳 1.20)`) and warns when that estimate is at least 80% of the target window. This is the latest request's occupancy, not accumulated session usage; cached input tokens are not counted twice.

Expand Down
3 changes: 3 additions & 0 deletions src/protocols/acp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ impl<'a> AuthenticationRequiredData<'a> {
}
}

/// Terminal auth method metadata naming the provider the method signs in to.
pub(crate) const AUTH_PROVIDER_META: &str = "kit/provider";

pub(crate) struct TerminalAuthMethodSpec {
pub(crate) method_id: &'static str,
pub(crate) name: &'static str,
Expand Down
63 changes: 61 additions & 2 deletions src/protocols/acp/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,11 @@ fn terminal_auth_methods(
.args(vec![
"--terminal-auth-login".into(),
method.method_id.into(),
]),
])
.meta(Map::from_iter([(
super::AUTH_PROVIDER_META.into(),
Value::from(method.provider.as_str()),
)])),
)
})
.collect()
Expand Down Expand Up @@ -1592,7 +1596,7 @@ async fn session_actor<S: ModelSession + Send + 'static>(actor: SessionActor<S>)
structured_completion,
mut skill_catalog,
adapter,
catalog,
mut catalog,
mut commands,
mut mcp_events,
} = actor;
Expand Down Expand Up @@ -1660,6 +1664,15 @@ async fn session_actor<S: ModelSession + Send + 'static>(actor: SessionActor<S>)
{
return Err(model_switch::error("model change cancelled"));
}
// A terminal login since the session opened changes which
// providers resolve credentials; rebuild the catalog so the
// client's post-login refresh advertises their models.
if !catalog_matches_providers(&catalog, &adapter.catalog_providers()) {
let current = adapter
.selection()
.map_err(|error| model_switch::error(&error))?;
catalog = adapter.model_catalog(&current).await;
}
if request.config_id.to_string() == super::MODEL_CONFIG_ID {
let target = request.value.as_id().ok_or_else(|| {
model_switch::error("selection requires an id value")
Expand Down Expand Up @@ -2366,6 +2379,16 @@ fn v2_config_options(
]
}

fn catalog_matches_providers(
catalog: &[crate::provider::ModelGroup],
providers: &[crate::ProviderKind],
) -> bool {
catalog
.iter()
.map(|group| group.provider)
.eq(providers.iter().copied())
}

fn set_v2_config(
adapter: &SelectableAdapter,
catalog: &[crate::provider::ModelGroup],
Expand Down Expand Up @@ -3022,6 +3045,25 @@ pub(crate) fn component(
)]
mod tests {
include!("v2/identity_tests.rs");
#[test]
fn catalog_matches_providers_detects_logins_and_logouts() {
let group = |provider| crate::provider::ModelGroup {
provider,
models: Vec::new(),
context_windows: Default::default(),
};
let catalog = [group(ProviderKind::OpenAiSubscription)];
assert!(catalog_matches_providers(
&catalog,
&[ProviderKind::OpenAiSubscription]
));
assert!(!catalog_matches_providers(
&catalog,
&[ProviderKind::OpenAiSubscription, ProviderKind::Anthropic]
));
assert!(!catalog_matches_providers(&catalog, &[]));
}

#[test]
fn compose_title_replay_preserves_identity_and_trims_intent() {
let call = agentkit_core::ToolCallPart::new(
Expand Down Expand Up @@ -6789,6 +6831,23 @@ mod tests {
});
let method_ids = terminal_auth_method_ids(&methods);
assert_eq!(method_ids, ["openai", "speakeasy", "anthropic"]);
let providers = methods
.iter()
.map(|method| match method {
wire::AuthMethod::Terminal(method) => {
method.meta.as_ref().unwrap()[crate::protocols::acp::AUTH_PROVIDER_META].clone()
}
_ => unreachable!("only terminal methods are advertised"),
})
.collect::<Vec<_>>();
assert_eq!(
providers,
[
json!("openai-subscription"),
json!("speakeasy"),
json!("anthropic")
]
);
assert!(
terminal_auth_method_specs()
.iter()
Expand Down
9 changes: 9 additions & 0 deletions src/provider/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,15 @@ impl SelectableAdapter {
.await
}

/// Providers whose credentials resolve now, in catalog order.
pub fn catalog_providers(&self) -> Vec<ProviderKind> {
available_catalog_providers(
&self.credential_storage,
self.openrouter_api_key.as_ref(),
|name| std::env::var(name),
)
}

pub fn select(&self, selection: ModelSelection) -> Result<(), String> {
if !valid_model_id(&selection.model) {
return Err("model name is outside canonical bounds".into());
Expand Down
Loading
Loading