Refactor application and runtime boundaries #61
@@ -1173,7 +1173,7 @@ pub(crate) fn parse_tool_calls(
|
|||||||
let (content, calls) = if model == ModelChoice::Glm52 {
|
let (content, calls) = if model == ModelChoice::Glm52 {
|
||||||
parse_glm_calls(text)?
|
parse_glm_calls(text)?
|
||||||
} else {
|
} else {
|
||||||
crate::server::parse_dsml_tool_calls(text)?
|
crate::dsml::parse_tool_calls(text)?
|
||||||
};
|
};
|
||||||
calls
|
calls
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
19
src/app.rs
19
src/app.rs
@@ -19,7 +19,7 @@ use crate::config::{
|
|||||||
GitDiffWhitespace,
|
GitDiffWhitespace,
|
||||||
};
|
};
|
||||||
use crate::database::{Database, ProjectWithSessions, SessionState, StoredMessage};
|
use crate::database::{Database, ProjectWithSessions, SessionState, StoredMessage};
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", test))]
|
||||||
use crate::engine::ChatTurn;
|
use crate::engine::ChatTurn;
|
||||||
use crate::metrics::{KvCacheReport, Metrics, MetricsSnapshot};
|
use crate::metrics::{KvCacheReport, Metrics, MetricsSnapshot};
|
||||||
use crate::model::{self, DownloadOutcome, DownloadProgress, ManagedArtifactId, ModelChoice};
|
use crate::model::{self, DownloadOutcome, DownloadProgress, ManagedArtifactId, ModelChoice};
|
||||||
@@ -36,9 +36,11 @@ use rfd::AsyncFileDialog;
|
|||||||
use std::collections::{HashMap, HashSet, VecDeque};
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||||
use std::sync::mpsc::{self, TryRecvError};
|
use std::sync::mpsc::{self, TryRecvError};
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
#[cfg(target_os = "macos")]
|
||||||
|
use std::sync::{Mutex, RwLock};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
@@ -595,6 +597,7 @@ impl App {
|
|||||||
active_generation: None,
|
active_generation: None,
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
active_compaction: None,
|
active_compaction: None,
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
active_tool_check: None,
|
active_tool_check: None,
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
agent_tools: None,
|
agent_tools: None,
|
||||||
@@ -737,6 +740,7 @@ impl App {
|
|||||||
active_generation: None,
|
active_generation: None,
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
active_compaction: None,
|
active_compaction: None,
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
active_tool_check: None,
|
active_tool_check: None,
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
agent_tools: None,
|
agent_tools: None,
|
||||||
@@ -1567,8 +1571,11 @@ impl App {
|
|||||||
self.error = Some(format!("Could not play media: {error}"));
|
self.error = Some(format!("Could not play media: {error}"));
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
if let Err(error) = std::process::Command::new("open").arg(url).spawn() {
|
{
|
||||||
self.error = Some(format!("Could not open media: {error}"));
|
let _ = (title, video);
|
||||||
|
if let Err(error) = std::process::Command::new("open").arg(url).spawn() {
|
||||||
|
self.error = Some(format!("Could not open media: {error}"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::RequestA2uiDismiss(surface_id) => {
|
Message::RequestA2uiDismiss(surface_id) => {
|
||||||
@@ -1788,8 +1795,10 @@ impl App {
|
|||||||
if let Some(database) = &mut self.database {
|
if let Some(database) = &mut self.database {
|
||||||
match database.delete_project(project_id) {
|
match database.delete_project(project_id) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
let _ = checkpoint_ids;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
for session_id in checkpoint_ids {
|
for session_id in checkpoint_ids {
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
self.background_chats.remove(&session_id);
|
self.background_chats.remove(&session_id);
|
||||||
}
|
}
|
||||||
self.drafts.remove(&project_id);
|
self.drafts.remove(&project_id);
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ fn sync_a2ui_message(
|
|||||||
renderable_surface_updated
|
renderable_surface_updated
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", test))]
|
||||||
fn chat_turn(message: &ChatMessage) -> ChatTurn {
|
fn chat_turn(message: &ChatMessage) -> ChatTurn {
|
||||||
ChatTurn {
|
ChatTurn {
|
||||||
user: message.user,
|
user: message.user,
|
||||||
@@ -368,7 +368,7 @@ fn project_agents(path: &Path) -> Result<Option<String>, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", test))]
|
||||||
fn title_context(messages: impl IntoIterator<Item = ChatTurn>) -> Vec<ChatTurn> {
|
fn title_context(messages: impl IntoIterator<Item = ChatTurn>) -> Vec<ChatTurn> {
|
||||||
let mut started = false;
|
let mut started = false;
|
||||||
messages
|
messages
|
||||||
@@ -497,6 +497,8 @@ impl App {
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let agents =
|
let agents =
|
||||||
agents_prompt_for_turn(opening_turn, opening_agents, self.session_agents_prompt());
|
agents_prompt_for_turn(opening_turn, opening_agents, self.session_agents_prompt());
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
let agents = None::<String>;
|
||||||
self.a2ui_auto_switch_pending = true;
|
self.a2ui_auto_switch_pending = true;
|
||||||
self.context_notice = None;
|
self.context_notice = None;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -535,6 +537,7 @@ impl App {
|
|||||||
&effective.turn.system_prompt,
|
&effective.turn.system_prompt,
|
||||||
self.compaction_summary(),
|
self.compaction_summary(),
|
||||||
);
|
);
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
let assistant_reasoning = effective.turn.reasoning_mode != ReasoningMode::Direct;
|
let assistant_reasoning = effective.turn.reasoning_mode != ReasoningMode::Direct;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let model_prompt = if self.config.a2ui_enabled {
|
let model_prompt = if self.config.a2ui_enabled {
|
||||||
@@ -700,7 +703,6 @@ impl App {
|
|||||||
{
|
{
|
||||||
let _ = effective;
|
let _ = effective;
|
||||||
self.error = Some("Local Metal generation requires macOS.".into());
|
self.error = Some("Local Metal generation requires macOS.".into());
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(super) struct PreferenceDraft {
|
pub(super) struct PreferenceDraft {
|
||||||
@@ -437,6 +438,7 @@ impl App {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
let dev_brain_changed = self.config.dev_brain != config.dev_brain;
|
let dev_brain_changed = self.config.dev_brain != config.dev_brain;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let endpoint_changed = self.config.endpoint != config.endpoint;
|
let endpoint_changed = self.config.endpoint != config.endpoint;
|
||||||
@@ -447,6 +449,7 @@ impl App {
|
|||||||
{
|
{
|
||||||
self._endpoint = None;
|
self._endpoint = None;
|
||||||
}
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
let pending_endpoint =
|
let pending_endpoint =
|
||||||
if config.endpoint.enabled && (endpoint_changed || self._endpoint.is_none()) {
|
if config.endpoint.enabled && (endpoint_changed || self._endpoint.is_none()) {
|
||||||
let Some(generation) = &self.generation_service else {
|
let Some(generation) = &self.generation_service else {
|
||||||
|
|||||||
210
src/dsml.rs
Normal file
210
src/dsml.rs
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub(crate) struct Syntax {
|
||||||
|
pub(crate) tool_start: &'static str,
|
||||||
|
pub(crate) tool_end: &'static str,
|
||||||
|
pub(crate) invoke_start: &'static str,
|
||||||
|
pub(crate) invoke_end: &'static str,
|
||||||
|
pub(crate) parameter_start: &'static str,
|
||||||
|
pub(crate) parameter_end: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) const SYNTAXES: [Syntax; 3] = [
|
||||||
|
Syntax {
|
||||||
|
tool_start: "<|DSML|tool_calls>",
|
||||||
|
tool_end: "</|DSML|tool_calls>",
|
||||||
|
invoke_start: "<|DSML|invoke",
|
||||||
|
invoke_end: "</|DSML|invoke>",
|
||||||
|
parameter_start: "<|DSML|parameter",
|
||||||
|
parameter_end: "</|DSML|parameter>",
|
||||||
|
},
|
||||||
|
Syntax {
|
||||||
|
tool_start: "<DSML|tool_calls>",
|
||||||
|
tool_end: "</DSML|tool_calls>",
|
||||||
|
invoke_start: "<DSML|invoke",
|
||||||
|
invoke_end: "</DSML|invoke>",
|
||||||
|
parameter_start: "<DSML|parameter",
|
||||||
|
parameter_end: "</DSML|parameter>",
|
||||||
|
},
|
||||||
|
Syntax {
|
||||||
|
tool_start: "<tool_calls>",
|
||||||
|
tool_end: "</tool_calls>",
|
||||||
|
invoke_start: "<invoke",
|
||||||
|
invoke_end: "</invoke>",
|
||||||
|
parameter_start: "<parameter",
|
||||||
|
parameter_end: "</parameter>",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(crate) fn parse_tool_calls(text: &str) -> Result<(String, Vec<(String, Value)>), String> {
|
||||||
|
let Some((start, syntax)) = SYNTAXES
|
||||||
|
.iter()
|
||||||
|
.filter_map(|syntax| text.find(syntax.tool_start).map(|start| (start, *syntax)))
|
||||||
|
.min_by_key(|(start, _)| *start)
|
||||||
|
else {
|
||||||
|
return Ok((text.to_owned(), Vec::new()));
|
||||||
|
};
|
||||||
|
let Some(relative_end) = text[start..].find(syntax.tool_end) else {
|
||||||
|
return Err("invalid or incomplete DSML tool call".into());
|
||||||
|
};
|
||||||
|
let end = start + relative_end + syntax.tool_end.len();
|
||||||
|
let content = text[..start].trim_end().to_owned();
|
||||||
|
let raw = &text[start..end];
|
||||||
|
let mut cursor = syntax.tool_start.len();
|
||||||
|
let mut calls = Vec::new();
|
||||||
|
loop {
|
||||||
|
skip_whitespace(raw, &mut cursor);
|
||||||
|
if raw[cursor..].starts_with(syntax.tool_end) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if !raw[cursor..].starts_with(syntax.invoke_start) {
|
||||||
|
return Err("invalid or incomplete DSML tool call".into());
|
||||||
|
}
|
||||||
|
let tag_end = raw[cursor..]
|
||||||
|
.find('>')
|
||||||
|
.map(|offset| cursor + offset + 1)
|
||||||
|
.ok_or_else(|| "invalid or incomplete DSML tool call".to_owned())?;
|
||||||
|
let name = attribute(&raw[cursor..tag_end], "name")
|
||||||
|
.ok_or_else(|| "invalid or incomplete DSML tool call".to_owned())?;
|
||||||
|
cursor = tag_end;
|
||||||
|
let mut arguments = Map::new();
|
||||||
|
loop {
|
||||||
|
skip_whitespace(raw, &mut cursor);
|
||||||
|
if raw[cursor..].starts_with(syntax.invoke_end) {
|
||||||
|
cursor += syntax.invoke_end.len();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let (name, value) = parse_parameter(raw, &mut cursor, syntax)?
|
||||||
|
.ok_or_else(|| "invalid or incomplete DSML tool call".to_owned())?;
|
||||||
|
arguments.insert(name, value);
|
||||||
|
}
|
||||||
|
calls.push((name, Value::Object(arguments)));
|
||||||
|
}
|
||||||
|
if calls.is_empty() {
|
||||||
|
return Err("invalid or incomplete DSML tool call".into());
|
||||||
|
}
|
||||||
|
Ok((content, calls))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_parameter(
|
||||||
|
text: &str,
|
||||||
|
cursor: &mut usize,
|
||||||
|
syntax: Syntax,
|
||||||
|
) -> Result<Option<(String, Value)>, String> {
|
||||||
|
if !text[*cursor..].starts_with(syntax.parameter_start) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let Some(tag_end) = text[*cursor..].find('>').map(|end| *cursor + end + 1) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let tag = &text[*cursor..tag_end];
|
||||||
|
let Some(name) = attribute(tag, "name") else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let is_string = attribute(tag, "string");
|
||||||
|
*cursor = tag_end;
|
||||||
|
let mut nested_start = *cursor;
|
||||||
|
skip_whitespace(text, &mut nested_start);
|
||||||
|
if is_string.is_none() && text[nested_start..].starts_with(syntax.parameter_start) {
|
||||||
|
*cursor = nested_start;
|
||||||
|
let mut nested = Map::new();
|
||||||
|
loop {
|
||||||
|
skip_whitespace(text, cursor);
|
||||||
|
if !text[*cursor..].starts_with(syntax.parameter_start) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let Some((name, value)) = parse_parameter(text, cursor, syntax)? else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
nested.insert(name, value);
|
||||||
|
}
|
||||||
|
skip_whitespace(text, cursor);
|
||||||
|
if !text[*cursor..].starts_with(syntax.parameter_end) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
*cursor += syntax.parameter_end.len();
|
||||||
|
return Ok(Some((name, Value::Object(nested))));
|
||||||
|
}
|
||||||
|
let Some(value_end) = text[*cursor..]
|
||||||
|
.find(syntax.parameter_end)
|
||||||
|
.map(|end| *cursor + end)
|
||||||
|
else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let raw = &text[*cursor..value_end];
|
||||||
|
*cursor = value_end + syntax.parameter_end.len();
|
||||||
|
let value = if is_string.as_deref().unwrap_or("true") == "true" {
|
||||||
|
Value::String(unescape(raw))
|
||||||
|
} else {
|
||||||
|
serde_json::from_str(raw)
|
||||||
|
.map_err(|error| format!("invalid DSML tool arguments: {error}"))?
|
||||||
|
};
|
||||||
|
Ok(Some((name, value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attribute(tag: &str, name: &str) -> Option<String> {
|
||||||
|
let start = tag.find(&format!("{name}=\""))? + name.len() + 2;
|
||||||
|
let end = start + tag[start..].find('"')?;
|
||||||
|
Some(unescape(&tag[start..end]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_whitespace(text: &str, cursor: &mut usize) {
|
||||||
|
while text[*cursor..]
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.is_some_and(char::is_whitespace)
|
||||||
|
{
|
||||||
|
*cursor += text[*cursor..].chars().next().unwrap().len_utf8();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unescape(text: &str) -> String {
|
||||||
|
text.replace(""", "\"")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace("&", "&")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_reference_and_legacy_dsml_syntaxes() {
|
||||||
|
for (start, end, invoke, invoke_end, parameter, parameter_end) in [
|
||||||
|
(
|
||||||
|
"<|DSML|tool_calls>",
|
||||||
|
"</|DSML|tool_calls>",
|
||||||
|
"<|DSML|invoke",
|
||||||
|
"</|DSML|invoke>",
|
||||||
|
"<|DSML|parameter",
|
||||||
|
"</|DSML|parameter>",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"<DSML|tool_calls>",
|
||||||
|
"</DSML|tool_calls>",
|
||||||
|
"<DSML|invoke",
|
||||||
|
"</DSML|invoke>",
|
||||||
|
"<DSML|parameter",
|
||||||
|
"</DSML|parameter>",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"<tool_calls>",
|
||||||
|
"</tool_calls>",
|
||||||
|
"<invoke",
|
||||||
|
"</invoke>",
|
||||||
|
"<parameter",
|
||||||
|
"</parameter>",
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let raw = format!(
|
||||||
|
"done{start}{invoke} name=\"read\">{parameter} name=\"path\" string=\"true\">src/main.rs{parameter_end}{invoke_end}{end}"
|
||||||
|
);
|
||||||
|
let (content, calls) = parse_tool_calls(&raw).unwrap();
|
||||||
|
assert_eq!(content, "done");
|
||||||
|
assert_eq!(calls[0].0, "read");
|
||||||
|
assert_eq!(calls[0].1["path"], "src/main.rs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
mod gguf;
|
mod gguf;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", test))]
|
||||||
mod kvstore;
|
mod kvstore;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
mod metal;
|
mod metal;
|
||||||
@@ -9,6 +9,7 @@ mod validation;
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use crate::metrics::{KvLookup, Metrics};
|
use crate::metrics::{KvLookup, Metrics};
|
||||||
use crate::model::ModelChoice;
|
use crate::model::ModelChoice;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
use crate::settings::TurnSettings;
|
use crate::settings::TurnSettings;
|
||||||
use crate::settings::{EngineSettings, ReasoningMode};
|
use crate::settings::{EngineSettings, ReasoningMode};
|
||||||
use gguf::{F16, F32, Gguf, I32, IQ2_XXS, Q2_K, Q4_0, Q4_K, Q5_K, Q6_K, Q8_0, Tensor, Value};
|
use gguf::{F16, F32, Gguf, I32, IQ2_XXS, Q2_K, Q4_0, Q4_K, Q5_K, Q6_K, Q8_0, Tensor, Value};
|
||||||
@@ -17,9 +18,12 @@ use kvstore::{KvStore, StoreReason};
|
|||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use std::path::PathBuf;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
@@ -1386,7 +1390,7 @@ impl Generator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", test))]
|
||||||
fn append_generated_bytes(
|
fn append_generated_bytes(
|
||||||
generated: &mut ChatTurn,
|
generated: &mut ChatTurn,
|
||||||
reasoning: bool,
|
reasoning: bool,
|
||||||
@@ -1449,7 +1453,7 @@ fn flush_generated(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", test))]
|
||||||
fn emit_safe_text(
|
fn emit_safe_text(
|
||||||
text: &mut String,
|
text: &mut String,
|
||||||
emitted: &mut usize,
|
emitted: &mut usize,
|
||||||
@@ -1494,7 +1498,7 @@ fn emit_safe_text(
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", test))]
|
||||||
fn conversation_key(system: &str, reasoning: ReasoningMode, messages: &[ChatTurn]) -> Vec<u8> {
|
fn conversation_key(system: &str, reasoning: ReasoningMode, messages: &[ChatTurn]) -> Vec<u8> {
|
||||||
fn text(output: &mut Vec<u8>, value: &str) {
|
fn text(output: &mut Vec<u8>, value: &str) {
|
||||||
output.extend_from_slice(&(value.len() as u64).to_le_bytes());
|
output.extend_from_slice(&(value.len() as u64).to_le_bytes());
|
||||||
@@ -1526,12 +1530,12 @@ fn conversation_key(system: &str, reasoning: ReasoningMode, messages: &[ChatTurn
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", test))]
|
||||||
fn conversation_tag(system: &str, reasoning: ReasoningMode, messages: &[ChatTurn]) -> [u8; 32] {
|
fn conversation_tag(system: &str, reasoning: ReasoningMode, messages: &[ChatTurn]) -> [u8; 32] {
|
||||||
Sha256::digest(conversation_key(system, reasoning, messages)).into()
|
Sha256::digest(conversation_key(system, reasoning, messages)).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", test))]
|
||||||
fn checkpoint_matches_prefix(
|
fn checkpoint_matches_prefix(
|
||||||
checkpoint: [u8; 32],
|
checkpoint: [u8; 32],
|
||||||
system: &str,
|
system: &str,
|
||||||
@@ -1555,7 +1559,7 @@ fn resident_key(directory: &Path, tag: [u8; 32]) -> PathBuf {
|
|||||||
directory.join("resident").join(name)
|
directory.join("resident").join(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", test))]
|
||||||
fn sample(
|
fn sample(
|
||||||
logits: &[f32],
|
logits: &[f32],
|
||||||
temperature: f32,
|
temperature: f32,
|
||||||
@@ -1631,10 +1635,10 @@ fn sample(
|
|||||||
probabilities.last().map_or(0, |(token, _)| *token as i32)
|
probabilities.last().map_or(0, |(token, _)| *token as i32)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", test))]
|
||||||
struct Rng(u64);
|
struct Rng(u64);
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", test))]
|
||||||
impl Rng {
|
impl Rng {
|
||||||
fn new(seed: u64) -> Self {
|
fn new(seed: u64) -> Self {
|
||||||
Self(seed.max(1))
|
Self(seed.max(1))
|
||||||
@@ -1654,7 +1658,7 @@ impl Rng {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(test, target_os = "macos"))]
|
#[cfg(test)]
|
||||||
mod sampling_tests {
|
mod sampling_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -1835,6 +1839,7 @@ mod sampling_tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
#[ignore = "requires the 80 GiB Flash checkpoint and Apple Metal"]
|
#[ignore = "requires the 80 GiB Flash checkpoint and Apple Metal"]
|
||||||
fn metal_executes_real_flash_token() {
|
fn metal_executes_real_flash_token() {
|
||||||
configure_metal_sources().unwrap();
|
configure_metal_sources().unwrap();
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![cfg_attr(not(target_os = "macos"), allow(dead_code))]
|
||||||
|
|
||||||
mod a2ui;
|
mod a2ui;
|
||||||
mod a2ui_validation;
|
mod a2ui_validation;
|
||||||
mod agent;
|
mod agent;
|
||||||
@@ -6,6 +8,7 @@ mod compaction;
|
|||||||
mod config;
|
mod config;
|
||||||
mod database;
|
mod database;
|
||||||
mod dev_brain;
|
mod dev_brain;
|
||||||
|
mod dsml;
|
||||||
mod engine;
|
mod engine;
|
||||||
mod metrics;
|
mod metrics;
|
||||||
mod model;
|
mod model;
|
||||||
|
|||||||
@@ -312,49 +312,6 @@ impl Drop for ConnectionSlot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_dsml_tool_calls(text: &str) -> Result<(String, Vec<(String, Value)>), String> {
|
|
||||||
let has_tool_marker = TOOL_SYNTAXES
|
|
||||||
.iter()
|
|
||||||
.any(|syntax| text.contains(syntax.tool_start));
|
|
||||||
let mut projector = ToolProjector::new();
|
|
||||||
let events = projector.push(text, true, "");
|
|
||||||
let mut content = String::new();
|
|
||||||
let mut calls = Vec::<(String, String, bool)>::new();
|
|
||||||
for event in events {
|
|
||||||
match event {
|
|
||||||
ToolProjectionEvent::Text(text) => content.push_str(&text),
|
|
||||||
ToolProjectionEvent::Start { index, name, .. } => {
|
|
||||||
if calls.len() == index {
|
|
||||||
calls.push((name, String::new(), false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToolProjectionEvent::Arguments { index, fragment } => {
|
|
||||||
if let Some((_, arguments, _)) = calls.get_mut(index) {
|
|
||||||
arguments.push_str(&fragment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToolProjectionEvent::End { index } => {
|
|
||||||
if let Some((_, _, complete)) = calls.get_mut(index) {
|
|
||||||
*complete = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let calls = calls
|
|
||||||
.into_iter()
|
|
||||||
.filter(|(_, _, complete)| *complete)
|
|
||||||
.map(|(name, arguments, _)| {
|
|
||||||
serde_json::from_str(&arguments)
|
|
||||||
.map(|arguments| (name, arguments))
|
|
||||||
.map_err(|error| format!("invalid DSML tool arguments: {error}"))
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
if has_tool_marker && calls.is_empty() {
|
|
||||||
return Err("invalid or incomplete DSML tool call".into());
|
|
||||||
}
|
|
||||||
Ok((content, calls))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle(mut stream: TcpStream, state: &State) {
|
fn handle(mut stream: TcpStream, state: &State) {
|
||||||
let _ = stream.set_write_timeout(Some(HTTP_IO_TIMEOUT));
|
let _ = stream.set_write_timeout(Some(HTTP_IO_TIMEOUT));
|
||||||
let started = Instant::now();
|
let started = Instant::now();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
pub(super) use crate::dsml::{SYNTAXES as TOOL_SYNTAXES, Syntax as ToolSyntax};
|
||||||
|
|
||||||
const TOOL_MEMORY_FILE: &str = "tool-replay.json";
|
const TOOL_MEMORY_FILE: &str = "tool-replay.json";
|
||||||
const TOOL_MEMORY_MAX_IDS: usize = 100_000;
|
const TOOL_MEMORY_MAX_IDS: usize = 100_000;
|
||||||
@@ -698,43 +699,6 @@ fn repair_generated_tools(text: &str) -> Option<String> {
|
|||||||
Some(repaired)
|
Some(repaired)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub(super) struct ToolSyntax {
|
|
||||||
pub(super) tool_start: &'static str,
|
|
||||||
pub(super) tool_end: &'static str,
|
|
||||||
pub(super) invoke_start: &'static str,
|
|
||||||
pub(super) invoke_end: &'static str,
|
|
||||||
pub(super) parameter_start: &'static str,
|
|
||||||
pub(super) parameter_end: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) const TOOL_SYNTAXES: [ToolSyntax; 3] = [
|
|
||||||
ToolSyntax {
|
|
||||||
tool_start: "<|DSML|tool_calls>",
|
|
||||||
tool_end: "</|DSML|tool_calls>",
|
|
||||||
invoke_start: "<|DSML|invoke",
|
|
||||||
invoke_end: "</|DSML|invoke>",
|
|
||||||
parameter_start: "<|DSML|parameter",
|
|
||||||
parameter_end: "</|DSML|parameter>",
|
|
||||||
},
|
|
||||||
ToolSyntax {
|
|
||||||
tool_start: "<DSML|tool_calls>",
|
|
||||||
tool_end: "</DSML|tool_calls>",
|
|
||||||
invoke_start: "<DSML|invoke",
|
|
||||||
invoke_end: "</DSML|invoke>",
|
|
||||||
parameter_start: "<DSML|parameter",
|
|
||||||
parameter_end: "</DSML|parameter>",
|
|
||||||
},
|
|
||||||
ToolSyntax {
|
|
||||||
tool_start: "<tool_calls>",
|
|
||||||
tool_end: "</tool_calls>",
|
|
||||||
invoke_start: "<invoke",
|
|
||||||
invoke_end: "</invoke>",
|
|
||||||
parameter_start: "<parameter",
|
|
||||||
parameter_end: "</parameter>",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
fn parse_tool_parameter(
|
fn parse_tool_parameter(
|
||||||
text: &str,
|
text: &str,
|
||||||
cursor: &mut usize,
|
cursor: &mut usize,
|
||||||
|
|||||||
Reference in New Issue
Block a user