luaskills 0.4.5

LuaSkills core runtime library for loading, invoking, and managing Lua skill packages.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
use crate::download::manager::{DownloadProgress, DownloadProgressCallback};
use crate::runtime::entry::RuntimeEntryDescriptor;
use crate::skill::manager::{SkillLifecycleAction, SkillManagementAuthority, SkillOperationPlane};
use crate::skill::source::SkillInstallSourceType;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, OnceLock};
use std::time::{SystemTime, UNIX_EPOCH};

/// Callback type used by hosts to receive runtime skill-lifecycle events.
/// 宿主用于接收运行时技能生命周期事件的回调类型。
pub type RuntimeSkillLifecycleCallback = Arc<dyn Fn(&RuntimeSkillLifecycleEvent) + Send + Sync>;

/// Callback type used by hosts to receive fine-grained skill-operation progress events.
/// 宿主用于接收细粒度技能操作进度事件的回调类型。
pub type RuntimeSkillOperationProgressCallback =
    Arc<dyn Fn(&RuntimeSkillOperationProgressEvent) + Send + Sync>;

/// Callback type used by hosts to receive runtime entry-registry change events.
/// 宿主用于接收运行时入口注册表变化事件的回调类型。
pub type RuntimeEntryRegistryCallback = Arc<dyn Fn(&RuntimeEntryRegistryDelta) + Send + Sync>;

/// Callback type used by hosts to handle one Lua-triggered runtime skill-management request.
/// 宿主用于处理单个由 Lua 触发的运行时技能管理请求的回调类型。
pub type RuntimeSkillManagementCallback =
    Arc<dyn Fn(&RuntimeSkillManagementRequest) -> Result<Value, String> + Send + Sync>;

/// Callback type used by hosts to handle one Lua-triggered host-tool bridge request.
/// 宿主用于处理单个由 Lua 触发的宿主工具桥接请求的回调类型。
pub type RuntimeHostToolCallback =
    Arc<dyn Fn(&RuntimeHostToolRequest) -> Result<Value, String> + Send + Sync>;

/// Callback type used by hosts to handle one standard model embedding request.
/// 宿主用于处理单个标准模型 embedding 请求的回调类型。
pub type RuntimeModelEmbedCallback = Arc<
    dyn Fn(&RuntimeModelEmbedRequest) -> Result<RuntimeModelEmbedResponse, RuntimeModelError>
        + Send
        + Sync,
>;

/// Callback type used by hosts to handle one standard non-streaming LLM request.
/// 宿主用于处理单个标准非流式 LLM 请求的回调类型。
pub type RuntimeModelLlmCallback = Arc<
    dyn Fn(&RuntimeModelLlmRequest) -> Result<RuntimeModelLlmResponse, RuntimeModelError>
        + Send
        + Sync,
>;

/// Structured host-tool bridge actions that Lua may request through `vulcan.host.*`.
/// Lua 可以通过 `vulcan.host.*` 请求的结构化宿主工具桥接动作集合。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeHostToolAction {
    /// Request the current host-visible tool list.
    /// 请求当前对宿主可见的工具列表。
    List,
    /// Request whether one host tool exists.
    /// 请求判断某个宿主工具是否存在。
    Has,
    /// Request one host-tool invocation.
    /// 请求执行一次宿主工具调用。
    Call,
}

/// Structured Lua-triggered host-tool bridge request forwarded to the host.
/// 转发给宿主的结构化 Lua 触发宿主工具桥接请求。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RuntimeHostToolRequest {
    /// Requested host-tool bridge action kind.
    /// 请求的宿主工具桥接动作类型。
    pub action: RuntimeHostToolAction,
    /// Optional host tool name, required for `has` and `call`.
    /// 可选宿主工具名称,`has` 与 `call` 动作必填。
    pub tool_name: Option<String>,
    /// JSON payload converted from the Lua table argument.
    /// 从 Lua table 参数转换得到的 JSON 载荷。
    pub args: Value,
}

/// Caller context automatically attached to one standard model capability request.
/// 自动附加到单个标准模型能力请求上的调用方上下文。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeModelCaller {
    /// Skill identifier that owns the currently executing Lua entry when available.
    /// 当前执行 Lua 入口所属的 skill 标识符(如果存在)。
    pub skill_id: Option<String>,
    /// Local entry name declared by the owning skill when available.
    /// 所属 skill 声明的局部入口名称(如果存在)。
    pub entry_name: Option<String>,
    /// Canonical runtime tool name currently executing when available.
    /// 当前正在执行的 canonical 运行时工具名称(如果存在)。
    pub canonical_tool_name: Option<String>,
    /// Runtime root name that owns the current skill when available.
    /// 拥有当前 skill 的运行时根名称(如果存在)。
    pub root_name: Option<String>,
    /// Host-visible absolute skill directory when available.
    /// 对宿主可见的绝对 skill 目录(如果存在)。
    pub skill_dir: Option<String>,
    /// Host-provided client name from the current request context when available.
    /// 当前请求上下文中的宿主提供客户端名称(如果存在)。
    pub client_name: Option<String>,
    /// Host-provided request identifier from the current request context when available.
    /// 当前请求上下文中的宿主提供请求标识符(如果存在)。
    pub request_id: Option<String>,
}

/// Standard embedding request passed from LuaSkills into the host callback.
/// 从 LuaSkills 传递给宿主回调的标准 embedding 请求。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RuntimeModelEmbedRequest {
    /// Single input text that Lua requested to embed.
    /// Lua 请求进行 embedding 的单条输入文本。
    pub text: String,
    /// Caller context captured from the active Lua runtime scope.
    /// 从当前 Lua 运行时作用域捕获的调用方上下文。
    pub caller: RuntimeModelCaller,
}

/// Standard embedding response returned by the host callback.
/// 宿主回调返回的标准 embedding 响应。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RuntimeModelEmbedResponse {
    /// Embedding vector returned by the host-managed model provider.
    /// 宿主管理的模型 provider 返回的 embedding 向量。
    pub vector: Vec<f32>,
    /// Number of vector dimensions reported by the host.
    /// 宿主报告的向量维度数量。
    pub dimensions: usize,
    /// Optional token usage metadata reported by the host.
    /// 宿主报告的可选 token 用量元数据。
    pub usage: Option<RuntimeModelUsage>,
}

/// Standard non-streaming LLM request passed from LuaSkills into the host callback.
/// 从 LuaSkills 传递给宿主回调的标准非流式 LLM 请求。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RuntimeModelLlmRequest {
    /// System instruction text supplied by Lua.
    /// Lua 提供的 system 指令文本。
    pub system: String,
    /// User message text supplied by Lua.
    /// Lua 提供的 user 消息文本。
    pub user: String,
    /// Caller context captured from the active Lua runtime scope.
    /// 从当前 Lua 运行时作用域捕获的调用方上下文。
    pub caller: RuntimeModelCaller,
}

/// Standard non-streaming LLM response returned by the host callback.
/// 宿主回调返回的标准非流式 LLM 响应。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeModelLlmResponse {
    /// Assistant text returned by the host-managed model provider.
    /// 宿主管理的模型 provider 返回的 assistant 文本。
    pub assistant: String,
    /// Optional token usage metadata reported by the host.
    /// 宿主报告的可选 token 用量元数据。
    pub usage: Option<RuntimeModelUsage>,
}

/// Optional token usage metadata returned by a host-managed model provider.
/// 宿主管理的模型 provider 返回的可选 token 用量元数据。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeModelUsage {
    /// Optional input token count reported by the host.
    /// 宿主报告的可选输入 token 数量。
    pub input_tokens: Option<u64>,
    /// Optional output token count reported by the host.
    /// 宿主报告的可选输出 token 数量。
    pub output_tokens: Option<u64>,
}

/// Structured model error returned by LuaSkills or the host model callback.
/// LuaSkills 或宿主模型回调返回的结构化模型错误。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeModelError {
    /// Stable LuaSkills-level model error code.
    /// 稳定的 LuaSkills 级模型错误码。
    pub code: RuntimeModelErrorCode,
    /// Human-readable error summary.
    /// 人类可读的错误摘要。
    pub message: String,
    /// Optional raw provider error text after host-side redaction.
    /// 宿主侧脱敏后的可选 provider 原始错误文本。
    pub provider_message: Option<String>,
    /// Optional raw provider error code.
    /// 可选 provider 原始错误码。
    pub provider_code: Option<String>,
    /// Optional provider status such as an HTTP status code.
    /// 可选 provider 状态,例如 HTTP 状态码。
    pub provider_status: Option<u16>,
}

/// Stable model error codes exposed through the Lua result envelope.
/// 通过 Lua 返回包络暴露的稳定模型错误码。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeModelErrorCode {
    /// The requested model capability has no registered host callback.
    /// 请求的模型能力没有注册宿主回调。
    ModelUnavailable,
    /// Lua supplied an invalid argument count, type, or empty string.
    /// Lua 提供了非法参数数量、类型或空字符串。
    InvalidArgument,
    /// The host model provider returned an error.
    /// 宿主模型 provider 返回了错误。
    ProviderError,
    /// The host model invocation timed out.
    /// 宿主模型调用超时。
    Timeout,
    /// The host rejected the invocation because a budget or limit was exceeded.
    /// 宿主因预算或限制超额拒绝了本次调用。
    BudgetExceeded,
    /// LuaSkills or the host bridge hit an internal failure.
    /// LuaSkills 或宿主桥接遇到内部故障。
    InternalError,
}

impl RuntimeModelErrorCode {
    /// Return the stable snake_case code string used by Lua result envelopes.
    /// 返回 Lua 返回包络使用的稳定 snake_case 错误码字符串。
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::ModelUnavailable => "model_unavailable",
            Self::InvalidArgument => "invalid_argument",
            Self::ProviderError => "provider_error",
            Self::Timeout => "timeout",
            Self::BudgetExceeded => "budget_exceeded",
            Self::InternalError => "internal_error",
        }
    }

    /// Convert a stable snake_case model error code into the internal enum.
    /// 将稳定的 snake_case 模型错误码转换为内部枚举。
    pub fn from_code_str(value: &str) -> Self {
        match value {
            "model_unavailable" => Self::ModelUnavailable,
            "invalid_argument" => Self::InvalidArgument,
            "provider_error" => Self::ProviderError,
            "timeout" => Self::Timeout,
            "budget_exceeded" => Self::BudgetExceeded,
            _ => Self::InternalError,
        }
    }
}

/// Structured management actions that one Lua-exposed runtime bridge may request from the host.
/// Lua 暴露的运行时桥接可能向宿主请求的结构化管理动作集合。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeSkillManagementAction {
    /// Request one managed install operation.
    /// 请求执行一次受管安装操作。
    Install,
    /// Request one managed update operation.
    /// 请求执行一次受管更新操作。
    Update,
    /// Request one uninstall operation.
    /// 请求执行一次卸载操作。
    Uninstall,
    /// Request one enable operation.
    /// 请求执行一次启用操作。
    Enable,
    /// Request one disable operation.
    /// 请求执行一次停用操作。
    Disable,
}

/// Structured Lua-triggered skill-management request forwarded to the host.
/// 转发给宿主的结构化 Lua 触发技能管理请求。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RuntimeSkillManagementRequest {
    /// Requested management action kind.
    /// 请求的管理动作类型。
    pub action: RuntimeSkillManagementAction,
    /// Host-injected authority level for this ordinary runtime bridge request.
    /// 当前普通运行时桥接请求的宿主注入权限等级。
    pub authority: SkillManagementAuthority,
    /// Arbitrary JSON payload supplied by the Lua caller.
    /// 由 Lua 调用方提供的任意 JSON 载荷。
    pub input: Value,
}

/// Structured lifecycle event emitted after one skill-management operation is evaluated.
/// 在评估一次技能管理操作后发出的结构化生命周期事件。
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct RuntimeSkillLifecycleEvent {
    /// Operation plane that triggered the lifecycle event.
    /// 触发生命周期事件的操作平面。
    pub plane: SkillOperationPlane,
    /// Lifecycle action represented by the current event.
    /// 当前事件所表示的生命周期动作。
    pub action: SkillLifecycleAction,
    /// Skill identifier targeted by the current lifecycle operation.
    /// 当前生命周期操作对应的技能标识符。
    pub skill_id: String,
    /// Optional named skill root that owns the effective target skill instance.
    /// 拥有当前生效目标技能实例的可选命名技能根。
    pub root_name: Option<String>,
    /// Optional physical skill directory of the effective target skill instance.
    /// 当前生效目标技能实例的可选物理技能目录。
    pub skill_dir: Option<String>,
    /// High-level event status such as completed, failed, or blocked.
    /// 当前事件的高层状态,例如 completed、failed 或 blocked。
    pub status: String,
    /// Optional human-readable explanation of the current lifecycle outcome.
    /// 当前生命周期结果的可选人类可读说明。
    pub message: Option<String>,
}

/// Fine-grained progress event emitted during one skill install or update operation.
/// 单次技能安装或更新操作过程中发出的细粒度进度事件。
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct RuntimeSkillOperationProgressEvent {
    /// Stable operation id shared by all events from the same lifecycle operation.
    /// 同一生命周期操作全部事件共享的稳定操作标识符。
    pub operation_id: String,
    /// Monotonic sequence number within the current operation.
    /// 当前操作内单调递增的序号。
    pub sequence: u64,
    /// Operation plane that owns the current lifecycle operation.
    /// 当前生命周期操作所属的操作平面。
    pub plane: SkillOperationPlane,
    /// Lifecycle action represented by the current operation.
    /// 当前操作所表示的生命周期动作。
    pub action: SkillLifecycleAction,
    /// Machine-readable phase name such as `resolving_source` or `downloading_archive`.
    /// 机器可读阶段名称,例如 `resolving_source` 或 `downloading_archive`。
    pub phase: String,
    /// Machine-readable status such as `started`, `progress`, `completed`, or `failed`.
    /// 机器可读状态,例如 `started`、`progress`、`completed` 或 `failed`。
    pub status: String,
    /// Optional skill id targeted by the operation.
    /// 当前操作目标的可选技能标识符。
    pub skill_id: Option<String>,
    /// Optional named skill root targeted by the operation.
    /// 当前操作目标的可选命名技能根。
    pub root_name: Option<String>,
    /// Optional source type involved in the current phase.
    /// 当前阶段涉及的可选来源类型。
    pub source_type: Option<SkillInstallSourceType>,
    /// Optional source locator involved in the current phase.
    /// 当前阶段涉及的可选来源定位值。
    pub source_locator: Option<String>,
    /// Optional completed byte count for download phases.
    /// 下载阶段的可选已完成字节数。
    pub bytes_done: Option<u64>,
    /// Optional total byte count for download phases.
    /// 下载阶段的可选总字节数。
    pub bytes_total: Option<u64>,
    /// Optional percentage for determinate progress phases.
    /// 确定性进度阶段的可选百分比。
    pub percent: Option<f64>,
    /// Optional human-readable progress message.
    /// 可选的人类可读进度消息。
    pub message: Option<String>,
}

/// Lightweight helper that preserves operation identity and progress ordering.
/// 用于保持操作身份与进度顺序的轻量辅助器。
#[derive(Clone)]
pub(crate) struct RuntimeSkillOperationProgressEmitter {
    /// Stable operation id emitted on every progress event.
    /// 每条进度事件都会携带的稳定操作标识符。
    operation_id: String,
    /// Shared monotonic sequence counter for this operation.
    /// 当前操作共享的单调序号计数器。
    sequence: Arc<AtomicU64>,
    /// Operation plane emitted on every progress event.
    /// 每条进度事件都会携带的操作平面。
    plane: SkillOperationPlane,
    /// Lifecycle action emitted on every progress event.
    /// 每条进度事件都会携带的生命周期动作。
    action: SkillLifecycleAction,
    /// Optional root name emitted on every progress event.
    /// 每条进度事件都会携带的可选根名称。
    root_name: Option<String>,
    /// Optional default skill id emitted when a phase does not override it.
    /// 阶段没有覆盖时使用的可选默认技能标识符。
    skill_id: Option<String>,
}

impl RuntimeSkillOperationProgressEmitter {
    /// Create one progress emitter for a single lifecycle operation.
    /// 为单次生命周期操作创建一个进度发射器。
    pub(crate) fn new(
        plane: SkillOperationPlane,
        action: SkillLifecycleAction,
        root_name: Option<String>,
        skill_id: Option<String>,
    ) -> Self {
        Self {
            operation_id: build_skill_operation_id(action, skill_id.as_deref()),
            sequence: Arc::new(AtomicU64::new(0)),
            plane,
            action,
            root_name,
            skill_id,
        }
    }

    /// Emit one phase-level progress event.
    /// 发出一条阶段级进度事件。
    pub(crate) fn emit(&self, phase: &str, status: &str, message: Option<String>) {
        self.emit_detail(RuntimeSkillOperationProgressDetail {
            phase,
            status,
            skill_id: None,
            source_type: None,
            source_locator: None,
            bytes_done: None,
            bytes_total: None,
            message,
        });
    }

    /// Emit one detailed progress event with optional source and byte metadata.
    /// 发出一条携带可选来源与字节元数据的详细进度事件。
    pub(crate) fn emit_detail(&self, detail: RuntimeSkillOperationProgressDetail<'_>) {
        let bytes_total = detail.bytes_total;
        let percent = match (detail.bytes_done, bytes_total) {
            (Some(done), Some(total)) if total > 0 => {
                Some(((done as f64 / total as f64) * 100.0).min(100.0))
            }
            _ => None,
        };
        emit_skill_operation_progress_event(&RuntimeSkillOperationProgressEvent {
            operation_id: self.operation_id.clone(),
            sequence: self.sequence.fetch_add(1, Ordering::SeqCst) + 1,
            plane: self.plane,
            action: self.action,
            phase: detail.phase.to_string(),
            status: detail.status.to_string(),
            skill_id: detail
                .skill_id
                .map(ToOwned::to_owned)
                .or_else(|| self.skill_id.clone()),
            root_name: self.root_name.clone(),
            source_type: detail.source_type,
            source_locator: detail.source_locator.map(ToOwned::to_owned),
            bytes_done: detail.bytes_done,
            bytes_total,
            percent,
            message: detail.message,
        });
    }

    /// Build a download progress callback bound to this lifecycle operation.
    /// 构造绑定到当前生命周期操作的下载进度回调。
    pub(crate) fn download_callback(
        &self,
        source_type: SkillInstallSourceType,
        skill_id: String,
    ) -> DownloadProgressCallback {
        let emitter = self.clone();
        Arc::new(move |progress: &DownloadProgress| {
            emitter.emit_detail(RuntimeSkillOperationProgressDetail {
                phase: "downloading_archive",
                status: if progress.cached {
                    "cached"
                } else {
                    "progress"
                },
                skill_id: Some(skill_id.as_str()),
                source_type: Some(source_type),
                source_locator: Some(progress.source_locator.as_str()),
                bytes_done: Some(progress.bytes_done),
                bytes_total: progress.bytes_total,
                message: if progress.cached {
                    Some("download cache hit".to_string())
                } else {
                    None
                },
            });
        })
    }
}

/// Borrowed detail object used to build one progress event without a long parameter list.
/// 用于构造单条进度事件并避免冗长参数列表的借用详情对象。
pub(crate) struct RuntimeSkillOperationProgressDetail<'a> {
    /// Machine-readable progress phase.
    /// 机器可读进度阶段。
    pub phase: &'a str,
    /// Machine-readable progress status.
    /// 机器可读进度状态。
    pub status: &'a str,
    /// Optional phase-specific skill id.
    /// 阶段特定的可选技能标识符。
    pub skill_id: Option<&'a str>,
    /// Optional phase-specific source type.
    /// 阶段特定的可选来源类型。
    pub source_type: Option<SkillInstallSourceType>,
    /// Optional phase-specific source locator.
    /// 阶段特定的可选来源定位值。
    pub source_locator: Option<&'a str>,
    /// Optional downloaded byte count.
    /// 可选已下载字节数。
    pub bytes_done: Option<u64>,
    /// Optional total byte count.
    /// 可选总字节数。
    pub bytes_total: Option<u64>,
    /// Optional human-readable message.
    /// 可选人类可读消息。
    pub message: Option<String>,
}

/// Structured entry-registry delta emitted when one reload changes exposed runtime entries.
/// 当一次重载改变已暴露运行时入口时发出的结构化入口注册表差异。
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct RuntimeEntryRegistryDelta {
    /// Newly added runtime entries after the latest reload.
    /// 最近一次重载后新增的运行时入口。
    pub added_entries: Vec<RuntimeEntryDescriptor>,
    /// Canonical names removed after the latest reload.
    /// 最近一次重载后移除的 canonical 入口名称。
    pub removed_entry_names: Vec<String>,
    /// Existing canonical entries whose structure changed after the latest reload.
    /// 最近一次重载后结构发生变化的既有 canonical 入口。
    pub updated_entries: Vec<RuntimeEntryDescriptor>,
}

/// Install or clear the process-wide skill-lifecycle callback used by the host.
/// 安装或清理供宿主使用的进程级技能生命周期回调。
pub fn set_skill_lifecycle_callback(callback: Option<RuntimeSkillLifecycleCallback>) {
    let registry = skill_lifecycle_callback_registry();
    let mut guard = registry.lock().unwrap();
    *guard = callback;
}

/// Install or clear the process-wide skill-operation progress callback used by the host.
/// 安装或清理供宿主使用的进程级技能操作进度回调。
pub fn set_skill_operation_progress_callback(
    callback: Option<RuntimeSkillOperationProgressCallback>,
) {
    let registry = skill_operation_progress_callback_registry();
    let mut guard = registry.lock().unwrap();
    *guard = callback;
}

/// Install or clear the process-wide entry-registry callback used by the host.
/// 安装或清理供宿主使用的进程级入口注册表回调。
pub fn set_entry_registry_callback(callback: Option<RuntimeEntryRegistryCallback>) {
    let registry = entry_registry_callback_registry();
    let mut guard = registry.lock().unwrap();
    *guard = callback;
}

/// Install or clear the process-wide Lua-triggered skill-management callback used by the host.
/// 安装或清理由宿主使用的进程级 Lua 触发技能管理回调。
pub fn set_skill_management_callback(callback: Option<RuntimeSkillManagementCallback>) {
    let registry = skill_management_callback_registry();
    let mut guard = registry.lock().unwrap();
    *guard = callback;
}

/// Install or clear the process-wide Lua-triggered host-tool callback used by the host.
/// 安装或清理由宿主使用的进程级 Lua 触发宿主工具回调。
pub fn set_host_tool_callback(callback: Option<RuntimeHostToolCallback>) {
    let registry = host_tool_callback_registry();
    let mut guard = registry.lock().unwrap();
    *guard = callback;
}

/// Install or clear the process-wide standard model embedding callback used by the host.
/// 安装或清理由宿主使用的进程级标准模型 embedding 回调。
pub fn set_model_embed_callback(callback: Option<RuntimeModelEmbedCallback>) {
    let registry = model_embed_callback_registry();
    let mut guard = registry.lock().unwrap();
    *guard = callback;
}

/// Install or clear the process-wide standard non-streaming LLM callback used by the host.
/// 安装或清理由宿主使用的进程级标准非流式 LLM 回调。
pub fn set_model_llm_callback(callback: Option<RuntimeModelLlmCallback>) {
    let registry = model_llm_callback_registry();
    let mut guard = registry.lock().unwrap();
    *guard = callback;
}

/// Guard one process-wide model callback test and clear global callback state on drop.
/// 保护单个进程级模型回调测试,并在释放时清理全局回调状态。
#[cfg(test)]
pub(crate) struct RuntimeModelCallbackTestGuard {
    /// Hold the process-wide mutex guard until the current test finishes.
    /// 持有进程级互斥锁直到当前测试结束。
    _guard: std::sync::MutexGuard<'static, ()>,
}

#[cfg(test)]
impl Drop for RuntimeModelCallbackTestGuard {
    /// Clear global model callbacks when one guarded test finishes.
    /// 当受保护测试结束时清理全局模型回调。
    fn drop(&mut self) {
        set_model_embed_callback(None);
        set_model_llm_callback(None);
    }
}

/// Acquire the process-wide model callback test guard.
/// 获取进程级模型回调测试保护锁。
#[cfg(test)]
pub(crate) fn runtime_model_callback_test_guard() -> RuntimeModelCallbackTestGuard {
    static GUARD: OnceLock<Mutex<()>> = OnceLock::new();
    let guard = GUARD
        .get_or_init(|| Mutex::new(()))
        .lock()
        .expect("lock model callback test guard");
    set_model_embed_callback(None);
    set_model_llm_callback(None);
    RuntimeModelCallbackTestGuard { _guard: guard }
}

/// Emit one skill-lifecycle event to the currently registered host callback when it exists.
/// 当宿主已注册回调时向其发送一条技能生命周期事件。
pub(crate) fn emit_skill_lifecycle_event(event: &RuntimeSkillLifecycleEvent) {
    let registry = skill_lifecycle_callback_registry();
    let callback = {
        let guard = registry.lock().unwrap();
        guard.clone()
    };
    if let Some(callback) = callback {
        callback(event);
    }
}

/// Emit one skill-operation progress event to the currently registered host callback when it exists.
/// 当宿主已注册回调时向其发送一条技能操作进度事件。
pub(crate) fn emit_skill_operation_progress_event(event: &RuntimeSkillOperationProgressEvent) {
    let registry = skill_operation_progress_callback_registry();
    let callback = {
        let guard = registry.lock().unwrap();
        guard.clone()
    };
    if let Some(callback) = callback {
        callback(event);
    }
}

/// Emit one entry-registry delta to the currently registered host callback when it exists.
/// 当宿主已注册回调时向其发送一条入口注册表差异事件。
pub(crate) fn emit_entry_registry_delta(delta: &RuntimeEntryRegistryDelta) {
    let registry = entry_registry_callback_registry();
    let callback = {
        let guard = registry.lock().unwrap();
        guard.clone()
    };
    if let Some(callback) = callback {
        callback(delta);
    }
}

/// Dispatch one Lua-triggered skill-management request into the currently registered host callback.
/// 将单个 Lua 触发的技能管理请求分发给当前已注册的宿主回调。
pub(crate) fn dispatch_skill_management_request(
    request: &RuntimeSkillManagementRequest,
) -> Result<Value, String> {
    let registry = skill_management_callback_registry();
    let callback = {
        let guard = registry
            .lock()
            .map_err(|_| "Skill management callback registry lock poisoned".to_string())?;
        guard.clone()
    };
    let callback = callback.ok_or_else(|| {
        "Runtime skill management bridge is enabled but no host callback is registered".to_string()
    })?;
    callback(request)
}

/// Dispatch one Lua-triggered host-tool request into the currently registered host callback.
/// 将单个 Lua 触发的宿主工具请求分发给当前已注册的宿主回调。
pub(crate) fn dispatch_host_tool_request(
    request: &RuntimeHostToolRequest,
) -> Result<Value, String> {
    let registry = host_tool_callback_registry();
    let callback = {
        let guard = registry
            .lock()
            .map_err(|_| "Host tool callback registry lock poisoned".to_string())?;
        guard.clone()
    };
    let callback = callback.ok_or_else(|| {
        "Host tool bridge is enabled but no host callback is registered".to_string()
    })?;
    callback(request)
}

/// Build an internal model bridge error for registry or dispatch failures.
/// 为注册表或分发故障构造内部模型桥接错误。
fn model_internal_error(message: impl Into<String>) -> RuntimeModelError {
    RuntimeModelError {
        code: RuntimeModelErrorCode::InternalError,
        message: message.into(),
        provider_message: None,
        provider_code: None,
        provider_status: None,
    }
}

/// Build a model unavailable error for a missing capability callback.
/// 为缺失能力回调构造模型不可用错误。
fn model_unavailable_error(message: impl Into<String>) -> RuntimeModelError {
    RuntimeModelError {
        code: RuntimeModelErrorCode::ModelUnavailable,
        message: message.into(),
        provider_message: None,
        provider_code: None,
        provider_status: None,
    }
}

/// Dispatch one standard embedding request into the currently registered host callback.
/// 将单个标准 embedding 请求分发给当前已注册的宿主回调。
pub(crate) fn dispatch_model_embed_request(
    request: &RuntimeModelEmbedRequest,
) -> Result<RuntimeModelEmbedResponse, RuntimeModelError> {
    let registry = model_embed_callback_registry();
    let callback = {
        let guard = registry
            .lock()
            .map_err(|_| model_internal_error("Model embed callback registry lock poisoned"))?;
        guard.clone()
    };
    let callback =
        callback.ok_or_else(|| model_unavailable_error("embedding callback is not registered"))?;
    callback(request)
}

/// Dispatch one standard non-streaming LLM request into the currently registered host callback.
/// 将单个标准非流式 LLM 请求分发给当前已注册的宿主回调。
pub(crate) fn dispatch_model_llm_request(
    request: &RuntimeModelLlmRequest,
) -> Result<RuntimeModelLlmResponse, RuntimeModelError> {
    let registry = model_llm_callback_registry();
    let callback = {
        let guard = registry
            .lock()
            .map_err(|_| model_internal_error("Model llm callback registry lock poisoned"))?;
        guard.clone()
    };
    let callback =
        callback.ok_or_else(|| model_unavailable_error("llm callback is not registered"))?;
    callback(request)
}

/// Return whether one host callback is currently registered for runtime skill-management dispatch.
/// 返回当前是否已为运行时技能管理分发注册宿主回调。
pub(crate) fn try_has_skill_management_callback() -> Result<bool, String> {
    let registry = skill_management_callback_registry();
    let guard = registry
        .lock()
        .map_err(|_| "Skill management callback registry lock poisoned".to_string())?;
    Ok(guard.is_some())
}

/// Return whether one host callback is currently registered for host-tool dispatch.
/// 返回当前是否已为宿主工具分发注册宿主回调。
pub(crate) fn try_has_host_tool_callback() -> Result<bool, String> {
    let registry = host_tool_callback_registry();
    let guard = registry
        .lock()
        .map_err(|_| "Host tool callback registry lock poisoned".to_string())?;
    Ok(guard.is_some())
}

/// Return whether one host callback is currently registered for standard embedding dispatch.
/// 返回当前是否已为标准 embedding 分发注册宿主回调。
pub(crate) fn try_has_model_embed_callback() -> Result<bool, String> {
    let registry = model_embed_callback_registry();
    let guard = registry
        .lock()
        .map_err(|_| "Model embed callback registry lock poisoned".to_string())?;
    Ok(guard.is_some())
}

/// Return whether one host callback is currently registered for standard LLM dispatch.
/// 返回当前是否已为标准 LLM 分发注册宿主回调。
pub(crate) fn try_has_model_llm_callback() -> Result<bool, String> {
    let registry = model_llm_callback_registry();
    let guard = registry
        .lock()
        .map_err(|_| "Model llm callback registry lock poisoned".to_string())?;
    Ok(guard.is_some())
}

/// Build one host-visible skill operation id from action, skill id, and current time.
/// 根据动作、技能标识符与当前时间构造一个宿主可见的技能操作标识符。
fn build_skill_operation_id(action: SkillLifecycleAction, skill_id: Option<&str>) -> String {
    let action_name = match action {
        SkillLifecycleAction::Install => "install",
        SkillLifecycleAction::Update => "update",
        SkillLifecycleAction::Reload => "reload",
        SkillLifecycleAction::Uninstall => "uninstall",
        SkillLifecycleAction::Enable => "enable",
        SkillLifecycleAction::Disable => "disable",
    };
    let skill_fragment = skill_id
        .map(|value| {
            value
                .chars()
                .map(|ch| match ch {
                    'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => ch,
                    _ => '-',
                })
                .collect::<String>()
        })
        .filter(|value| !value.is_empty())
        .unwrap_or_else(|| "unknown".to_string());
    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis();
    format!("skill-{}-{}-{}", action_name, skill_fragment, timestamp)
}

/// Return the process-wide lifecycle callback storage.
/// 返回进程级生命周期回调存储。
fn skill_lifecycle_callback_registry() -> &'static Mutex<Option<RuntimeSkillLifecycleCallback>> {
    static REGISTRY: OnceLock<Mutex<Option<RuntimeSkillLifecycleCallback>>> = OnceLock::new();
    REGISTRY.get_or_init(|| Mutex::new(None))
}

/// Return the process-wide skill-operation progress callback storage.
/// 返回进程级技能操作进度回调存储。
fn skill_operation_progress_callback_registry()
-> &'static Mutex<Option<RuntimeSkillOperationProgressCallback>> {
    static REGISTRY: OnceLock<Mutex<Option<RuntimeSkillOperationProgressCallback>>> =
        OnceLock::new();
    REGISTRY.get_or_init(|| Mutex::new(None))
}

/// Return the process-wide entry-registry callback storage.
/// 返回进程级入口注册表回调存储。
fn entry_registry_callback_registry() -> &'static Mutex<Option<RuntimeEntryRegistryCallback>> {
    static REGISTRY: OnceLock<Mutex<Option<RuntimeEntryRegistryCallback>>> = OnceLock::new();
    REGISTRY.get_or_init(|| Mutex::new(None))
}

/// Return the process-wide skill-management callback storage.
/// 返回进程级技能管理回调存储。
fn skill_management_callback_registry() -> &'static Mutex<Option<RuntimeSkillManagementCallback>> {
    static REGISTRY: OnceLock<Mutex<Option<RuntimeSkillManagementCallback>>> = OnceLock::new();
    REGISTRY.get_or_init(|| Mutex::new(None))
}

/// Return the process-wide host-tool callback storage.
/// 返回进程级宿主工具回调存储。
fn host_tool_callback_registry() -> &'static Mutex<Option<RuntimeHostToolCallback>> {
    static REGISTRY: OnceLock<Mutex<Option<RuntimeHostToolCallback>>> = OnceLock::new();
    REGISTRY.get_or_init(|| Mutex::new(None))
}

/// Return the process-wide standard embedding callback storage.
/// 返回进程级标准 embedding 回调存储。
fn model_embed_callback_registry() -> &'static Mutex<Option<RuntimeModelEmbedCallback>> {
    static REGISTRY: OnceLock<Mutex<Option<RuntimeModelEmbedCallback>>> = OnceLock::new();
    REGISTRY.get_or_init(|| Mutex::new(None))
}

/// Return the process-wide standard LLM callback storage.
/// 返回进程级标准 LLM 回调存储。
fn model_llm_callback_registry() -> &'static Mutex<Option<RuntimeModelLlmCallback>> {
    static REGISTRY: OnceLock<Mutex<Option<RuntimeModelLlmCallback>>> = OnceLock::new();
    REGISTRY.get_or_init(|| Mutex::new(None))
}

#[cfg(test)]
mod tests {
    use super::{
        RuntimeSkillOperationProgressCallback, RuntimeSkillOperationProgressDetail,
        RuntimeSkillOperationProgressEmitter, RuntimeSkillOperationProgressEvent,
        set_skill_operation_progress_callback,
    };
    use crate::skill::manager::{SkillLifecycleAction, SkillOperationPlane};
    use crate::skill::source::SkillInstallSourceType;
    use std::sync::{Arc, Mutex, MutexGuard, OnceLock};

    /// Return one shared guard that serializes tests touching the global progress callback.
    /// 返回一把用于串行化访问全局进度回调的共享测试锁。
    fn progress_callback_test_guard() -> MutexGuard<'static, ()> {
        static TEST_MUTEX: OnceLock<Mutex<()>> = OnceLock::new();
        match TEST_MUTEX.get_or_init(|| Mutex::new(())).lock() {
            Ok(guard) => guard,
            Err(poisoned) => poisoned.into_inner(),
        }
    }

    /// Verify progress emitters preserve operation ordering and compute determinate percentages.
    /// 验证进度发射器会保持操作内顺序并计算确定性百分比。
    #[test]
    fn progress_emitter_reports_sequence_and_percent() {
        let _guard = progress_callback_test_guard();
        let captured = Arc::new(Mutex::new(Vec::<RuntimeSkillOperationProgressEvent>::new()));
        let callback_events = captured.clone();
        let callback: RuntimeSkillOperationProgressCallback =
            Arc::new(move |event: &RuntimeSkillOperationProgressEvent| {
                callback_events
                    .lock()
                    .expect("capture progress events")
                    .push(event.clone());
            });
        set_skill_operation_progress_callback(Some(callback));

        let emitter = RuntimeSkillOperationProgressEmitter::new(
            SkillOperationPlane::System,
            SkillLifecycleAction::Install,
            Some("ROOT".to_string()),
            Some("demo-skill".to_string()),
        );
        emitter.emit_detail(RuntimeSkillOperationProgressDetail {
            phase: "downloading_archive",
            status: "progress",
            skill_id: Some("demo-skill"),
            source_type: Some(SkillInstallSourceType::OfficialHub),
            source_locator: Some("https://hub.example.invalid/demo.zip"),
            bytes_done: Some(5),
            bytes_total: Some(10),
            message: None,
        });
        emitter.emit("completed", "completed", Some("done".to_string()));
        set_skill_operation_progress_callback(None);

        let events = captured.lock().expect("read captured progress events");
        let operation_id = events
            .iter()
            .find(|event| {
                event.skill_id.as_deref() == Some("demo-skill")
                    && event.phase == "downloading_archive"
            })
            .expect("download progress event should be captured")
            .operation_id
            .clone();
        let operation_events = events
            .iter()
            .filter(|event| event.operation_id == operation_id)
            .collect::<Vec<_>>();
        assert_eq!(operation_events.len(), 2);
        assert_eq!(operation_events[0].sequence, 1);
        assert_eq!(operation_events[1].sequence, 2);
        assert_eq!(operation_events[0].percent, Some(50.0));
        assert_eq!(
            operation_events[0].source_type,
            Some(SkillInstallSourceType::OfficialHub)
        );
    }
}