1use sim_citizen_derive::non_citizen;
2use sim_kernel::{CapabilityName, Cx, Expr, Object, ObjectCompat, Result, ShapeRef, Symbol, Value};
3
4#[derive(Clone, Debug, PartialEq, Eq)]
9pub enum SkillRole {
10 Tool,
12 Model,
14 Resource,
16 Prompt,
18 Memory,
20 Retriever,
22 Judge,
24 Router,
26}
27
28impl SkillRole {
29 pub fn as_symbol(&self) -> Symbol {
31 Symbol::new(match self {
32 Self::Tool => "tool",
33 Self::Model => "model",
34 Self::Resource => "resource",
35 Self::Prompt => "prompt",
36 Self::Memory => "memory",
37 Self::Retriever => "retriever",
38 Self::Judge => "judge",
39 Self::Router => "router",
40 })
41 }
42}
43
44#[derive(Clone, Debug, PartialEq, Eq)]
46pub enum SkillPrivacyPolicy {
47 MetadataOnly,
49 NoRaw,
51 LocalOnly,
53 AllowRaw,
55}
56
57impl SkillPrivacyPolicy {
58 pub fn as_symbol(&self) -> Symbol {
60 Symbol::new(match self {
61 Self::MetadataOnly => "metadata-only",
62 Self::NoRaw => "no-raw",
63 Self::LocalOnly => "local-only",
64 Self::AllowRaw => "allow-raw",
65 })
66 }
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
74pub enum SkillCacheMode {
75 Disabled,
77 ReadThrough,
79 ReadOnly,
81 WriteOnly,
83 Refresh,
85}
86
87impl SkillCacheMode {
88 pub fn as_symbol(&self) -> Symbol {
90 Symbol::new(match self {
91 Self::Disabled => "disabled",
92 Self::ReadThrough => "read-through",
93 Self::ReadOnly => "read-only",
94 Self::WriteOnly => "write-only",
95 Self::Refresh => "refresh",
96 })
97 }
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
105pub enum SkillCassetteMode {
106 Disabled,
108 RecordReplay,
110 ReplayOnly,
112 RecordOnly,
114}
115
116impl SkillCassetteMode {
117 pub fn as_symbol(&self) -> Symbol {
119 Symbol::new(match self {
120 Self::Disabled => "disabled",
121 Self::RecordReplay => "record-replay",
122 Self::ReplayOnly => "replay-only",
123 Self::RecordOnly => "record-only",
124 })
125 }
126}
127
128#[derive(Clone, Debug, PartialEq, Eq)]
130pub struct SkillPolicy {
131 pub privacy: SkillPrivacyPolicy,
133 pub cache: SkillCacheMode,
135 pub cassette: SkillCassetteMode,
137 pub idempotent: bool,
140 pub semantic_key: Option<String>,
143}
144
145impl Default for SkillPolicy {
146 fn default() -> Self {
147 Self {
148 privacy: SkillPrivacyPolicy::NoRaw,
149 cache: SkillCacheMode::Disabled,
150 cassette: SkillCassetteMode::Disabled,
151 idempotent: false,
152 semantic_key: None,
153 }
154 }
155}
156
157#[derive(Clone)]
167#[non_citizen(
168 reason = "shape-bearing runtime skill card; serializable projection is skill/Card descriptor",
169 kind = "handle"
170)]
171pub struct SkillCard {
172 pub id: String,
174 pub symbol: Symbol,
176 pub aliases: Vec<Symbol>,
178 pub origin: Symbol,
180 pub title: String,
182 pub description: String,
184 pub input_shape: ShapeRef,
186 pub output_shape: ShapeRef,
188 pub roles: Vec<SkillRole>,
190 pub capabilities: Vec<CapabilityName>,
192 pub policy: SkillPolicy,
194 pub transport_id: String,
196 pub transport_kind: String,
198 pub operation: String,
200}
201
202pub struct FixtureSkillSpec {
204 pub id: String,
206 pub symbol: Symbol,
208 pub title: String,
210 pub description: String,
212 pub input_shape: ShapeRef,
214 pub output_shape: ShapeRef,
216 pub transport_id: String,
218 pub operation: String,
220}
221
222impl SkillCard {
223 pub fn fixture(spec: FixtureSkillSpec) -> Self {
228 let id = spec.id;
229 Self {
230 capabilities: vec![crate::skill_specific_call_capability(&id)],
231 id,
232 symbol: spec.symbol,
233 aliases: Vec::new(),
234 origin: Symbol::new("fixture"),
235 title: spec.title,
236 description: spec.description,
237 input_shape: spec.input_shape,
238 output_shape: spec.output_shape,
239 roles: vec![SkillRole::Tool],
240 policy: SkillPolicy::default(),
241 transport_id: spec.transport_id,
242 transport_kind: "fixture".to_owned(),
243 operation: spec.operation,
244 }
245 }
246
247 pub fn with_capability(mut self, capability: CapabilityName) -> Self {
249 self.capabilities.push(capability);
250 self
251 }
252
253 pub fn with_role(mut self, role: SkillRole) -> Self {
255 if !self.roles.contains(&role) {
256 self.roles.push(role);
257 }
258 self
259 }
260
261 pub fn with_policy(mut self, policy: SkillPolicy) -> Self {
263 self.policy = policy;
264 self
265 }
266
267 pub fn with_cache_mode(mut self, cache: SkillCacheMode) -> Self {
269 self.policy.cache = cache;
270 self
271 }
272
273 pub fn with_cassette_mode(mut self, cassette: SkillCassetteMode) -> Self {
275 self.policy.cassette = cassette;
276 self
277 }
278
279 pub fn with_idempotent(mut self, idempotent: bool) -> Self {
281 self.policy.idempotent = idempotent;
282 self
283 }
284
285 pub fn with_semantic_key(mut self, semantic_key: impl Into<String>) -> Self {
287 self.policy.semantic_key = Some(semantic_key.into());
288 self
289 }
290
291 pub fn with_privacy(mut self, privacy: SkillPrivacyPolicy) -> Self {
293 self.policy.privacy = privacy;
294 self
295 }
296
297 pub fn value(&self, cx: &mut Cx) -> Result<Value> {
299 cx.factory().opaque(std::sync::Arc::new(self.clone()))
300 }
301
302 pub fn table_value(&self, cx: &mut Cx) -> Result<Value> {
304 let aliases = cx.factory().list(
305 self.aliases
306 .iter()
307 .map(|alias| cx.factory().symbol(alias.clone()))
308 .collect::<Result<Vec<_>>>()?,
309 )?;
310 let roles = cx.factory().list(
311 self.roles
312 .iter()
313 .map(|role| cx.factory().symbol(role.as_symbol()))
314 .collect::<Result<Vec<_>>>()?,
315 )?;
316 let capabilities = cx.factory().list(
317 self.capabilities
318 .iter()
319 .map(|capability| cx.factory().string(capability.as_str().to_owned()))
320 .collect::<Result<Vec<_>>>()?,
321 )?;
322 let transport = cx.factory().table(vec![
323 (
324 Symbol::new("id"),
325 cx.factory().string(self.transport_id.clone())?,
326 ),
327 (
328 Symbol::new("kind"),
329 cx.factory()
330 .symbol(Symbol::new(self.transport_kind.clone()))?,
331 ),
332 (
333 Symbol::new("operation"),
334 cx.factory().string(self.operation.clone())?,
335 ),
336 ])?;
337 let mut policy = vec![
338 (
339 Symbol::new("privacy"),
340 cx.factory().symbol(self.policy.privacy.as_symbol())?,
341 ),
342 (
343 Symbol::new("cache"),
344 cx.factory().symbol(self.policy.cache.as_symbol())?,
345 ),
346 (
347 Symbol::new("cassette"),
348 cx.factory().symbol(self.policy.cassette.as_symbol())?,
349 ),
350 (
351 Symbol::new("idempotent"),
352 cx.factory().bool(self.policy.idempotent)?,
353 ),
354 ];
355 if let Some(semantic_key) = &self.policy.semantic_key {
356 policy.push((
357 Symbol::new("semantic-key"),
358 cx.factory().string(semantic_key.clone())?,
359 ));
360 }
361 let policy = cx.factory().table(policy)?;
362 cx.factory().table(vec![
363 (
364 Symbol::new("kind"),
365 cx.factory().symbol(Symbol::qualified("skill", "card"))?,
366 ),
367 (Symbol::new("id"), cx.factory().string(self.id.clone())?),
368 (
369 Symbol::new("symbol"),
370 cx.factory().symbol(self.symbol.clone())?,
371 ),
372 (Symbol::new("aliases"), aliases),
373 (
374 Symbol::new("origin"),
375 cx.factory().symbol(self.origin.clone())?,
376 ),
377 (
378 Symbol::new("title"),
379 cx.factory().string(self.title.clone())?,
380 ),
381 (
382 Symbol::new("description"),
383 cx.factory().string(self.description.clone())?,
384 ),
385 (Symbol::new("input-shape"), self.input_shape.clone()),
386 (Symbol::new("output-shape"), self.output_shape.clone()),
387 (Symbol::new("roles"), roles),
388 (Symbol::new("capabilities"), capabilities),
389 (Symbol::new("policy"), policy),
390 (Symbol::new("transport"), transport),
391 ])
392 }
393}
394
395impl Object for SkillCard {
396 fn display(&self, _cx: &mut Cx) -> Result<String> {
397 Ok(format!("#<skill-card {}>", self.id))
398 }
399
400 fn as_any(&self) -> &dyn std::any::Any {
401 self
402 }
403}
404
405impl ObjectCompat for SkillCard {
406 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
407 self.to_expr(cx)
408 }
409
410 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
411 self.table_value(cx)
412 }
413}