Skip to content

Commit fde8b8d

Browse files
RoyLinRoyLin
authored andcommitted
ci: add PR/push ci workflow, sync optionalDependencies to all 7 platforms
1 parent 0f8b9d0 commit fde8b8d

15 files changed

Lines changed: 1883 additions & 402 deletions

File tree

.github/workflows/ci.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
env:
10+
CARGO_TERM_COLOR: always
11+
12+
jobs:
13+
check:
14+
name: Check
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Setup workspace context
20+
run: bash .github/setup-workspace.sh
21+
22+
- name: Install Rust
23+
uses: dtolnay/rust-toolchain@stable
24+
with:
25+
components: rustfmt, clippy
26+
27+
- uses: Swatinem/rust-cache@v2
28+
29+
- name: Install protobuf compiler
30+
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
31+
32+
- name: Format check
33+
run: cargo fmt --all -- --check
34+
35+
- name: Clippy
36+
run: cargo clippy --workspace -- -D warnings
37+
38+
- name: Tests
39+
run: cargo test --workspace --lib

core/skills/agentic-parse.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
name: agentic-parse
3+
description: Intelligent document parsing with LLM-enhanced extraction for PDFs, Word docs, spreadsheets, code, and more
4+
allowed-tools: "agentic_parse(*), read(*)"
5+
kind: instruction
6+
tags: ["parse", "document", "pdf", "extraction", "llm"]
7+
version: "1.0.0"
8+
---
9+
10+
# Agentic Parse Skill
11+
12+
You are a document intelligence assistant. Use the `agentic_parse` tool to extract structured information from any file type, including binary formats like PDFs and Word documents.
13+
14+
## When to Use
15+
16+
Use `agentic_parse` when the user asks to:
17+
- Read, summarize, or extract information from PDFs, Word docs, or spreadsheets
18+
- Parse complex file formats that `read` cannot handle (binary, encoding issues)
19+
- Answer specific questions about document content (`query` parameter)
20+
- Identify structure in source code, CSV data, or configuration files
21+
22+
## Parse Strategies
23+
24+
| Strategy | Triggers on |
25+
|----------|-------------|
26+
| `auto` | Default — inferred from extension and content heuristics |
27+
| `structured` | JSON, TOML, YAML, XML, HCL |
28+
| `narrative` | Markdown, plain text, RST, AsciiDoc |
29+
| `tabular` | CSV, TSV |
30+
| `code` | Rust, Python, JS, Go, Java, and other source files |
31+
32+
## Usage Examples
33+
34+
### Summarize a PDF
35+
```
36+
agentic_parse({ path: "report.pdf", query: "What are the key findings?" })
37+
```
38+
39+
### Query a CSV
40+
```
41+
agentic_parse({ path: "data.csv", strategy: "tabular", query: "What columns exist and how many rows?" })
42+
```
43+
44+
### Get a structural overview (no LLM)
45+
```
46+
agentic_parse({ path: "config.yaml" })
47+
```
48+
49+
### Extract symbols from source code
50+
```
51+
agentic_parse({ path: "lib.rs", strategy: "code" })
52+
```
53+
54+
## Best Practices
55+
56+
1. **Provide a `query`** to enable LLM-enhanced semantic extraction
57+
2. **Omit `query`** for a fast structural overview without LLM cost
58+
3. **Adjust `max_chars`** for very large documents (default: 8000 characters)
59+
4. **Use `read` instead** for plain-text files where full content is needed without semantic extraction

core/src/agent_api.rs

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,27 @@ pub struct SessionOptions {
181181
/// dispatched locally. The executor is also propagated to sub-agents via
182182
/// the sentinel hook mechanism.
183183
pub hook_executor: Option<Arc<dyn crate::hooks::HookExecutor>>,
184-
/// Optional document parser registry for agentic_search tool.
184+
/// Optional document parser registry for document-aware tools.
185185
///
186-
/// When set, enables custom document format support (PDF, Excel, Word, etc.)
187-
/// for the agentic_search tool. If not set, only plain text files are searched.
188-
pub document_parser_registry:
189-
Option<Arc<crate::tools::document_parser::DocumentParserRegistry>>,
186+
/// When set, enables custom format support (PDF, Excel, Word, etc.)
187+
/// for tools such as `agentic_search` and `agentic_parse`.
188+
/// If not set, only plain text files are accessible to those tools.
189+
pub document_parser_registry: Option<Arc<crate::document_parser::DocumentParserRegistry>>,
190+
/// Plugins to mount onto this session.
191+
///
192+
/// Each plugin is loaded in order after the core tools are registered.
193+
/// Use [`PluginManager`] or add plugins directly via [`SessionOptions::with_plugin`].
194+
///
195+
/// # Example
196+
///
197+
/// ```rust,no_run
198+
/// use a3s_code_core::{SessionOptions, AgenticSearchPlugin, AgenticParsePlugin};
199+
///
200+
/// let opts = SessionOptions::new()
201+
/// .with_plugin(AgenticSearchPlugin::new())
202+
/// .with_plugin(AgenticParsePlugin::new());
203+
/// ```
204+
pub plugins: Vec<std::sync::Arc<dyn crate::plugin::Plugin>>,
190205
}
191206

192207
impl std::fmt::Debug for SessionOptions {
@@ -221,6 +236,10 @@ impl std::fmt::Debug for SessionOptions {
221236
.field("auto_compact_threshold", &self.auto_compact_threshold)
222237
.field("continuation_enabled", &self.continuation_enabled)
223238
.field("max_continuation_turns", &self.max_continuation_turns)
239+
.field(
240+
"plugins",
241+
&self.plugins.iter().map(|p| p.name()).collect::<Vec<_>>(),
242+
)
224243
.field("mcp_manager", &self.mcp_manager.is_some())
225244
.field("temperature", &self.temperature)
226245
.field("thinking_budget", &self.thinking_budget)
@@ -235,6 +254,15 @@ impl SessionOptions {
235254
Self::default()
236255
}
237256

257+
/// Mount a plugin onto this session.
258+
///
259+
/// The plugin's tools are registered after the core tools, in the order
260+
/// plugins are added.
261+
pub fn with_plugin(mut self, plugin: impl crate::plugin::Plugin + 'static) -> Self {
262+
self.plugins.push(std::sync::Arc::new(plugin));
263+
self
264+
}
265+
238266
pub fn with_model(mut self, model: impl Into<String>) -> Self {
239267
self.model = Some(model.into());
240268
self
@@ -1017,13 +1045,6 @@ impl Agent {
10171045
}
10181046
}
10191047

1020-
// Register custom agentic_search tool with document parser registry if provided
1021-
if let Some(ref registry) = opts.document_parser_registry {
1022-
use crate::tools::AgenticSearchTool;
1023-
let tool = AgenticSearchTool::with_parser_registry((**registry).clone());
1024-
tool_executor.register_dynamic_tool(Arc::new(tool));
1025-
}
1026-
10271048
let tool_defs = tool_executor.definitions();
10281049

10291050
// Build prompt slots: start from session options or agent-level config
@@ -1092,6 +1113,37 @@ impl Agent {
10921113
}
10931114
let effective_registry = Arc::new(base_registry);
10941115

1116+
// Load user-specified plugins — must happen before `skill_prompt` is built
1117+
// so that plugin companion skills appear in the initial system prompt.
1118+
if !opts.plugins.is_empty() {
1119+
use crate::plugin::PluginContext;
1120+
let mut plugin_ctx = PluginContext::new()
1121+
.with_llm(Arc::clone(&self.llm_client))
1122+
.with_skill_registry(Arc::clone(&effective_registry));
1123+
if let Some(ref dp) = opts.document_parser_registry {
1124+
plugin_ctx = plugin_ctx.with_document_parsers(Arc::clone(dp));
1125+
}
1126+
let plugin_registry = tool_executor.registry();
1127+
for plugin in &opts.plugins {
1128+
tracing::info!("Loading plugin '{}' v{}", plugin.name(), plugin.version());
1129+
match plugin.load(plugin_registry, &plugin_ctx) {
1130+
Ok(()) => {
1131+
for skill in plugin.skills() {
1132+
tracing::debug!(
1133+
"Plugin '{}' registered skill '{}'",
1134+
plugin.name(),
1135+
skill.name
1136+
);
1137+
effective_registry.register_unchecked(skill);
1138+
}
1139+
}
1140+
Err(e) => {
1141+
tracing::error!("Plugin '{}' failed to load: {}", plugin.name(), e);
1142+
}
1143+
}
1144+
}
1145+
}
1146+
10951147
// Append skill directory listing to the extra prompt slot
10961148
let skill_prompt = effective_registry.to_system_prompt();
10971149
if !skill_prompt.is_empty() {
@@ -1235,6 +1287,11 @@ impl Agent {
12351287
}
12361288
tool_context = tool_context.with_agent_event_tx(agent_event_tx);
12371289

1290+
// Wire document parser registry so all tools can access it via ToolContext.
1291+
if let Some(ref registry) = opts.document_parser_registry {
1292+
tool_context = tool_context.with_document_parsers(Arc::clone(registry));
1293+
}
1294+
12381295
// Wire sandbox when a concrete handle is provided by the host application.
12391296
if let Some(handle) = opts.sandbox_handle.clone() {
12401297
tool_executor.registry().set_sandbox(Arc::clone(&handle));

0 commit comments

Comments
 (0)