Skip to content

Commit ffe0df1

Browse files
committed
fix(providers): complete live Zen chat and tool round trips
Share the persisted provider credential namespace across setup and daemon requests. Accept absent nullable Chat delta fields, negotiate usage consistently, and retain bounded assistant reasoning through local transcript recovery. Add sanitized live Zen canary fixtures and regression coverage. Verified 716 tests, strict Clippy, formatting, standard repository checks, optimized packaging, and live chat, tool-result replay, and model refresh.
1 parent 4fd9654 commit ffe0df1

24 files changed

Lines changed: 497 additions & 137 deletions

File tree

crates/app/peritus-daemon/src/component/connection.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ pub async fn test_provider_connection(
1717
) -> Result<ConnectionReport, DaemonError> {
1818
let declaration = route.declaration()?;
1919
let id = declaration.profile().profile_id();
20-
let broker = PlatformCredentialSource::new("org.corvidae-coding.peritus.providers")
21-
.map_err(connection_error)?;
20+
let broker = PlatformCredentialSource::providers();
2221
let registry = ProviderRegistry::build(
2322
vec![declaration],
2423
ProviderRegistryLimits::PRODUCTION,

crates/app/peritus-daemon/src/component/credentials.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,10 @@ pub struct PlatformCredentialSource {
1010
}
1111

1212
impl PlatformCredentialSource {
13-
/// Opens the platform credential namespace without reading any secret material.
14-
///
15-
/// # Errors
16-
///
17-
/// Rejects an invalid service namespace.
18-
pub fn new(service: &str) -> Result<Self, ProviderCoreError> {
19-
let store = PlatformCredentialStore::new(service.to_owned())
20-
.map_err(|_| credential_error("platform credential namespace is invalid"))?;
21-
Ok(Self { store })
13+
/// Opens the same provider namespace used by setup without reading any secret material.
14+
#[must_use]
15+
pub fn providers() -> Self {
16+
Self { store: PlatformCredentialStore::providers() }
2217
}
2318

2419
/// Reports whether the current platform credential adapter is available.

crates/app/peritus-daemon/src/component/inventory.rs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ impl DaemonComponents {
2727
pub fn build(config: &DaemonConfig) -> Result<Self, DaemonError> {
2828
let direct =
2929
config.providers().iter().any(crate::ProviderRoute::requires_credential_broker);
30-
let credential_source = PlatformCredentialSource::new("peritus").map_err(provider_error)?;
30+
let credential_source = PlatformCredentialSource::providers();
3131
if direct && !credential_source.available() {
3232
return Err(DaemonError::new(
3333
DaemonErrorCode::RecoveryRequired,
@@ -111,13 +111,3 @@ fn tool_error(error: ToolComponentError) -> DaemonError {
111111
error,
112112
)
113113
}
114-
115-
fn provider_error(error: peritus_provider_core::ProviderCoreError) -> DaemonError {
116-
DaemonError::with_source(
117-
DaemonErrorCode::InvalidInput,
118-
DaemonRecovery::CorrectRequest,
119-
"construct credential broker",
120-
error.to_string(),
121-
error,
122-
)
123-
}

crates/app/peritus-product-runner/src/local_context/memory/ingestion.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,12 @@ impl LocalMemory {
7979
&mut self,
8080
message: &Message,
8181
) -> Result<u64, DeveloperLoopError> {
82+
// Assistant reasoning is bounded protocol replay, retained exactly with its exchange.
83+
// It is never projected into authoritative instructions or derived working entries.
8284
if message.content().iter().any(|block| {
83-
matches!(block, ContentBlock::Reasoning(_) | ContentBlock::ProviderExtension(_))
85+
matches!(block, ContentBlock::ProviderExtension(_))
86+
|| (matches!(block, ContentBlock::Reasoning(_))
87+
&& message.role() != Role::Assistant)
8488
}) {
8589
return Err(error("opaque provider content is not local working memory"));
8690
}

crates/app/peritus-product-runner/src/local_context/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod capacity;
55
mod folder;
66
mod inspection;
77
mod invocation;
8+
mod reasoning;
89
mod recovery;
910
mod retrieval;
1011
mod reviewer;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//! Exact assistant reasoning replay stays separate from authoritative working entries.
2+
3+
use super::support::*;
4+
use peritus_agent::DeveloperToolObservation;
5+
use peritus_model_protocol::{
6+
ContentBlock, Message, ProtocolLimits, ReasoningReplay, Role, ToolResult,
7+
};
8+
9+
#[test]
10+
fn assistant_reasoning_survives_local_archive_and_recovery_without_becoming_instructions() {
11+
let fixture = Fixture::new();
12+
let mut memory = fixture.open();
13+
begin(&mut memory, "reasoning");
14+
let limits = ProtocolLimits::PRODUCTION;
15+
let replay = ContentBlock::Reasoning(
16+
ReasoningReplay::new(
17+
None,
18+
br#"{"service":"opencode-zen","fields":{"reasoning_content":"untrusted opaque note"}}"#
19+
.to_vec(),
20+
limits,
21+
)
22+
.unwrap(),
23+
);
24+
let mut blocks = message(Role::Assistant, "visible answer").content().to_vec();
25+
blocks.insert(0, replay.clone());
26+
let assistant = Message::new(Role::Assistant, blocks, limits).unwrap();
27+
memory.observe_message(&assistant).expect("assistant replay can be archived");
28+
let tool = call("reasoning-call");
29+
let proposal = Message::new(
30+
Role::Assistant,
31+
vec![replay.clone(), ContentBlock::ToolCall(tool.clone())],
32+
limits,
33+
)
34+
.unwrap();
35+
memory.observe_message(&proposal).unwrap();
36+
let output = canonical(&serde_json::json!({"content":"observed"}));
37+
memory
38+
.observe_tool(&tool, &DeveloperToolObservation { output: output.clone(), is_error: false })
39+
.unwrap();
40+
let result = Message::new(
41+
Role::Tool,
42+
vec![ContentBlock::ToolResult(ToolResult::new(tool.id().clone(), output, false))],
43+
limits,
44+
)
45+
.unwrap();
46+
memory.observe_message(&result).unwrap();
47+
let view = memory.prepare_view(&profile(32_768), &[]).unwrap();
48+
assert!(view.contains(&assistant));
49+
let position = view.iter().position(|message| message == &proposal).unwrap();
50+
assert_eq!(view.get(position + 1), Some(&result));
51+
assert!(!render(&view).contains("untrusted opaque note"));
52+
assert!(memory.state.entries(memory.state.binding()).unwrap().is_empty());
53+
memory.publish(&view).unwrap();
54+
drop(memory);
55+
56+
let mut memory = fixture.open();
57+
let recovered = memory.prepare_view(&profile(32_768), &[]).unwrap();
58+
assert!(recovered.contains(&assistant));
59+
let position = recovered.iter().position(|message| message == &proposal).unwrap();
60+
assert_eq!(recovered.get(position + 1), Some(&result));
61+
for role in [Role::System, Role::Developer, Role::User, Role::Tool] {
62+
assert!(
63+
Message::new(role, vec![replay.clone()], limits).is_err(),
64+
"opaque content from {role:?}"
65+
);
66+
}
67+
begin(&mut memory, "next-invocation");
68+
let fresh = memory.prepare_view(&profile(32_768), &[]).unwrap();
69+
assert!(
70+
!fresh
71+
.iter()
72+
.flat_map(Message::content)
73+
.any(|block| matches!(block, ContentBlock::Reasoning(_)))
74+
);
75+
}

crates/app/peritus-provider-onboarding/src/direct.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ use peritus_types::ResourceId;
1111

1212
use crate::OnboardingError;
1313

14-
const SERVICE: &str = "org.corvidae-coding.peritus.providers";
15-
1614
/// Sensitive provider material that zeroizes its allocation on drop.
1715
pub struct DirectCredential(SecretMaterial);
1816

@@ -96,7 +94,7 @@ impl DirectProviderDraft {
9694
credential: &DirectCredential,
9795
) -> Result<DirectProviderProfile, OnboardingError> {
9896
let resource_id = random_resource_id()?;
99-
let store = PlatformCredentialStore::new(SERVICE.to_owned())?;
97+
let store = PlatformCredentialStore::providers();
10098
let reference = store.store(resource_id, &credential.0)?;
10199
let profile = DirectProviderProfile::new(
102100
self.kind,
@@ -123,7 +121,7 @@ impl DirectProviderDraft {
123121
/// Returns a malformed-reference or credential-store removal failure.
124122
pub fn remove_direct_credential(profile: &DirectProviderProfile) -> Result<(), OnboardingError> {
125123
let reference = parse_credential_reference(profile.credential_reference())?;
126-
PlatformCredentialStore::new(SERVICE.to_owned())?.remove(reference.resource_id())?;
124+
PlatformCredentialStore::providers().remove(reference.resource_id())?;
127125
Ok(())
128126
}
129127

crates/model/peritus-provider-compatible/fixtures/v1/MANIFEST

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@ responses-unknown-ancillary.sse
2323
responses-unknown-critical.sse
2424
SHA256SUMS
2525
transient-error.json
26+
zen-deepseek-live-1.sse
27+
zen-deepseek-live-2.sse
28+
zen-deepseek-live-3.sse

crates/model/peritus-provider-compatible/fixtures/v1/SHA256SUMS

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
553fe57de7657b0aa2e4203a95824bb27c34f2d0021c51c8f95c7f240b9f92ef MANIFEST
12
5fee750e7d57cc3f3d75b662a094b6f5b915411ad83b4024d0ba0b227f48cfab auth-error.json
23
7a561dfb21a3d8a193a5ad4fda3a76c791f67498ba99f35dba670aea00d110f5 chat-corrupt.sse
34
0b5cfa9147ebbb11f6d5ebfc8feb42d36663b6d7bdf804bf07b3c092e769a070 chat-error-after-http-success.sse
@@ -7,9 +8,8 @@ f80c44a65eedaf084447e562188874e0a202cb64fab8e71a3a6a42aa252b33a2 chat-refusal.s
78
a4d884cd2c75ab8fa44bb9090d20447fd4bf3e007f6beb6edc5d865df314821d chat-tool.sse
89
8e25be29faa22810440a78ec2fef2ebf6e445f2cc00ab18bee394239cd4270db chat-unknown-ancillary.sse
910
fc21fcead6ee0cb09352bec149f7e270f9f8e72fc6779d1658ec1455af983f61 chat-unknown-critical.sse
10-
022e388dc79e961ae8fcad33bbc707bad7d4bfab8bd01b7b3397c46f0b2063a9 golden-chat-request.json
11+
fb6f24452b7eeb2a7cca8cf04da811648da988fb9e5c71b0092ced52046f7e88 golden-chat-request.json
1112
6a271bb917f1360c3b8f611f9706c8ae07d8749c19f9cdc530f5c161735e2068 golden-responses-request.json
12-
d769cb9dc66a01cb4a011018996acc69f81c62cc6164ed57f6be612c5be79055 MANIFEST
1313
11148a3362043bdeedcf5f05c11276d54fd5bbb2b460d9874c36624ec20842b9 rate-error.json
1414
7222e84d43ffc1aa2b7d38ece9566b585dd220ca337b2f7bc28a9f603b293cda responses-corrupt.sse
1515
285096508f979169cdc7fa615ba9d560304aa02ef3ddb3e8df866aa4e1615bd4 responses-error-after-http-success.sse
@@ -21,3 +21,6 @@ d769cb9dc66a01cb4a011018996acc69f81c62cc6164ed57f6be612c5be79055 MANIFEST
2121
a7fcc2b50259ca602672970adda7fd235a828040e5574daa43fa887ad4062d23 responses-unknown-ancillary.sse
2222
379b59d8c02973bfa9b8c75ac056534d0fedbfdf32385607d3987d0f312bb8d8 responses-unknown-critical.sse
2323
557b4eb7efa5b6847c431483b307d878617617fff5be7a87801fbc7431ecae52 transient-error.json
24+
3bf111bb39490d83e9be1343e58b5602b81b6f2f40f9094a14d1c753c78eb256 zen-deepseek-live-1.sse
25+
a73b65f2cd38dd1b81607d2a96cf0852bd661b5aba24f455238a2264da8f45b7 zen-deepseek-live-2.sse
26+
90698be3fb77bed28cc75a37b9cd8bf66a98800d66c249a2ea4ee6fa98fb730a zen-deepseek-live-3.sse
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"max_completion_tokens":64,"messages":[{"content":[{"text":"hello","type":"text"}],"role":"user"}],"model":"model-test","parallel_tool_calls":false,"response_format":{"type":"text"},"stream":true,"stream_options":{"include_usage":true}}
1+
{"max_completion_tokens":64,"messages":[{"content":[{"text":"hello","type":"text"}],"role":"user"}],"model":"model-test","parallel_tool_calls":false,"response_format":{"type":"text"},"stream":true,"stream_options":{"include_usage":false}}

0 commit comments

Comments
 (0)