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
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["crates/capture-ipc", "crates/capture-policy", "crates/core", "crates/desktop-api", "crates/gateway", "crates/prompt-ir", "crates/proxy-recovery", "crates/storage"]
members = ["crates/adapters", "crates/capture-ipc", "crates/capture-policy", "crates/core", "crates/desktop-api", "crates/gateway", "crates/prompt-ir", "crates/proxy-recovery", "crates/storage"]
exclude = ["apps/desktop/src-tauri"]
resolver = "3"

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

## 当前实现

当前实现包含版本化 Prompt IR、共享捕获策略、可信 Core 入口、SQLCipher 加密存储、可双向流式转发的本地 AI Gateway,以及 React + Tauri 桌面工作台。
当前实现包含版本化 Prompt IR、隔离式 OpenAI-compatible 适配器注册表、共享捕获策略、可信 Core 入口、SQLCipher 加密存储、可双向流式转发的本地 AI Gateway,以及 React + Tauri 桌面工作台。

启动桌面工作台:

Expand All @@ -35,6 +35,8 @@ cargo clippy --workspace --all-targets -- -D warnings

Rust crate 位于 `crates/prompt-ir`,公开 JSON Schema 位于 `schemas/prompt-ir/v0.1.schema.json`。

解析器注册表位于 `crates/adapters`。OpenAI Responses、Chat Completions 与 Completions 使用固定 capture/golden 样本;适配器错误、panic、未知内容和无匹配协议均会隔离并降级 Raw。

加密存储位于 `crates/storage`:使用 SQLCipher、WAL、版本化迁移与 FTS5,数据库 key 由 Windows Credential Manager 或 macOS Keychain 持有,并覆盖错误密钥拒绝、加密备份恢复与落盘明文 canary 测试。

桌面应用位于 `apps/desktop`。Tauri 通过 OS 凭据库打开 SQLCipher,并从加密数据库加载三栏工作台;首次启动会幂等写入一条经过共享 Capture Policy 清洗的演示请求。浏览器开发模式继续使用无凭据 fixture,实时 Gateway 捕获将在后续接入。
Expand Down
13 changes: 12 additions & 1 deletion apps/desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2.5.3", features = [] }

[dependencies]
codeischeap-adapters = { path = "../../../crates/adapters" }
codeischeap-capture-ipc = { path = "../../../crates/capture-ipc" }
codeischeap-capture-policy = { path = "../../../crates/capture-policy" }
codeischeap-core = { path = "../../../crates/core" }
codeischeap-desktop-api = { path = "../../../crates/desktop-api" }
codeischeap-prompt-ir = { path = "../../../crates/prompt-ir" }
codeischeap-storage = { path = "../../../crates/storage" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
84 changes: 0 additions & 84 deletions apps/desktop/src-tauri/fixtures/demo-prompt-ir.json

This file was deleted.

19 changes: 15 additions & 4 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
use std::error::Error;
use std::sync::Mutex;

use codeischeap_adapters::AdapterRegistry;
use codeischeap_capture_ipc::CaptureEnvelope;
use codeischeap_capture_policy::CapturePolicy;
use codeischeap_core::persist_capture;
use codeischeap_desktop_api::{WorkspaceBootstrap, load_workspace};
use codeischeap_prompt_ir::PromptIr;
use codeischeap_storage::{EncryptedStore, OsKeyStore};
use tauri::menu::{Menu, MenuItem};
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
use tauri::{AppHandle, Manager, State, WindowEvent};

const DEMO_CAPTURE: &str = include_str!("../fixtures/demo-capture.json");
const DEMO_PROMPT_IR: &str = include_str!("../fixtures/demo-prompt-ir.json");
const KEY_SERVICE: &str = "com.codeischeap.desktop";
const KEY_ACCOUNT: &str = "capture-database-v1";

Expand Down Expand Up @@ -100,10 +99,10 @@ fn seed_demo_capture(store: &mut EncryptedStore) -> Result<(), Box<dyn Error>> {
if store.get_capture(&envelope.capture_id)?.is_some() {
return Ok(());
}
let prompt_ir: PromptIr = serde_json::from_str(DEMO_PROMPT_IR)?;
let policy = CapturePolicy::load_default()?;
let sanitized = policy.sanitize_envelope(envelope)?;
persist_capture(store, &sanitized, Some(&prompt_ir))?;
let parsed = AdapterRegistry::default().parse(&sanitized);
persist_capture(store, &sanitized, parsed.prompt_ir.as_ref())?;
Ok(())
}

Expand Down Expand Up @@ -139,6 +138,18 @@ mod tests {
assert_eq!(workspace.requests.len(), 1);
assert_eq!(workspace.requests[0].id, "demo_openai_parser");
assert_eq!(workspace.requests[0].provider, "OpenAI");
assert_eq!(
workspace.requests[0].prompt_preview,
"Fix the failing parser test."
);
assert!(workspace.requests[0].has_tools);
assert!(
workspace.requests[0]
.detail
.anatomy
.iter()
.any(|section| section.id == "messages" && section.count == 1)
);
let encoded = serde_json::to_string(&workspace).expect("workspace must encode");
for forbidden in ["Bearer ", "sk-", "x-api-key"] {
assert!(
Expand Down
13 changes: 13 additions & 0 deletions crates/adapters/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "codeischeap-adapters"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
description = "Provider adapter registry and Prompt IR parsers for CodeIsCheap"

[dependencies]
codeischeap-capture-ipc = { path = "../capture-ipc" }
codeischeap-capture-policy = { path = "../capture-policy" }
codeischeap-prompt-ir = { path = "../prompt-ir" }
serde.workspace = true
serde_json.workspace = true
19 changes: 19 additions & 0 deletions crates/adapters/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# CodeIsCheap Adapters

Provider adapters receive only `SanitizedCapture` values and emit validated Prompt IR. The registry orders candidates by confidence, isolates adapter errors and panics, and falls back to Raw when no valid adapter succeeds.

The OpenAI-compatible adapter currently covers:

- `/v1/responses`
- `/v1/chat/completions`
- `/v1/completions`
- system/developer/user/assistant/tool messages
- text, image, audio, file, tool call, and tool result parts
- function definitions and common generation parameters

Regenerate checked-in golden Prompt IR after an intentional mapping change:

```powershell
cargo run -p codeischeap-adapters --bin export-openai-goldens
cargo test -p codeischeap-adapters --all-targets
```
39 changes: 39 additions & 0 deletions crates/adapters/src/bin/export-openai-goldens.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use std::fs;
use std::path::{Path, PathBuf};

use codeischeap_adapters::AdapterRegistry;
use codeischeap_capture_ipc::CaptureEnvelope;
use codeischeap_capture_policy::CapturePolicy;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
export(
&fixtures,
"openai-responses-capture.json",
"openai-responses-prompt-ir.json",
)?;
export(
&fixtures,
"openai-chat-capture.json",
"openai-chat-prompt-ir.json",
)?;
Ok(())
}

fn export(
fixtures: &Path,
capture_name: &str,
golden_name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let envelope: CaptureEnvelope =
serde_json::from_str(&fs::read_to_string(fixtures.join(capture_name))?)?;
let sanitized = CapturePolicy::load_default()?.sanitize_envelope(envelope)?;
let result = AdapterRegistry::default().parse(&sanitized);
let prompt = result
.prompt_ir
.ok_or("fixture did not produce Prompt IR")?;
let mut encoded = serde_json::to_string_pretty(&prompt)?;
encoded.push('\n');
fs::write(fixtures.join(golden_name), encoded)?;
Ok(())
}
12 changes: 12 additions & 0 deletions crates/adapters/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//! Isolated provider adapters that turn sanitized captures into Prompt IR.

mod model;
mod openai;
mod registry;

pub use model::{
AdapterError, AdapterInput, AdapterOutput, ParseIssue, ParseIssueCode, ParseResult,
PromptAdapter,
};
pub use openai::{OPENAI_ADAPTER_ID, OpenAiAdapter};
pub use registry::AdapterRegistry;
Loading
Loading