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
6 changes: 3 additions & 3 deletions .repository-projection.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"projection": "deixic-code",
"projectionSchemaVersion": 1,
"sourceRepository": "dx-corp/mono",
"sourceSha": "06b74765cc5e7353134d868de4140e69fa634f38",
"sourceSha": "5f0f02dc74958fce77b77d166a14dc09ec490804",
"destinationRepository": "dx-corp/code",
"priorProjectedBase": "85879e61077a7fd73f0dedcfd21e7c06e7f2f280",
"priorProjectedBase": "acfb663d7933c3028edc4e5315ce353159715c68",
"definitionDigest": "82936441c776e3e8edb5d215a75007ec9714a233f489d460075d79d5ef5ba32f",
"toolDigest": "45502ff0478e541d02f34cc39ac332935f8d3c0030fa221afe5bcc3b5e51f88e",
"contentDigest": "65e63c3da4396f8ae48cd156df59c171580f0159ba3d1d943b9fe667879f9856",
"contentDigest": "e71c46bc28e9552284f4b3d89e26852771d0d14cee7df7337f4876d4712d8f72",
"publicationEligible": true
}
50 changes: 47 additions & 3 deletions packages/local-host-rs/src/model_catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,18 @@ fn distinct_output_tokens(context: Option<u32>, output: Option<u32>) -> Option<u
}
}

fn omit_unverified_sonar_output(id: &str, output: Option<u32>) -> Option<u32> {
// Perplexity publishes a 128K context for Sonar Reasoning Pro but no
// 4,096-token output ceiling. The models.dev Perplexity row supplies 4,096
// for OpenRouter's fallback after its derived 90% value is rejected.
// Keep this exact disputed value unknown until the vendor
// publishes a cap: https://docs.perplexity.ai/docs/sonar/models/sonar-reasoning-pro
if id == "perplexity/sonar-reasoning-pro" && output == Some(4_096) {
return None;
}
output
}

fn parse_limit_tokens(model: &serde_json::Value, field: &str) -> Option<u32> {
model
.get("limit")
Expand Down Expand Up @@ -1058,9 +1070,12 @@ fn map_openrouter_model(
.and_then(serde_json::Value::as_u64)
.and_then(|output| u32::try_from(output).ok())
.filter(|output| *output > 0);
let output_tokens = limits.map_or_else(
|| distinct_output_tokens(Some(context_tokens), advertised_output),
|limits| limits.distinct_output(id, context_tokens, advertised_output),
let output_tokens = omit_unverified_sonar_output(
id,
limits.map_or_else(
|| distinct_output_tokens(Some(context_tokens), advertised_output),
|limits| limits.distinct_output(id, context_tokens, advertised_output),
),
);
let name = model
.get("name")
Expand Down Expand Up @@ -1652,6 +1667,35 @@ mod tests {
}
}

#[test]
fn unverified_sonar_output_limit_stays_unknown_after_refresh() {
assert_eq!(
catalog_model("perplexity/sonar-reasoning-pro")
.capabilities
.output_tokens,
None
);
let models_dev = serde_json::json!({
"perplexity": {"models": {
"sonar-reasoning-pro": {"limit": {"context": 128_000, "output": 4_096}}
}}
});
let openrouter = serde_json::json!({"data": [
{"id": "perplexity/sonar-reasoning-pro", "context_length": 128_000,
"top_provider": {"max_completion_tokens": 115_200}},
{"id": "perplexity/another-model", "context_length": 128_000,
"top_provider": {"max_completion_tokens": 4_096}}
]});
let limits = DevTokenLimits::from_models_dev(&models_dev);
let refreshed = map_openrouter_catalog_with_limits(&openrouter, Some(&limits)).unwrap();
assert_eq!(refreshed[0].capabilities.output_tokens, None);
assert_eq!(refreshed[1].capabilities.output_tokens, Some(4_096));
assert_eq!(
omit_unverified_sonar_output("perplexity/sonar-reasoning-pro", Some(8_192)),
Some(8_192)
);
}

#[test]
fn managed_model_routes_survive_cache_and_preserve_provider_and_limits() {
let mut models = Vec::new();
Expand Down
1 change: 0 additions & 1 deletion packages/local-host-rs/src/model_catalog_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -9189,7 +9189,6 @@
"reasoning": true,
"streaming": true,
"context_tokens": 128000,
"output_tokens": 4096,
"temperature": true
},
"cost": {
Expand Down
2 changes: 1 addition & 1 deletion packages/local-host-rs/src/model_facts_generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ pub static MODEL_FACTS: &[(&str, ModelFacts)] = &[
("perplexity/sonar-deep-research", ModelFacts { context_window: 128_000, max_output_tokens: Some(32_768), input_per_million: Some(2.0), output_per_million: Some(8.0), cache_read_per_million: None, cache_write_per_million: None }),
("perplexity/sonar-pro", ModelFacts { context_window: 200_000, max_output_tokens: Some(8_000), input_per_million: Some(3.0), output_per_million: Some(15.0), cache_read_per_million: None, cache_write_per_million: None }),
("perplexity/sonar-pro-search", ModelFacts { context_window: 200_000, max_output_tokens: Some(8_000), input_per_million: Some(3.0), output_per_million: Some(15.0), cache_read_per_million: None, cache_write_per_million: None }),
("perplexity/sonar-reasoning-pro", ModelFacts { context_window: 128_000, max_output_tokens: Some(4_096), input_per_million: Some(2.0), output_per_million: Some(8.0), cache_read_per_million: None, cache_write_per_million: None }),
("perplexity/sonar-reasoning-pro", ModelFacts { context_window: 128_000, max_output_tokens: None, input_per_million: Some(2.0), output_per_million: Some(8.0), cache_read_per_million: None, cache_write_per_million: None }),
("poolside/laguna-s-2.1", ModelFacts { context_window: 1_048_576, max_output_tokens: Some(131_072), input_per_million: Some(0.09), output_per_million: Some(0.18), cache_read_per_million: Some(0.009), cache_write_per_million: None }),
("poolside/laguna-s-2.1:free", ModelFacts { context_window: 262_144, max_output_tokens: Some(32_768), input_per_million: Some(0.0), output_per_million: Some(0.0), cache_read_per_million: None, cache_write_per_million: None }),
("poolside/laguna-xs-2.1", ModelFacts { context_window: 262_144, max_output_tokens: Some(32_768), input_per_million: Some(0.06), output_per_million: Some(0.12), cache_read_per_million: Some(0.03), cache_write_per_million: None }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,11 @@ fn read_http_request(stream: &mut TcpStream) -> String {
let mut request = Vec::new();
loop {
let mut chunk = [0_u8; 4096];
let bytes = stream.read(&mut chunk).expect("read test request");
let bytes = match stream.read(&mut chunk) {
Ok(bytes) => bytes,
Err(error) if error.kind() == std::io::ErrorKind::Interrupted => continue,
Err(error) => panic!("read test request: {error}"),
};
if bytes == 0 {
break;
}
Expand Down
11 changes: 8 additions & 3 deletions packages/local-host-rs/src/tools/gh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,11 @@ pub(crate) async fn gh_repo(
mod tests {
use super::*;

// Process scheduling on a loaded CI runner can outlast the command's own
// shutdown bound. Wait for the fake command to start before testing it.
#[cfg(unix)]
const GH_TEST_PROCESS_START_TIMEOUT: Duration = Duration::from_secs(10);

#[cfg(unix)]
struct TestGhOverride;

Expand Down Expand Up @@ -1260,7 +1265,7 @@ mod tests {
let execution =
tokio::spawn(async move { run_command_output(command, Some(&cancel_for_task)).await });

tokio::time::timeout(std::time::Duration::from_secs(2), async {
tokio::time::timeout(GH_TEST_PROCESS_START_TIMEOUT, async {
while !pid_path.exists() {
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
Expand Down Expand Up @@ -1325,7 +1330,7 @@ mod tests {
)
.await
});
tokio::time::timeout(Duration::from_secs(2), async {
tokio::time::timeout(GH_TEST_PROCESS_START_TIMEOUT, async {
while !pid_path.exists() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
Expand Down Expand Up @@ -1388,7 +1393,7 @@ mod tests {
)
.await
});
tokio::time::timeout(Duration::from_secs(2), async {
tokio::time::timeout(GH_TEST_PROCESS_START_TIMEOUT, async {
while !pid_path.exists() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
Expand Down
14 changes: 12 additions & 2 deletions packages/tui-rs/tests/entrypoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,21 @@ fn argv(words: &[&str]) -> Vec<OsString> {
fn every_native_utility_command_routes_to_the_utility_handler() {
assert_eq!(
NATIVE_UTILITY_COMMANDS.len(),
45,
"this test's evidence baseline is 45 utility commands; update it deliberately \
46,
"this test's evidence baseline is 46 utility commands; update it deliberately \
if the canonical table changes size"
);

let thread_args = argv(&["thread", "attach", "thread-id"]);
assert_eq!(
native_utility_tokens(&thread_args[1..]),
Some(vec![
"thread".to_owned(),
"attach".to_owned(),
"thread-id".to_owned()
])
);

assert!(NATIVE_UTILITY_COMMANDS.contains(&"experiments"));
let experiment_args = argv(&["experiments", "off"]);
assert_eq!(
Expand Down
66 changes: 66 additions & 0 deletions packages/ui-rs/src/translations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6666,6 +6666,17 @@ pub(crate) const MESSAGES: &[(&str, [&str; 6])] = &[
"完成",
],
),
(
"Complete Deixic sign-in in your browser.",
[
"Completa el inicio de sesión de Deixic en el navegador.",
"Terminez la connexion à Deixic dans votre navigateur.",
"Schließen Sie die Deixic-Anmeldung im Browser ab.",
"ブラウザーで Deixic へのサインインを完了してください。",
"브라우저에서 Deixic 로그인을 완료하세요.",
"请在浏览器中完成 Deixic 登录。",
],
),
(
"Complete setup to start.",
[
Expand Down Expand Up @@ -15692,6 +15703,17 @@ pub(crate) const MESSAGES: &[(&str, [&str; 6])] = &[
"偏好设置",
],
),
(
"Preparing browser sign-in…",
[
"Preparando el inicio de sesión en el navegador…",
"Préparation de la connexion dans le navigateur…",
"Browser-Anmeldung wird vorbereitet…",
"ブラウザーでのサインインを準備しています…",
"브라우저 로그인을 준비하는 중…",
"正在准备浏览器登录…",
],
),
(
"Preparing workspace search",
[
Expand Down Expand Up @@ -15769,6 +15791,17 @@ pub(crate) const MESSAGES: &[(&str, [&str; 6])] = &[
"按 a 以批准控股。",
],
),
(
"Press c to copy the link or o to open the browser again.",
[
"Pulsa c para copiar el enlace u o para abrir el navegador de nuevo.",
"Appuyez sur c pour copier le lien ou sur o pour rouvrir le navigateur.",
"Drücken Sie c, um den Link zu kopieren, oder o, um den Browser erneut zu öffnen.",
"c でリンクをコピーするか、o でブラウザーを再度開いてください。",
"c를 눌러 링크를 복사하거나 o를 눌러 브라우저를 다시 여세요.",
"按 c 复制链接,或按 o 再次打开浏览器。",
],
),
(
"Preview and select a color theme",
[
Expand Down Expand Up @@ -20738,6 +20771,17 @@ pub(crate) const MESSAGES: &[(&str, [&str; 6])] = &[
"选中的账户已更改 。 完成前再检查一遍",
],
),
(
"The session will continue when sign-in finishes.",
[
"La sesión continuará cuando finalice el inicio de sesión.",
"La session reprendra une fois la connexion terminée.",
"Die Sitzung wird fortgesetzt, sobald die Anmeldung abgeschlossen ist.",
"サインインが完了するとセッションが続行されます。",
"로그인이 완료되면 세션이 계속됩니다.",
"登录完成后,会话将继续。",
],
),
(
"The test sends a small fixed request to your selected model and may incur usage charges. It does not send repository contents.",
[
Expand Down Expand Up @@ -23842,6 +23886,17 @@ pub(crate) const MESSAGES: &[(&str, [&str; 6])] = &[
"等待ChatGPT登录完成...",
],
),
(
"Waiting for browser sign-in…",
[
"Esperando el inicio de sesión en el navegador…",
"En attente de la connexion dans le navigateur…",
"Warten auf die Browser-Anmeldung…",
"ブラウザーでのサインインを待っています…",
"브라우저 로그인을 기다리는 중…",
"正在等待浏览器登录…",
],
),
(
"Waiting for the browser callback…",
[
Expand Down Expand Up @@ -24744,6 +24799,17 @@ pub(crate) const MESSAGES: &[(&str, [&str; 6])] = &[
"内置专家简介必须来自汇编的指令",
],
),
(
"c copy o open u show link enter retry esc close",
[
"c copiar o abrir u mostrar enlace enter reintentar esc cerrar",
"c copier o ouvrir u afficher le lien enter réessayer esc fermer",
"c kopieren o öffnen u Link anzeigen enter erneut versuchen esc schließen",
"c コピー o 開く u リンク表示 enter 再試行 esc 閉じる",
"c 복사 o 열기 u 링크 표시 enter 재시도 esc 닫기",
"c 复制 o 打开 u 显示链接 enter 重试 esc 关闭",
],
),
(
"cancel",
[
Expand Down
12 changes: 12 additions & 0 deletions scripts/catalog-output-limits.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** Keep disputed aggregator output caps unknown until a vendor publishes them. */
export function omitUnverifiedSonarOutput(id, output) {
// Perplexity documents a 128K context for Sonar Reasoning Pro, but no
// 4,096-token output ceiling. OpenRouter's derived 90%-of-context value
// falls back to the models.dev Perplexity row, which supplies 4,096.
// Scope the omission to this exact model and disputed value.
// https://docs.perplexity.ai/docs/sonar/models/sonar-reasoning-pro
if (id === "perplexity/sonar-reasoning-pro" && output === 4096) {
return undefined;
}
return output;
}
10 changes: 10 additions & 0 deletions scripts/catalog-output-limits.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import assert from "node:assert/strict";
import test from "node:test";

import { omitUnverifiedSonarOutput } from "./catalog-output-limits.mjs";

test("only the unverified Sonar Reasoning Pro output cap is omitted", () => {
assert.equal(omitUnverifiedSonarOutput("perplexity/sonar-reasoning-pro", 4096), undefined);
assert.equal(omitUnverifiedSonarOutput("perplexity/sonar-reasoning-pro", 8192), 8192);
assert.equal(omitUnverifiedSonarOutput("perplexity/another-model", 4096), 4096);
});
4 changes: 3 additions & 1 deletion scripts/fetch-model-catalog.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env node

import { omitUnverifiedSonarOutput } from "./catalog-output-limits.mjs";

/**
* Regenerate the bundled model catalog snapshot consumed by
* `packages/local-host-rs/src/model_catalog.rs` via `include_str!`.
Expand Down Expand Up @@ -244,7 +246,7 @@ function mapOpenRouterModel(model, tokenLimits, vendorFacts) {
(model.reasoning != null && typeof model.reasoning === "object"),
streaming: true,
context_tokens: context,
output_tokens: resolveOpenRouterOutput(tokenLimits, id, context, advertised),
output_tokens: omitUnverifiedSonarOutput(id, resolveOpenRouterOutput(tokenLimits, id, context, advertised)),
// OpenRouter lists `temperature` in `supported_parameters` for
// routes that accept it. Omitted when the route lists no
// parameters at all.
Expand Down
Loading