snapshot of https://github.com/toeverything/AFFiNE.git (canary, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Build & Test / Analyze (javascript, affine) (push) Failing after 4s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 4s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 3s
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 4s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 3s
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Release / Canary Gate (push) Has been skipped
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Copilot Test Automatically / Setup Test (push) Failing after 1s
Release / Prepare (push) Failing after 2s
Release / Release Cloud (push) Has been skipped
Release / Release Desktop (push) Has been skipped
Release / Release Docker Image (push) Has been skipped
Release / Release Mobile (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-16 04:01:20 +05:00
commit 702cc698a7
9990 changed files with 1073618 additions and 0 deletions

View File

@@ -0,0 +1,288 @@
use llm_adapter::capability::{
AttachmentKind, AttachmentSource, ModelFeature, ModelInput, ModelOutput, ModelRequirements,
};
use crate::llm::{
prompt_catalog::{built_in_managed_target, built_in_managed_targets},
route::CopilotManagedTier,
};
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum RouteOperation {
Chat,
Structured,
Embedding,
Rerank,
Image,
Transcription,
}
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct CatalogSlot {
pub(crate) id: &'static str,
pub(crate) operation: RouteOperation,
pub(crate) requirements: ModelRequirements,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum QuotaPolicy {
Metered,
Internal,
System,
}
pub(crate) fn quota_policy(slot: &CatalogSlot, built_in_route_id: Option<&str>) -> QuotaPolicy {
if matches!(slot.id, "index.embedding" | "search.rerank") {
QuotaPolicy::System
} else if built_in_route_id == Some("Summary as title") {
QuotaPolicy::Internal
} else {
QuotaPolicy::Metered
}
}
pub(crate) fn managed_targets(
slot: &CatalogSlot,
built_in_route_id: Option<&str>,
managed_tier: CopilotManagedTier,
) -> Option<Vec<String>> {
if let Some(route_id) = built_in_route_id {
return built_in_managed_targets(route_id, managed_tier == CopilotManagedTier::Premium).map(<[String]>::to_vec);
}
match slot.id {
"index.embedding" => Some(vec!["gemini-embedding-001".to_string()]),
"search.rerank" => Some(vec!["gpt-4o-mini".to_string()]),
_ => None,
}
}
pub(crate) fn managed_selected_target(
built_in_route_id: Option<&str>,
target_id: &str,
managed_tier: CopilotManagedTier,
) -> Option<String> {
built_in_managed_target(
built_in_route_id?,
target_id,
managed_tier == CopilotManagedTier::Premium,
)
.map(|target| target.model_id.clone())
}
pub(crate) fn slot(id: &str) -> Option<CatalogSlot> {
let (canonical, operation, input, output, features, attachment_kinds, attachment_sources) = match id {
"chat.default" | "prompt.text" => (
if id == "chat.default" {
"chat.default"
} else {
"prompt.text"
},
RouteOperation::Chat,
vec![ModelInput::Text],
vec![ModelOutput::Text],
vec![],
vec![],
vec![],
),
"chat.structured" | "prompt.structured" => (
if id == "chat.structured" {
"chat.structured"
} else {
"prompt.structured"
},
RouteOperation::Structured,
vec![ModelInput::Text],
vec![ModelOutput::Structured],
vec![],
vec![],
vec![],
),
"action.mindmap.generate" => (
"action.mindmap.generate",
RouteOperation::Structured,
vec![ModelInput::Text],
vec![ModelOutput::Structured],
vec![],
vec![],
vec![],
),
"action.slides.outline" => (
"action.slides.outline",
RouteOperation::Structured,
vec![ModelInput::Text],
vec![ModelOutput::Structured],
vec![],
vec![],
vec![],
),
"image.generate"
| "action.image.filter.sketch"
| "action.image.filter.clay"
| "action.image.filter.anime"
| "action.image.filter.pixel" => (
match id {
"image.generate" => "image.generate",
"action.image.filter.sketch" => "action.image.filter.sketch",
"action.image.filter.clay" => "action.image.filter.clay",
"action.image.filter.anime" => "action.image.filter.anime",
_ => "action.image.filter.pixel",
},
RouteOperation::Image,
vec![ModelInput::Text],
vec![ModelOutput::Image],
vec![],
vec![],
vec![],
),
"index.embedding" => (
"index.embedding",
RouteOperation::Embedding,
vec![ModelInput::Text],
vec![ModelOutput::Embedding],
vec![],
vec![],
vec![],
),
"search.rerank" => (
"search.rerank",
RouteOperation::Rerank,
vec![ModelInput::Text],
vec![ModelOutput::Rerank],
vec![],
vec![],
vec![],
),
"transcript.audio" => (
"transcript.audio",
RouteOperation::Transcription,
vec![ModelInput::Audio],
vec![ModelOutput::Structured],
vec![],
vec![AttachmentKind::Audio],
vec![
AttachmentSource::Url,
AttachmentSource::Data,
AttachmentSource::Bytes,
AttachmentSource::FileHandle,
],
),
_ => return None,
};
Some(CatalogSlot {
id: canonical,
operation,
requirements: ModelRequirements {
input,
output,
features,
attachment_kinds,
attachment_sources,
},
})
}
pub(crate) fn with_request_requirements(
mut slot: CatalogSlot,
needs_tools: bool,
attachment_kinds: Vec<AttachmentKind>,
attachment_sources: Vec<AttachmentSource>,
) -> CatalogSlot {
if needs_tools {
slot.requirements.features.push(ModelFeature::ToolCalling);
}
for kind in attachment_kinds {
let input = match kind {
AttachmentKind::Image => ModelInput::Image,
AttachmentKind::Audio => ModelInput::Audio,
AttachmentKind::File => ModelInput::File,
};
if !slot.requirements.input.contains(&input) {
slot.requirements.input.push(input);
}
if !slot.requirements.attachment_kinds.contains(&kind) {
slot.requirements.attachment_kinds.push(kind);
}
}
for source in attachment_sources {
if !slot.requirements.attachment_sources.contains(&source) {
slot.requirements.attachment_sources.push(source);
}
}
slot
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn inventory_slots_have_one_operation_and_explicit_requirements() {
let slots = [
"chat.default",
"chat.structured",
"prompt.text",
"prompt.structured",
"action.mindmap.generate",
"action.slides.outline",
"action.image.filter.sketch",
"action.image.filter.clay",
"action.image.filter.anime",
"action.image.filter.pixel",
"image.generate",
"index.embedding",
"search.rerank",
"transcript.audio",
];
for id in slots {
let slot = slot(id).expect("inventory slot must exist");
assert!(!slot.requirements.input.is_empty());
assert!(!slot.requirements.output.is_empty());
}
}
#[test]
fn managed_routes_are_built_in_by_prompt_or_system_slot() {
let prompt_slot = slot("prompt.text").unwrap();
assert_eq!(
managed_targets(&prompt_slot, Some("Summary as title"), CopilotManagedTier::Standard).unwrap(),
["gpt-5.6-luna"]
);
assert!(
managed_targets(
&prompt_slot,
Some("workflow:presentation"),
CopilotManagedTier::Standard
)
.is_none()
);
assert_eq!(
managed_targets(
&prompt_slot,
Some("Transcript audio structured"),
CopilotManagedTier::Premium
)
.unwrap(),
["gemini-3.7-flash"]
);
assert_eq!(
managed_targets(&slot("index.embedding").unwrap(), None, CopilotManagedTier::Standard).unwrap(),
["gemini-embedding-001"]
);
assert_eq!(
managed_targets(&slot("search.rerank").unwrap(), None, CopilotManagedTier::Standard).unwrap(),
["gpt-4o-mini"]
);
assert!(matches!(
quota_policy(&prompt_slot, Some("Summary as title")),
QuotaPolicy::Internal
));
assert!(matches!(
quota_policy(&prompt_slot, Some("Chat With AFFiNE AI")),
QuotaPolicy::Metered
));
assert!(matches!(
quota_policy(&slot("index.embedding").unwrap(), None),
QuotaPolicy::System
));
}
}

View File

@@ -0,0 +1,50 @@
#[napi_derive::napi(string_enum)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum CopilotManagedTier {
Standard,
Premium,
}
#[derive(Clone)]
#[napi_derive::napi(object)]
pub struct CopilotAccessProjection {
pub route_allowed: bool,
pub managed_tier: CopilotManagedTier,
pub server_byok: bool,
pub local_byok: bool,
}
#[derive(Clone)]
#[napi_derive::napi(object)]
pub struct CopilotTargetOverrideInput {
pub profile_id: String,
pub model_id: String,
}
#[derive(Clone)]
#[napi_derive::napi(object)]
pub struct CopilotRouteCheckInput {
pub slot: String,
pub built_in_route_id: Option<String>,
pub workspace_id: Option<String>,
pub user_id: Option<String>,
pub local_lease_id: Option<String>,
pub access: CopilotAccessProjection,
pub managed_target_id: Option<String>,
pub target_override: Option<CopilotTargetOverrideInput>,
}
#[derive(Clone)]
#[napi_derive::napi(object)]
pub struct CopilotExecuteInput {
pub slot: String,
pub built_in_route_id: Option<String>,
pub workspace_id: Option<String>,
pub user_id: Option<String>,
pub local_lease_id: Option<String>,
pub access: CopilotAccessProjection,
pub managed_target_id: Option<String>,
pub target_override: Option<CopilotTargetOverrideInput>,
#[napi(ts_type = "unknown")]
pub request: serde_json::Value,
}

View File

@@ -0,0 +1,15 @@
mod catalog;
mod contract;
mod policy;
pub(crate) use catalog::{
CatalogSlot, QuotaPolicy, RouteOperation, managed_selected_target, managed_targets, quota_policy, slot,
with_request_requirements,
};
pub use contract::{
CopilotAccessProjection, CopilotExecuteInput, CopilotManagedTier, CopilotRouteCheckInput, CopilotTargetOverrideInput,
};
pub(crate) use policy::{
AuthorizedProviderProfile, AuthorizedTargetRef, CredentialRef, ProfileSource, RouteDecision, RouteDecisionReason,
RoutePolicyInput, TargetOverride, decide,
};

View File

@@ -0,0 +1,304 @@
use llm_adapter::{
capability::declared_model_matches,
target::{BackendEndpoint, EgressPolicy, OpenAiDialect},
};
use super::CatalogSlot;
use crate::{llm::byok::ByokModelDeclaration, runtime::Deployment};
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum ProfileSource {
Server,
Local,
Managed,
}
pub(crate) struct AuthorizedProviderProfile {
pub(crate) profile_id: String,
pub(crate) source: ProfileSource,
pub(crate) provider: String,
pub(crate) endpoint: BackendEndpoint,
pub(crate) openai_dialect: Option<OpenAiDialect>,
pub(crate) egress_policy: EgressPolicy,
pub(crate) models: Vec<ByokModelDeclaration>,
pub(crate) sort_order: i32,
pub(crate) credential_ref: CredentialRef,
}
pub(crate) enum CredentialRef {
Envelope { encrypted: String, aad: Vec<u8> },
Managed { profile_id: String },
}
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct TargetOverride {
pub(crate) profile_id: String,
pub(crate) model_id: String,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum RouteDecisionReason {
ByokDisabled,
AccessUnavailable,
ExplicitTargetUnavailable,
NoCompatibleTarget,
ManagedPresetUnavailable,
}
pub(crate) enum RouteDecision {
Ready(Vec<AuthorizedTargetRef>),
Denied(RouteDecisionReason),
NoRoute(RouteDecisionReason),
}
pub(crate) struct AuthorizedTargetRef {
pub(crate) profile_index: usize,
pub(crate) model_index: usize,
}
pub(crate) struct RoutePolicyInput<'a> {
pub(crate) slot: &'a CatalogSlot,
pub(crate) deployment: Deployment,
pub(crate) byok_enabled: bool,
pub(crate) access_available: bool,
pub(crate) profiles: &'a [AuthorizedProviderProfile],
pub(crate) target_override: Option<&'a TargetOverride>,
pub(crate) target_override_managed: bool,
}
pub(crate) fn decide(input: RoutePolicyInput<'_>) -> RouteDecision {
if input.deployment == Deployment::SelfHosted && !input.byok_enabled {
return RouteDecision::NoRoute(RouteDecisionReason::ByokDisabled);
}
if input.target_override_managed {
if input.deployment != Deployment::Cloud {
return RouteDecision::Denied(RouteDecisionReason::ExplicitTargetUnavailable);
}
if !input.access_available {
return RouteDecision::Denied(RouteDecisionReason::AccessUnavailable);
}
}
if let Some(target) = input.target_override {
let mut selected = compatible_targets(&input, input.target_override_managed);
selected.retain(|candidate| {
let profile = &input.profiles[candidate.profile_index];
let model = &profile.models[candidate.model_index];
profile.profile_id == target.profile_id && model.model_id == target.model_id
});
return if selected.is_empty() {
RouteDecision::Denied(RouteDecisionReason::ExplicitTargetUnavailable)
} else {
RouteDecision::Ready(selected)
};
}
let byok = compatible_targets(&input, false);
if !byok.is_empty() {
return RouteDecision::Ready(byok);
}
if !input.access_available {
return RouteDecision::Denied(RouteDecisionReason::AccessUnavailable);
}
if input.deployment == Deployment::Cloud {
let managed = compatible_targets(&input, true);
if managed.is_empty() {
RouteDecision::NoRoute(RouteDecisionReason::ManagedPresetUnavailable)
} else {
RouteDecision::Ready(managed)
}
} else {
RouteDecision::NoRoute(RouteDecisionReason::NoCompatibleTarget)
}
}
fn compatible_targets(input: &RoutePolicyInput<'_>, managed: bool) -> Vec<AuthorizedTargetRef> {
let mut profiles = input
.profiles
.iter()
.enumerate()
.filter(|(_, profile)| (profile.source == ProfileSource::Managed) == managed)
.collect::<Vec<_>>();
profiles.sort_by_key(|(_, profile)| profile.sort_order);
profiles
.into_iter()
.flat_map(|(profile_index, profile)| {
profile
.models
.iter()
.enumerate()
.filter(|(_, model)| model.enabled && declared_model_matches(&model.capabilities, &input.slot.requirements))
.map(move |(model_index, _)| AuthorizedTargetRef {
profile_index,
model_index,
})
})
.collect()
}
#[cfg(test)]
mod tests {
use llm_adapter::capability::{DeclaredModelCapability, ModelInput, ModelOutput};
use super::*;
use crate::llm::{byok::ByokModelDeclaration, route::catalog};
fn profile(id: &str, source: ProfileSource, model: &str, output: ModelOutput) -> AuthorizedProviderProfile {
AuthorizedProviderProfile {
profile_id: id.to_string(),
source,
provider: "openai".to_string(),
endpoint: BackendEndpoint::Custom("https://example.test/v1".to_string()),
openai_dialect: Some(OpenAiDialect::Responses),
egress_policy: EgressPolicy::PublicOnly,
models: vec![ByokModelDeclaration {
model_id: model.to_string(),
enabled: true,
capabilities: vec![DeclaredModelCapability {
input: vec![ModelInput::Text],
output: vec![output],
features: vec![],
attachment_kinds: vec![],
attachment_sources: vec![],
}],
}],
sort_order: 0,
credential_ref: CredentialRef::Managed {
profile_id: id.to_string(),
},
}
}
#[test]
fn deployment_matrix_and_unsupported_only_fallback() {
let slot = catalog::slot("chat.default").unwrap();
let cases = [
(
Deployment::Cloud,
true,
vec![profile("managed", ProfileSource::Managed, "A", ModelOutput::Text)],
true,
),
(
Deployment::SelfHosted,
false,
vec![profile("managed", ProfileSource::Managed, "A", ModelOutput::Text)],
false,
),
(Deployment::SelfHosted, true, vec![], false),
(
Deployment::Cloud,
true,
vec![
profile("byok", ProfileSource::Server, "B", ModelOutput::Image),
profile("managed", ProfileSource::Managed, "A", ModelOutput::Text),
],
true,
),
];
for (deployment, byok_enabled, profiles, ready) in cases {
let decision = decide(RoutePolicyInput {
slot: &slot,
deployment,
byok_enabled,
access_available: true,
profiles: &profiles,
target_override: None,
target_override_managed: false,
});
assert_eq!(matches!(decision, RouteDecision::Ready(_)), ready);
}
let profiles = vec![
profile("byok", ProfileSource::Server, "B", ModelOutput::Text),
profile("managed", ProfileSource::Managed, "A", ModelOutput::Text),
];
assert!(matches!(
decide(RoutePolicyInput {
slot: &slot,
deployment: Deployment::Cloud,
byok_enabled: true,
access_available: false,
profiles: &profiles,
target_override: None,
target_override_managed: false,
}),
RouteDecision::Ready(_)
));
assert!(matches!(
decide(RoutePolicyInput {
slot: &slot,
deployment: Deployment::Cloud,
byok_enabled: true,
access_available: false,
profiles: &profiles[1..],
target_override: None,
target_override_managed: false,
}),
RouteDecision::Denied(RouteDecisionReason::AccessUnavailable)
));
}
#[test]
fn override_is_complete_and_custom_model_is_not_replaced_by_managed_model() {
let slot = catalog::slot("chat.default").unwrap();
let profiles = vec![
profile("byok", ProfileSource::Server, "vendor/model:B", ModelOutput::Text),
profile("managed", ProfileSource::Managed, "model:A", ModelOutput::Text),
];
let target = TargetOverride {
profile_id: "byok".to_string(),
model_id: "vendor/model:B".to_string(),
};
let RouteDecision::Ready(candidates) = decide(RoutePolicyInput {
slot: &slot,
deployment: Deployment::Cloud,
byok_enabled: true,
access_available: true,
profiles: &profiles,
target_override: Some(&target),
target_override_managed: false,
}) else {
panic!("override should resolve");
};
assert_eq!(
profiles[candidates[0].profile_index].models[candidates[0].model_index].model_id,
"vendor/model:B"
);
let managed_target = TargetOverride {
profile_id: "managed".to_string(),
model_id: "model:A".to_string(),
};
let RouteDecision::Ready(candidates) = decide(RoutePolicyInput {
slot: &slot,
deployment: Deployment::Cloud,
byok_enabled: true,
access_available: true,
profiles: &profiles,
target_override: Some(&managed_target),
target_override_managed: true,
}) else {
panic!("managed selection should resolve");
};
assert!(matches!(
profiles[candidates[0].profile_index].source,
ProfileSource::Managed
));
let mut disabled = profile("disabled", ProfileSource::Server, "model:C", ModelOutput::Text);
disabled.models[0].enabled = false;
assert!(matches!(
decide(RoutePolicyInput {
slot: &slot,
deployment: Deployment::SelfHosted,
byok_enabled: true,
access_available: true,
profiles: &[disabled],
target_override: None,
target_override_managed: false,
}),
RouteDecision::NoRoute(RouteDecisionReason::NoCompatibleTarget)
));
}
}