@deepstrike/core 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/core-darwin-arm64",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "cpu": [
5
5
  "arm64"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/core-darwin-x64",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "cpu": [
5
5
  "x64"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/core-linux-arm64-gnu",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "cpu": [
5
5
  "arm64"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/core-linux-arm64-musl",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "cpu": [
5
5
  "arm64"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/core-linux-x64-gnu",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "cpu": [
5
5
  "x64"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/core-linux-x64-musl",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "cpu": [
5
5
  "x64"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/core-win32-x64-msvc",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "cpu": [
5
5
  "x64"
6
6
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "DeepStrike kernel — pre-built native addon",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -12,18 +12,14 @@
12
12
  "aarch64-apple-darwin",
13
13
  "x86_64-unknown-linux-gnu",
14
14
  "aarch64-unknown-linux-gnu",
15
- "x86_64-unknown-linux-musl",
16
- "aarch64-unknown-linux-musl",
17
15
  "x86_64-pc-windows-msvc"
18
16
  ]
19
17
  },
20
18
  "optionalDependencies": {
21
- "@deepstrike/core-linux-x64-gnu": "0.1.0",
22
- "@deepstrike/core-linux-arm64-gnu": "0.1.0",
23
- "@deepstrike/core-linux-x64-musl": "0.1.0",
24
- "@deepstrike/core-linux-arm64-musl": "0.1.0",
25
- "@deepstrike/core-darwin-x64": "0.1.0",
26
- "@deepstrike/core-darwin-arm64": "0.1.0",
27
- "@deepstrike/core-win32-x64-msvc": "0.1.0"
19
+ "@deepstrike/core-linux-x64-gnu": "0.1.3",
20
+ "@deepstrike/core-linux-arm64-gnu": "0.1.3",
21
+ "@deepstrike/core-darwin-x64": "0.1.3",
22
+ "@deepstrike/core-darwin-arm64": "0.1.3",
23
+ "@deepstrike/core-win32-x64-msvc": "0.1.3"
28
24
  }
29
25
  }
package/src/lib.rs CHANGED
@@ -43,7 +43,10 @@ use compact_str::CompactString;
43
43
 
44
44
  use deepstrike_core::context::manager::ContextManager;
45
45
  use deepstrike_core::context::pressure::PressureAction;
46
+ use deepstrike_core::governance::permission::{PermissionAction, PermissionRule};
46
47
  use deepstrike_core::governance::pipeline::GovernancePipeline as RustGovernancePipeline;
48
+ use deepstrike_core::types::agent::AgentIdentity;
49
+ use deepstrike_core::types::policy::GovernanceVerdict as RustGovernanceVerdict;
47
50
  use deepstrike_core::harness::eval_pipeline::{
48
51
  EvalAction as RustEvalAction, EvalEvent as RustEvalEvent, EvalPolicy as RustEvalPolicy,
49
52
  EvalPipeline as RustEvalPipeline,
@@ -76,11 +79,30 @@ use deepstrike_core::types::task::RuntimeTask as RustRuntimeTask;
76
79
 
77
80
  // ────────────────────────────────────── POD types (plain JS objects) ──────────────────────────────────────
78
81
 
82
+ #[napi(object)]
83
+ #[derive(Clone)]
84
+ pub struct ContentPartObj {
85
+ /// `"text"` | `"image"` | `"audio"` | `"tool_result"`
86
+ pub r#type: String,
87
+ pub text: Option<String>,
88
+ pub url: Option<String>,
89
+ pub data: Option<String>,
90
+ pub media_type: Option<String>,
91
+ pub detail: Option<String>,
92
+ pub call_id: Option<String>,
93
+ pub output: Option<String>,
94
+ pub is_error: Option<bool>,
95
+ }
96
+
79
97
  #[napi(object)]
80
98
  #[derive(Clone)]
81
99
  pub struct Message {
82
100
  pub role: String,
101
+ /// Plain-text content. When `content_parts` is present, this holds only the
102
+ /// concatenated text segments for backward compatibility.
83
103
  pub content: String,
104
+ /// Structured multimodal content parts. When present, takes precedence over `content`.
105
+ pub content_parts: Option<Vec<ContentPartObj>>,
84
106
  pub token_count: Option<u32>,
85
107
  pub tool_calls: Vec<ToolCall>,
86
108
  }
@@ -288,6 +310,51 @@ fn role_to_str(role: Role) -> &'static str {
288
310
  }
289
311
  }
290
312
 
313
+ fn content_part_to_rust(p: ContentPartObj) -> ContentPart {
314
+ match p.r#type.as_str() {
315
+ "image" => ContentPart::Image {
316
+ url: p.url,
317
+ data: p.data,
318
+ media_type: p.media_type,
319
+ detail: p.detail,
320
+ },
321
+ "audio" => ContentPart::Audio {
322
+ data: p.data.unwrap_or_default(),
323
+ media_type: p.media_type.unwrap_or_else(|| "audio/wav".into()),
324
+ },
325
+ "tool_result" => ContentPart::ToolResult {
326
+ call_id: CompactString::new(&p.call_id.unwrap_or_default()),
327
+ output: p.output.unwrap_or_default(),
328
+ is_error: p.is_error.unwrap_or(false),
329
+ },
330
+ _ => ContentPart::Text { text: p.text.unwrap_or_default() },
331
+ }
332
+ }
333
+
334
+ fn content_part_from_rust(p: &ContentPart) -> ContentPartObj {
335
+ match p {
336
+ ContentPart::Text { text } => ContentPartObj {
337
+ r#type: "text".into(), text: Some(text.clone()),
338
+ url: None, data: None, media_type: None, detail: None,
339
+ call_id: None, output: None, is_error: None,
340
+ },
341
+ ContentPart::Image { url, data, media_type, detail } => ContentPartObj {
342
+ r#type: "image".into(), text: None,
343
+ url: url.clone(), data: data.clone(), media_type: media_type.clone(), detail: detail.clone(),
344
+ call_id: None, output: None, is_error: None,
345
+ },
346
+ ContentPart::Audio { data, media_type } => ContentPartObj {
347
+ r#type: "audio".into(), text: None, url: None,
348
+ data: Some(data.clone()), media_type: Some(media_type.clone()), detail: None,
349
+ call_id: None, output: None, is_error: None,
350
+ },
351
+ ContentPart::ToolResult { call_id, output, is_error } => ContentPartObj {
352
+ r#type: "tool_result".into(), text: None, url: None, data: None, media_type: None, detail: None,
353
+ call_id: Some(call_id.to_string()), output: Some(output.clone()), is_error: Some(*is_error),
354
+ },
355
+ }
356
+ }
357
+
291
358
  fn message_to_rust(m: Message) -> Result<RustMessage> {
292
359
  let role = role_str_to_rust(&m.role)?;
293
360
  let tool_calls: Vec<RustToolCall> = m
@@ -295,32 +362,29 @@ fn message_to_rust(m: Message) -> Result<RustMessage> {
295
362
  .into_iter()
296
363
  .map(tool_call_to_rust)
297
364
  .collect::<Result<_>>()?;
298
- Ok(RustMessage {
299
- role,
300
- content: Content::Text(m.content),
301
- tool_calls,
302
- token_count: m.token_count,
303
- })
365
+ let content = match m.content_parts {
366
+ Some(parts) if !parts.is_empty() => Content::Parts(parts.into_iter().map(content_part_to_rust).collect()),
367
+ _ => Content::Text(m.content),
368
+ };
369
+ Ok(RustMessage { role, content, tool_calls, token_count: m.token_count })
304
370
  }
305
371
 
306
372
  fn message_from_rust(m: &RustMessage) -> Message {
307
- let content = match &m.content {
308
- Content::Text(s) => s.clone(),
309
- Content::Parts(parts) => parts
310
- .iter()
311
- .map(|p| match p {
312
- ContentPart::Text { text } => text.clone(),
313
- ContentPart::Image { url } => format!("[image: {url}]"),
314
- ContentPart::ToolResult { call_id, output, .. } => {
315
- format!("[tool_result {call_id}]: {output}")
316
- }
317
- })
318
- .collect::<Vec<_>>()
319
- .join("\n"),
373
+ let (content, content_parts) = match &m.content {
374
+ Content::Text(s) => (s.clone(), None),
375
+ Content::Parts(parts) => {
376
+ let text_only: String = parts.iter().filter_map(|p| match p {
377
+ ContentPart::Text { text } => Some(text.as_str()),
378
+ _ => None,
379
+ }).collect::<Vec<_>>().join("\n");
380
+ let objs: Vec<ContentPartObj> = parts.iter().map(content_part_from_rust).collect();
381
+ (text_only, Some(objs))
382
+ }
320
383
  };
321
384
  Message {
322
385
  role: role_to_str(m.role).to_string(),
323
386
  content,
387
+ content_parts,
324
388
  token_count: m.token_count,
325
389
  tool_calls: m.tool_calls.iter().map(tool_call_from_rust).collect(),
326
390
  }
@@ -683,27 +747,100 @@ impl SignalRouter {
683
747
 
684
748
  // ─────────────────────────────────────────── Governance ───────────────────────────────────────────
685
749
 
750
+ /// JS-friendly governance verdict returned by `Governance.evaluate`.
751
+ #[napi(object)]
752
+ #[derive(Clone)]
753
+ pub struct GovernanceVerdictObj {
754
+ /// `"allow"` | `"deny"` | `"rate_limited"` | `"ask_user"`
755
+ pub kind: String,
756
+ pub reason: Option<String>,
757
+ /// Milliseconds until the tool may be retried. Only set when `kind === "rate_limited"`.
758
+ pub retry_after_ms: Option<f64>,
759
+ }
760
+
761
+ fn verdict_to_js(v: RustGovernanceVerdict) -> GovernanceVerdictObj {
762
+ match v {
763
+ RustGovernanceVerdict::Allow => GovernanceVerdictObj { kind: "allow".into(), reason: None, retry_after_ms: None },
764
+ RustGovernanceVerdict::Deny { reason, .. } => GovernanceVerdictObj { kind: "deny".into(), reason: Some(reason), retry_after_ms: None },
765
+ RustGovernanceVerdict::RateLimited { retry_after_ms } => GovernanceVerdictObj { kind: "rate_limited".into(), reason: None, retry_after_ms: Some(retry_after_ms as f64) },
766
+ RustGovernanceVerdict::AskUser { reason } => GovernanceVerdictObj { kind: "ask_user".into(), reason: Some(reason), retry_after_ms: None },
767
+ }
768
+ }
769
+
686
770
  #[napi]
687
771
  pub struct Governance {
688
772
  inner: RustGovernancePipeline,
773
+ agent_id: String,
774
+ session_id: String,
689
775
  }
690
776
 
691
777
  #[napi]
692
778
  impl Governance {
779
+ /// Create a governance pipeline.
780
+ /// `defaultAction` controls the fallback when no rule matches: `"allow"` (default) or `"deny"`.
693
781
  #[napi(constructor)]
694
- pub fn new() -> Self {
695
- Self { inner: RustGovernancePipeline::default() }
782
+ pub fn new(default_action: Option<String>) -> Self {
783
+ let action = match default_action.as_deref() {
784
+ Some("deny") => PermissionAction::Deny,
785
+ Some("ask_user") => PermissionAction::AskUser,
786
+ _ => PermissionAction::Allow,
787
+ };
788
+ Self {
789
+ inner: RustGovernancePipeline::new(action),
790
+ agent_id: "anonymous".into(),
791
+ session_id: "".into(),
792
+ }
793
+ }
794
+
795
+ /// Set the agent identity used in governance audit logs.
796
+ #[napi]
797
+ pub fn set_identity(&mut self, agent_id: String, session_id: String) {
798
+ self.agent_id = agent_id;
799
+ self.session_id = session_id;
696
800
  }
697
801
 
802
+ /// Add a permission rule. `pattern` supports globs: `"db.*"`, `"*.delete"`, `"*"`, or exact names.
803
+ /// `action`: `"allow"` | `"deny"` | `"ask_user"`.
804
+ /// Rules are evaluated in insertion order; first match wins.
805
+ #[napi]
806
+ pub fn add_permission_rule(&mut self, pattern: String, action: String) {
807
+ let perm_action = match action.as_str() {
808
+ "deny" => PermissionAction::Deny,
809
+ "ask_user" => PermissionAction::AskUser,
810
+ _ => PermissionAction::Allow,
811
+ };
812
+ self.inner.permission.add_rule(PermissionRule {
813
+ tool_pattern: pattern.into(),
814
+ action: perm_action,
815
+ });
816
+ }
817
+
818
+ /// Hard-block a tool name (veto stage — cannot be overridden by permission rules).
698
819
  #[napi]
699
820
  pub fn block_tool(&mut self, name: String) {
700
821
  self.inner.veto.block_tool(name);
701
822
  }
702
823
 
824
+ /// Advance the internal clock used by rate limiting and audit.
703
825
  #[napi]
704
826
  pub fn set_time(&mut self, now_ms: BigInt) {
705
827
  self.inner.set_time(now_ms.get_u64().1);
706
828
  }
829
+
830
+ /// Evaluate a tool call through the full pipeline (Permission → Veto → RateLimit → Constraint → Audit).
831
+ /// `argsJson`: JSON-encoded tool arguments string.
832
+ #[napi]
833
+ pub fn evaluate(&mut self, tool_name: String, args_json: String) -> Result<GovernanceVerdictObj> {
834
+ let args: serde_json::Value = serde_json::from_str(&args_json)
835
+ .unwrap_or(serde_json::Value::Null);
836
+ let call = RustToolCall {
837
+ id: compact_str::CompactString::new(""),
838
+ name: compact_str::CompactString::new(&tool_name),
839
+ arguments: args,
840
+ };
841
+ let caller = AgentIdentity::new(self.agent_id.as_str(), self.session_id.as_str());
842
+ Ok(verdict_to_js(self.inner.evaluate(&call, &caller)))
843
+ }
707
844
  }
708
845
 
709
846
  // ──────────────────────────────── Dream / idle-pipeline POD types ────────────────────────────────
Binary file