everruns-provider 0.17.13

Provider/LLM abstraction foundation shared by Everruns core and provider crates
Documentation
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
// Typed ID module - provides type-safe, prefixed identifiers
// See specs/id-schema.md for the full specification
//
// Design decisions:
// - Uses marker traits to differentiate ID types at compile time
// - Stores IDs as prefixed strings (e.g., "agent_01933b5a...")
// - Uses UUIDv7 for DB-backed ids (time-ordering) and UUIDv4 for random-public
//   ids (e.g. MessageId) — dispatched per class via IdMarker::generate_uuid()
// - Supports serde, sqlx, and utoipa for full integration

use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::hash::Hash;
use std::marker::PhantomData;
use std::str::FromStr;
use uuid::Uuid;

/// Error type for ID parsing failures
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IdParseError {
    /// ID doesn't start with the expected prefix
    InvalidPrefix { expected: &'static str, got: String },
    /// Suffix is not valid hex
    InvalidHex(String),
    /// Suffix has wrong length
    InvalidLength { expected: usize, got: usize },
}

impl std::fmt::Display for IdParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            IdParseError::InvalidPrefix { expected, got } => {
                write!(f, "invalid prefix: expected '{}', got '{}'", expected, got)
            }
            IdParseError::InvalidHex(s) => write!(f, "invalid hex in ID: {}", s),
            IdParseError::InvalidLength { expected, got } => {
                write!(f, "invalid length: expected {}, got {}", expected, got)
            }
        }
    }
}

impl std::error::Error for IdParseError {}

/// Marker trait for typed ID types
pub trait IdMarker: Clone + Copy + Send + Sync + 'static {
    /// The prefix for this ID type (e.g., "agt" for agents)
    const PREFIX: &'static str;

    /// Generate a fresh UUID for a new id of this class.
    ///
    /// Defaults to UUIDv7, whose time-ordering gives DB-backed keys B-tree
    /// locality and sortability (events, sessions, agents, …). Id classes that
    /// are purely *public/correlation* identifiers with no DB sort/index
    /// dependency override this to a random UUIDv4 so no creation timestamp
    /// leaks into a client-visible id. See `specs/id-schema.md`.
    fn generate_uuid() -> Uuid {
        Uuid::now_v7()
    }
}

/// A type-safe identifier with a specific prefix
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct TypedId<T: IdMarker> {
    uuid: Uuid,
    _marker: PhantomData<T>,
}

impl<T: IdMarker> TypedId<T> {
    /// Create a new ID using this id class's generation strategy
    /// ([`IdMarker::generate_uuid`] — UUIDv7 by default, UUIDv4 for
    /// random-public id classes such as [`MessageId`]).
    pub fn new() -> Self {
        Self {
            uuid: T::generate_uuid(),
            _marker: PhantomData,
        }
    }

    /// Create a new ID with a random (non-time-ordered) UUIDv4.
    ///
    /// Use for public/correlation ids that must not embed a creation
    /// timestamp. The wire format is unchanged (`prefix_{32-hex}`), so parsing,
    /// validation, and persistence are identical to a UUIDv7-backed id.
    pub fn new_random() -> Self {
        Self {
            uuid: Uuid::new_v4(),
            _marker: PhantomData,
        }
    }

    /// Create an ID from an existing UUID
    pub fn from_uuid(uuid: Uuid) -> Self {
        Self {
            uuid,
            _marker: PhantomData,
        }
    }

    /// Get the underlying UUID
    pub fn uuid(&self) -> Uuid {
        self.uuid
    }

    /// Get the prefix for this ID type
    pub fn prefix() -> &'static str {
        T::PREFIX
    }

    /// Parse an ID from a prefixed string
    pub fn parse(s: &str) -> Result<Self, IdParseError> {
        let expected_prefix = format!("{}_", T::PREFIX);

        if !s.starts_with(&expected_prefix) {
            let got_prefix = s.split('_').next().unwrap_or("").to_string();
            return Err(IdParseError::InvalidPrefix {
                expected: T::PREFIX,
                got: got_prefix,
            });
        }

        let suffix = &s[expected_prefix.len()..];

        if suffix.len() != 32 {
            return Err(IdParseError::InvalidLength {
                expected: 32,
                got: suffix.len(),
            });
        }

        // Validate hex characters (lowercase only)
        if !suffix
            .chars()
            .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
        {
            return Err(IdParseError::InvalidHex(suffix.to_string()));
        }

        // Parse the UUID
        let uuid =
            Uuid::parse_str(suffix).map_err(|_| IdParseError::InvalidHex(suffix.to_string()))?;

        Ok(Self {
            uuid,
            _marker: PhantomData,
        })
    }

    /// Create an ID from a well-known integer value (for seeding)
    /// Produces IDs like "agent_00000000000000000000000000000001"
    pub fn from_seed(value: u128) -> Self {
        let uuid = Uuid::from_u128(value);
        Self {
            uuid,
            _marker: PhantomData,
        }
    }
}

impl<T: IdMarker> Default for TypedId<T> {
    fn default() -> Self {
        Self::new()
    }
}

// Conversion from TypedId to Uuid
impl<T: IdMarker> From<TypedId<T>> for Uuid {
    fn from(id: TypedId<T>) -> Self {
        id.uuid
    }
}

// Conversion from Uuid to TypedId
impl<T: IdMarker> From<Uuid> for TypedId<T> {
    fn from(uuid: Uuid) -> Self {
        Self::from_uuid(uuid)
    }
}

// Allow using TypedId as a key in HashMap/HashSet that expects Uuid
impl<T: IdMarker> std::borrow::Borrow<Uuid> for TypedId<T> {
    fn borrow(&self) -> &Uuid {
        &self.uuid
    }
}

// AsRef<Uuid> for TypedId
impl<T: IdMarker> AsRef<Uuid> for TypedId<T> {
    fn as_ref(&self) -> &Uuid {
        &self.uuid
    }
}

// PartialEq<Uuid> for TypedId (allows comparing TypedId == Uuid)
impl<T: IdMarker> PartialEq<Uuid> for TypedId<T> {
    fn eq(&self, other: &Uuid) -> bool {
        self.uuid == *other
    }
}

// PartialEq<TypedId> for Uuid (allows comparing Uuid == TypedId)
impl<T: IdMarker> PartialEq<TypedId<T>> for Uuid {
    fn eq(&self, other: &TypedId<T>) -> bool {
        *self == other.uuid
    }
}

impl<T: IdMarker> fmt::Display for TypedId<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}_{}", T::PREFIX, self.uuid.simple())
    }
}

impl<T: IdMarker> fmt::Debug for TypedId<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}({})",
            std::any::type_name::<T>()
                .split("::")
                .last()
                .unwrap_or("Id"),
            self
        )
    }
}

impl<T: IdMarker> FromStr for TypedId<T> {
    type Err = IdParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::parse(s)
    }
}

impl<T: IdMarker> Serialize for TypedId<T> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl<'de, T: IdMarker> Deserialize<'de> for TypedId<T> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Self::parse(&s).map_err(serde::de::Error::custom)
    }
}

// OpenAPI schema support
#[cfg(feature = "openapi")]
impl<T: IdMarker> utoipa::ToSchema for TypedId<T> {
    fn name() -> std::borrow::Cow<'static, str> {
        std::borrow::Cow::Owned(format!("{}Id", T::PREFIX))
    }
}

#[cfg(feature = "openapi")]
impl<T: IdMarker> utoipa::PartialSchema for TypedId<T> {
    #[allow(deprecated)]
    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
        let example_value = format!("{}_{}", T::PREFIX, "01933b5a00007000800000000000001");
        utoipa::openapi::ObjectBuilder::new()
            .schema_type(utoipa::openapi::schema::Type::String)
            .description(Some(format!(
                "Prefixed identifier with '{}' prefix",
                T::PREFIX
            )))
            .example(Some(serde_json::json!(example_value)))
            .pattern(Some(format!("^{}_[0-9a-f]{{32}}$", T::PREFIX)))
            .into()
    }
}

// sqlx support - maps TypedId to/from UUID in database
#[cfg(feature = "sqlx")]
impl<T: IdMarker> sqlx::Type<sqlx::Postgres> for TypedId<T> {
    fn type_info() -> sqlx::postgres::PgTypeInfo {
        <Uuid as sqlx::Type<sqlx::Postgres>>::type_info()
    }

    fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
        <Uuid as sqlx::Type<sqlx::Postgres>>::compatible(ty)
    }
}

#[cfg(feature = "sqlx")]
impl<T: IdMarker> sqlx::Encode<'_, sqlx::Postgres> for TypedId<T> {
    fn encode_by_ref(
        &self,
        buf: &mut sqlx::postgres::PgArgumentBuffer,
    ) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
        <Uuid as sqlx::Encode<sqlx::Postgres>>::encode_by_ref(&self.uuid, buf)
    }
}

#[cfg(feature = "sqlx")]
impl<T: IdMarker> sqlx::Decode<'_, sqlx::Postgres> for TypedId<T> {
    fn decode(
        value: sqlx::postgres::PgValueRef<'_>,
    ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        let uuid = <Uuid as sqlx::Decode<sqlx::Postgres>>::decode(value)?;
        Ok(Self::from_uuid(uuid))
    }
}

// ============================================================================
// Marker types for each entity
// ============================================================================

/// Marker for Organization IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct OrgIdMarker;
impl IdMarker for OrgIdMarker {
    const PREFIX: &'static str = "org";
}

/// Marker for Agent IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct AgentIdMarker;
impl IdMarker for AgentIdMarker {
    const PREFIX: &'static str = "agent";
}

/// Marker for immutable Agent Version IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct AgentVersionIdMarker;
impl IdMarker for AgentVersionIdMarker {
    const PREFIX: &'static str = "agentver";
}

/// Marker for Harness IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct HarnessIdMarker;
impl IdMarker for HarnessIdMarker {
    const PREFIX: &'static str = "harness";
}

/// Marker for agent identity IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct AgentIdentityIdMarker;
impl IdMarker for AgentIdentityIdMarker {
    const PREFIX: &'static str = "identity";
}

/// Marker for agent trigger IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TriggerIdMarker;
impl IdMarker for TriggerIdMarker {
    const PREFIX: &'static str = "trg";
}

/// Marker for principal IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PrincipalIdMarker;
impl IdMarker for PrincipalIdMarker {
    const PREFIX: &'static str = "principal";
}

/// Marker for Session IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SessionIdMarker;
impl IdMarker for SessionIdMarker {
    const PREFIX: &'static str = "session";
}

/// Marker for Session Participant IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SessionParticipantIdMarker;
impl IdMarker for SessionParticipantIdMarker {
    const PREFIX: &'static str = "part";
}

/// Marker for Message IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct MessageIdMarker;
impl IdMarker for MessageIdMarker {
    const PREFIX: &'static str = "message";

    // Messages are not DB entities — they live embedded in `events.data` JSONB
    // with no table, FK, index, or sort dependency on their id, and the id is
    // the *public* identifier serialized to clients (`output.message.completed`,
    // `EventContext.input_message_id`). UUIDv7's time-ordering does no work here
    // and would leak a creation timestamp into a client-visible id, so message
    // ids are random UUIDv4. See EVE-771 and `specs/id-schema.md`.
    fn generate_uuid() -> Uuid {
        Uuid::new_v4()
    }
}

/// Marker for Event IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct EventIdMarker;
impl IdMarker for EventIdMarker {
    const PREFIX: &'static str = "event";
}

/// Marker for LLM Provider IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ProviderIdMarker;
impl IdMarker for ProviderIdMarker {
    const PREFIX: &'static str = "provider";
}

/// Marker for LLM Model IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ModelIdMarker;
impl IdMarker for ModelIdMarker {
    const PREFIX: &'static str = "model";
}

/// Marker for Image IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ImageIdMarker;
impl IdMarker for ImageIdMarker {
    const PREFIX: &'static str = "img";
}

/// Marker for MCP Server IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct McpServerIdMarker;
impl IdMarker for McpServerIdMarker {
    const PREFIX: &'static str = "mcp";
}

/// Marker for Skill IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SkillIdMarker;
impl IdMarker for SkillIdMarker {
    const PREFIX: &'static str = "skill";
}

/// Marker for Declarative Capability IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DeclarativeCapabilityIdMarker;
impl IdMarker for DeclarativeCapabilityIdMarker {
    const PREFIX: &'static str = "cap";
}

/// Marker for Turn IDs (used in workflow execution)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TurnIdMarker;
impl IdMarker for TurnIdMarker {
    const PREFIX: &'static str = "turn";
}

/// Marker for Execution IDs (workflow execution instance)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ExecIdMarker;
impl IdMarker for ExecIdMarker {
    const PREFIX: &'static str = "exec";
}

/// Marker for Session Schedule IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ScheduleIdMarker;
impl IdMarker for ScheduleIdMarker {
    const PREFIX: &'static str = "sched";
}

/// Marker for leased resource IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct LeasedResourceIdMarker;
impl IdMarker for LeasedResourceIdMarker {
    const PREFIX: &'static str = "resource";
}

/// Marker for App IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct AppIdMarker;
impl IdMarker for AppIdMarker {
    const PREFIX: &'static str = "app";
}

/// Marker for App Channel IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct AppChannelIdMarker;
impl IdMarker for AppChannelIdMarker {
    const PREFIX: &'static str = "appchan";
}

/// Marker for Notification IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct NotificationIdMarker;
impl IdMarker for NotificationIdMarker {
    const PREFIX: &'static str = "notification";
}

/// Marker for Memory IDs (org-scoped named Memories — see `specs/memory.md`)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct MemoryIdMarker;
impl IdMarker for MemoryIdMarker {
    const PREFIX: &'static str = "mem";
}

/// Marker for Workspace IDs (org-scoped named working areas — see `specs/workspace.md`)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct WorkspaceIdMarker;
impl IdMarker for WorkspaceIdMarker {
    const PREFIX: &'static str = "wsp";
}

/// Marker for Eval IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct EvalIdMarker;
impl IdMarker for EvalIdMarker {
    const PREFIX: &'static str = "eval";
}

/// Marker for Eval Case IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct EvalCaseIdMarker;
impl IdMarker for EvalCaseIdMarker {
    const PREFIX: &'static str = "evalcase";
}

/// Marker for Eval Run IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct EvalRunIdMarker;
impl IdMarker for EvalRunIdMarker {
    const PREFIX: &'static str = "evalrun";
}

/// Marker for Eval Run Dataset IDs (async dataset export handles — see
/// `specs/dataset-export.md`)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct EvalDatasetIdMarker;
impl IdMarker for EvalDatasetIdMarker {
    const PREFIX: &'static str = "evaldataset";
}

/// Marker for Agent Health Check Run IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct HealthCheckRunIdMarker;
impl IdMarker for HealthCheckRunIdMarker {
    const PREFIX: &'static str = "healthcheck";
}

/// Marker for Eval Case Result IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct EvalResultIdMarker;
impl IdMarker for EvalResultIdMarker {
    const PREFIX: &'static str = "evalresult";
}

/// Marker for Observer IDs (online scoring of production sessions — see `specs/online-evals.md`)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ObserverIdMarker;
impl IdMarker for ObserverIdMarker {
    const PREFIX: &'static str = "observer";
}

/// Marker for Trace Score IDs (observer scoring output)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TraceScoreIdMarker;
impl IdMarker for TraceScoreIdMarker {
    const PREFIX: &'static str = "score";
}

/// Marker for Budget IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct BudgetIdMarker;
impl IdMarker for BudgetIdMarker {
    const PREFIX: &'static str = "bdgt";
}

/// Marker for payment account IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PaymentAccountIdMarker;
impl IdMarker for PaymentAccountIdMarker {
    const PREFIX: &'static str = "payacct";
}

/// Marker for payment policy IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PaymentPolicyIdMarker;
impl IdMarker for PaymentPolicyIdMarker {
    const PREFIX: &'static str = "paypol";
}

/// Marker for payment attempt IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PaymentAttemptIdMarker;
impl IdMarker for PaymentAttemptIdMarker {
    const PREFIX: &'static str = "payatt";
}

/// Marker for Budget Ledger Entry IDs
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct LedgerEntryIdMarker;
impl IdMarker for LedgerEntryIdMarker {
    const PREFIX: &'static str = "ledger";
}

/// Marker for Knowledge Base IDs (curated org knowledge — see `specs/knowledge-bases.md`)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct KnowledgeBaseIdMarker;
impl IdMarker for KnowledgeBaseIdMarker {
    const PREFIX: &'static str = "kb";
}

/// Marker for Knowledge Entry IDs (entries inside a Knowledge Base)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct KnowledgeEntryIdMarker;
impl IdMarker for KnowledgeEntryIdMarker {
    const PREFIX: &'static str = "kbe";
}

/// Marker for Knowledge Index IDs (source-backed embedded collections — see `specs/knowledge-indexes.md`)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct KnowledgeIndexIdMarker;
impl IdMarker for KnowledgeIndexIdMarker {
    const PREFIX: &'static str = "kidx";
}

/// Marker for Knowledge Index Document IDs (an ingested source document)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct KnowledgeIndexDocumentIdMarker;
impl IdMarker for KnowledgeIndexDocumentIdMarker {
    const PREFIX: &'static str = "kidoc";
}

/// Marker for Knowledge Index Chunk IDs (the citable retrieval unit)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct KnowledgeIndexChunkIdMarker;
impl IdMarker for KnowledgeIndexChunkIdMarker {
    const PREFIX: &'static str = "kchk";
}

/// Marker for Model Router IDs (semantic LLM selection — see `specs/model-router.md`)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ModelRouterIdMarker;
impl IdMarker for ModelRouterIdMarker {
    const PREFIX: &'static str = "mrtr";
}

/// Marker for Plugin Marketplace IDs (see `specs/plugins.md`)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PluginMarketplaceIdMarker;
impl IdMarker for PluginMarketplaceIdMarker {
    const PREFIX: &'static str = "plgmkt";
}

/// Marker for Plugin Install IDs (see `specs/plugins.md`)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PluginInstallIdMarker;
impl IdMarker for PluginInstallIdMarker {
    const PREFIX: &'static str = "plugin";
}

// ============================================================================
// Type aliases for convenience
// ============================================================================

/// Organization ID
pub type OrgId = TypedId<OrgIdMarker>;
/// Agent ID
pub type AgentId = TypedId<AgentIdMarker>;
/// Immutable Agent Version ID
pub type AgentVersionId = TypedId<AgentVersionIdMarker>;
/// Harness ID
pub type HarnessId = TypedId<HarnessIdMarker>;
/// Agent identity ID
pub type AgentIdentityId = TypedId<AgentIdentityIdMarker>;
/// Agent trigger ID
pub type TriggerId = TypedId<TriggerIdMarker>;
/// Principal ID
pub type PrincipalId = TypedId<PrincipalIdMarker>;
/// Session ID
pub type SessionId = TypedId<SessionIdMarker>;
/// Session Participant ID
pub type SessionParticipantId = TypedId<SessionParticipantIdMarker>;
/// Message ID
pub type MessageId = TypedId<MessageIdMarker>;
/// Event ID
pub type EventId = TypedId<EventIdMarker>;
/// LLM Provider ID
pub type ProviderId = TypedId<ProviderIdMarker>;
/// LLM Model ID
pub type ModelId = TypedId<ModelIdMarker>;
/// Image ID
pub type ImageId = TypedId<ImageIdMarker>;
/// MCP Server ID
pub type McpServerId = TypedId<McpServerIdMarker>;
/// Skill ID
pub type SkillId = TypedId<SkillIdMarker>;
/// Declarative Capability ID
pub type DeclarativeCapabilityId = TypedId<DeclarativeCapabilityIdMarker>;
/// Turn ID
pub type TurnId = TypedId<TurnIdMarker>;
/// Execution ID
pub type ExecId = TypedId<ExecIdMarker>;
/// Session Schedule ID
pub type ScheduleId = TypedId<ScheduleIdMarker>;
/// Leased resource ID
pub type LeasedResourceId = TypedId<LeasedResourceIdMarker>;
/// App ID
pub type AppId = TypedId<AppIdMarker>;
/// App Channel ID
pub type AppChannelId = TypedId<AppChannelIdMarker>;
/// Notification ID
pub type NotificationId = TypedId<NotificationIdMarker>;
/// Memory ID (org-scoped named Memory — see `specs/memory.md`)
pub type MemoryId = TypedId<MemoryIdMarker>;
/// Workspace ID (org-scoped named Workspace — see `specs/workspace.md`)
pub type WorkspaceId = TypedId<WorkspaceIdMarker>;
/// Eval ID
pub type EvalId = TypedId<EvalIdMarker>;
/// Eval Case ID
pub type EvalCaseId = TypedId<EvalCaseIdMarker>;
/// Eval Run ID
pub type EvalRunId = TypedId<EvalRunIdMarker>;
/// Eval Run Dataset ID (async dataset export handle — see `specs/dataset-export.md`)
pub type EvalDatasetId = TypedId<EvalDatasetIdMarker>;
/// Agent Health Check Run ID
pub type HealthCheckRunId = TypedId<HealthCheckRunIdMarker>;
/// Eval Case Result ID
pub type EvalResultId = TypedId<EvalResultIdMarker>;
/// Observer ID (online scoring — see `specs/online-evals.md`)
pub type ObserverId = TypedId<ObserverIdMarker>;
/// Trace Score ID (observer scoring output)
pub type TraceScoreId = TypedId<TraceScoreIdMarker>;
/// Budget ID
pub type BudgetId = TypedId<BudgetIdMarker>;
/// Payment account ID
pub type PaymentAccountId = TypedId<PaymentAccountIdMarker>;
/// Payment policy ID
pub type PaymentPolicyId = TypedId<PaymentPolicyIdMarker>;
/// Payment attempt ID
pub type PaymentAttemptId = TypedId<PaymentAttemptIdMarker>;
/// Budget Ledger Entry ID
pub type LedgerEntryId = TypedId<LedgerEntryIdMarker>;
/// Knowledge Base ID (curated org knowledge — see `specs/knowledge-bases.md`)
pub type KnowledgeBaseId = TypedId<KnowledgeBaseIdMarker>;
/// Knowledge Entry ID (entry inside a Knowledge Base)
pub type KnowledgeEntryId = TypedId<KnowledgeEntryIdMarker>;
/// Knowledge Index ID (source-backed embedded collection — see `specs/knowledge-indexes.md`)
pub type KnowledgeIndexId = TypedId<KnowledgeIndexIdMarker>;
/// Knowledge Index Document ID (an ingested source document)
pub type KnowledgeIndexDocumentId = TypedId<KnowledgeIndexDocumentIdMarker>;
/// Knowledge Index Chunk ID (the citable retrieval unit)
pub type KnowledgeIndexChunkId = TypedId<KnowledgeIndexChunkIdMarker>;
/// Model Router ID (semantic LLM selection — see `specs/model-router.md`)
pub type ModelRouterId = TypedId<ModelRouterIdMarker>;
/// Plugin Marketplace ID (see `specs/plugins.md`)
pub type PluginMarketplaceId = TypedId<PluginMarketplaceIdMarker>;
/// Plugin Install ID (see `specs/plugins.md`)
pub type PluginInstallId = TypedId<PluginInstallIdMarker>;

// ============================================================================
// Well-known IDs (for seeding and defaults)
// ============================================================================

/// Default organization ID
pub const DEFAULT_ORG_ID: OrgId = TypedId {
    uuid: Uuid::from_u128(1),
    _marker: PhantomData,
};

/// Well-known provider IDs
pub mod well_known {
    use super::*;

    /// OpenAI provider ID
    pub const OPENAI_PROVIDER_ID: ProviderId = TypedId {
        uuid: Uuid::from_u128(0x01933b5a_0000_7000_8000_000000000001),
        _marker: PhantomData,
    };

    /// Anthropic provider ID
    pub const ANTHROPIC_PROVIDER_ID: ProviderId = TypedId {
        uuid: Uuid::from_u128(0x01933b5a_0000_7000_8000_000000000002),
        _marker: PhantomData,
    };
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new_agent_id() {
        let id = AgentId::new();
        let s = id.to_string();
        assert!(s.starts_with("agent_"));
        assert_eq!(s.len(), 38); // "agent_" + 32 hex chars
    }

    #[test]
    fn test_parse_agent_id() {
        let id = AgentId::new();
        let s = id.to_string();
        let parsed = AgentId::parse(&s).unwrap();
        assert_eq!(id, parsed);
    }

    #[test]
    fn test_parse_invalid_prefix() {
        let result = AgentId::parse("session_01933b5a00007000800000000000001");
        assert!(matches!(result, Err(IdParseError::InvalidPrefix { .. })));
    }

    #[test]
    fn test_parse_invalid_length() {
        let result = AgentId::parse("agent_123");
        assert!(matches!(result, Err(IdParseError::InvalidLength { .. })));
    }

    #[test]
    fn test_parse_invalid_hex() {
        let result = AgentId::parse("agent_GHIJKLMNOPQRSTUVWXYZ123456789012");
        assert!(matches!(result, Err(IdParseError::InvalidHex(_))));
    }

    #[test]
    fn test_from_seed() {
        let id = AgentId::from_seed(1);
        assert_eq!(id.to_string(), "agent_00000000000000000000000000000001");
    }

    #[test]
    fn test_serde_roundtrip() {
        let id = AgentId::new();
        let json = serde_json::to_string(&id).unwrap();
        let parsed: AgentId = serde_json::from_str(&json).unwrap();
        assert_eq!(id, parsed);
    }

    #[test]
    fn test_default_org_id() {
        assert_eq!(
            DEFAULT_ORG_ID.to_string(),
            "org_00000000000000000000000000000001"
        );
    }

    #[test]
    fn test_well_known_provider_ids() {
        assert_eq!(
            well_known::OPENAI_PROVIDER_ID.to_string(),
            "provider_01933b5a000070008000000000000001"
        );
        assert_eq!(
            well_known::ANTHROPIC_PROVIDER_ID.to_string(),
            "provider_01933b5a000070008000000000000002"
        );
    }

    #[test]
    fn test_from_uuid() {
        let uuid = Uuid::now_v7();
        let id = AgentId::from_uuid(uuid);
        assert_eq!(id.uuid(), uuid);
    }

    #[test]
    fn test_hash() {
        use std::collections::HashSet;
        let id1 = AgentId::new();
        let id2 = AgentId::new();
        let mut set = HashSet::new();
        set.insert(id1);
        set.insert(id2);
        assert_eq!(set.len(), 2);
        set.insert(id1);
        assert_eq!(set.len(), 2); // No duplicate
    }

    #[test]
    fn test_message_id_is_random_v4() {
        // MessageId is a random-public id class: new() and new_random() must
        // both yield UUIDv4, unlike the default UUIDv7 classes.
        let id = MessageId::new();
        assert_eq!(
            id.uuid().get_version_num(),
            4,
            "MessageId::new() must be v4"
        );
        assert_eq!(
            MessageId::new_random().uuid().get_version_num(),
            4,
            "MessageId::new_random() must be v4"
        );
        // Format is unchanged: message_ + 32 lowercase hex, still parseable.
        let s = id.to_string();
        assert!(s.starts_with("message_"));
        assert_eq!(s.len(), "message_".len() + 32);
        assert_eq!(MessageId::parse(&s).unwrap(), id);
    }

    #[test]
    fn test_default_id_class_stays_v7() {
        // DB-backed classes keep UUIDv7 for B-tree locality / sortability.
        assert_eq!(AgentId::new().uuid().get_version_num(), 7);
        assert_eq!(SessionId::new().uuid().get_version_num(), 7);
        assert_eq!(EventId::new().uuid().get_version_num(), 7);
        // TurnId decision (EVE-771): stays UUIDv7 — turn ordering is used by
        // durable execution and its public AG-UI exposure is being removed by
        // the streaming message_id work (EVE-773), so it is not changed here.
        assert_eq!(TurnId::new().uuid().get_version_num(), 7);
    }

    #[test]
    fn test_message_id_parse_compat_legacy_and_random() {
        // Legacy message ids were UUIDv7; new ones are UUIDv4. Both must parse
        // identically — the wire format did not change, so no migration.
        let legacy_v7 = format!("message_{}", Uuid::now_v7().simple());
        let new_v4 = format!("message_{}", Uuid::new_v4().simple());
        for s in [legacy_v7, new_v4] {
            let parsed = MessageId::parse(&s).expect("both id vintages must parse");
            assert_eq!(parsed.to_string(), s);
        }
    }
}