capdag 0.157.363 → 0.161.384

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.
package/README.md CHANGED
@@ -23,7 +23,7 @@ npm install capdag
23
23
  const { CapUrn, CapUrnBuilder, CapMatcher } = require('capdag');
24
24
 
25
25
  // Create from string (with required direction specifiers)
26
- const cap = CapUrn.fromString('cap:in="media:binary";op=extract;out="media:object"');
26
+ const cap = CapUrn.fromString('cap:in="media:binary";extract;out="media:object"');
27
27
  console.log(cap.toString());
28
28
 
29
29
  // Use builder pattern
@@ -35,14 +35,14 @@ const built = new CapUrnBuilder()
35
35
  .build();
36
36
 
37
37
  // Matching
38
- const request = CapUrn.fromString('cap:in="media:binary";op=extract;out="media:object"');
38
+ const request = CapUrn.fromString('cap:in="media:binary";extract;out="media:object"');
39
39
  console.log(cap.accepts(request)); // true
40
40
 
41
41
  // Find best match by specificity
42
42
  const caps = [
43
- CapUrn.fromString('cap:in=*;op=extract;out=*'),
44
- CapUrn.fromString('cap:in="media:binary";op=extract;out="media:object"'),
45
- CapUrn.fromString('cap:ext=pdf;in="media:binary";op=extract;out="media:object"')
43
+ CapUrn.fromString('cap:in=*;extract;out=*'),
44
+ CapUrn.fromString('cap:in="media:binary";extract;out="media:object"'),
45
+ CapUrn.fromString('cap:ext=pdf;in="media:binary";extract;out="media:object"')
46
46
  ];
47
47
  const best = CapMatcher.findBestMatch(caps, request);
48
48
  console.log(best.toString()); // Most specific match
package/RULES.md CHANGED
@@ -14,7 +14,7 @@ Cap URNs **must** include `in` and `out` tags that specify input/output media ty
14
14
 
15
15
  ```javascript
16
16
  // Valid cap URN with direction specifiers
17
- const cap = CapUrn.fromString('cap:in="media:binary";op=extract;out="media:object"');
17
+ const cap = CapUrn.fromString('cap:in="media:binary";extract;out="media:object"');
18
18
 
19
19
  // Invalid - missing direction specifiers
20
20
  CapUrn.fromString('cap:op=extract'); // throws ErrorCodes.MISSING_IN_SPEC
@@ -26,13 +26,13 @@ Direction specifier values must be valid Media URNs or special pattern values:
26
26
 
27
27
  ```javascript
28
28
  // Valid: Media URN value
29
- 'cap:in="media:binary";op=extract;out="media:object"'
29
+ 'cap:in="media:binary";extract;out="media:object"'
30
30
 
31
31
  // Valid: Must-have-any (any media type)
32
- 'cap:in=*;op=extract;out=*'
32
+ 'cap:in=*;extract;out=*'
33
33
 
34
34
  // Invalid: Not a Media URN or special value
35
- 'cap:in=binary;op=extract;out=object' // throws ErrorCodes.INVALID_MEDIA_URN
35
+ 'cap:in=binary;extract;out=object' // throws ErrorCodes.INVALID_MEDIA_URN
36
36
  ```
37
37
 
38
38
  ### 3. Matching Semantics
@@ -59,8 +59,8 @@ Specificity uses graded scoring:
59
59
  | Unspecified (K=?) or missing | 0 |
60
60
 
61
61
  Examples:
62
- - `cap:in="media:binary";op=extract;out="media:object"` → 3+3+3 = 9
63
- - `cap:in=*;op=extract;out=*` → 2+3+2 = 7
62
+ - `cap:in="media:binary";extract;out="media:object"` → 3+3+3 = 9
63
+ - `cap:in=*;extract;out=*` → 2+3+2 = 7
64
64
 
65
65
  ## Cap-Specific Error Codes
66
66
 
package/capdag.js CHANGED
@@ -843,10 +843,44 @@ const MEDIA_OBJECT_LIST = 'media:list;record';
843
843
  // Semantic media types for specialized content
844
844
  // Media URN for PNG image data
845
845
  const MEDIA_PNG = 'media:image;png';
846
+ // Media URN for JPEG image data
847
+ const MEDIA_JPEG = 'media:jpeg;image';
848
+ // Media URN for GIF image data
849
+ const MEDIA_GIF = 'media:gif;image';
850
+ // Media URN for BMP image data
851
+ const MEDIA_BMP = 'media:bmp;image';
852
+ // Media URN for TIFF image data
853
+ const MEDIA_TIFF = 'media:tiff;image';
854
+ // Media URN for WebP image data
855
+ const MEDIA_WEBP = 'media:webp;image';
846
856
  // Media URN for audio data (wav, mp3, flac, etc.)
847
857
  const MEDIA_AUDIO = 'media:wav;audio';
858
+ // Media URN for MP3 audio data
859
+ const MEDIA_MP3 = 'media:mp3;audio';
860
+ // Media URN for WAV audio data
861
+ const MEDIA_WAV = 'media:wav;audio';
862
+ // Media URN for FLAC audio data
863
+ const MEDIA_FLAC = 'media:flac;audio';
864
+ // Media URN for OGG audio data
865
+ const MEDIA_OGG = 'media:ogg;audio';
866
+ // Media URN for AAC audio data
867
+ const MEDIA_AAC = 'media:aac;audio';
868
+ // Media URN for M4A audio data
869
+ const MEDIA_M4A = 'media:m4a;audio';
870
+ // Media URN for AIFF audio data
871
+ const MEDIA_AIFF = 'media:aiff;audio';
872
+ // Media URN for Opus audio data
873
+ const MEDIA_OPUS = 'media:opus;audio';
848
874
  // Media URN for video data (mp4, webm, mov, etc.)
849
875
  const MEDIA_VIDEO = 'media:video';
876
+ // Media URN for MP4 video data
877
+ const MEDIA_MP4 = 'media:mp4;video';
878
+ // Media URN for MOV video data
879
+ const MEDIA_MOV = 'media:mov;video';
880
+ // Media URN for WebM video data
881
+ const MEDIA_WEBM = 'media:webm;video';
882
+ // Media URN for MKV video data
883
+ const MEDIA_MKV = 'media:mkv;video';
850
884
 
851
885
  // Semantic AI input types - distinguished by their purpose/context
852
886
  // Media URN for audio input containing speech for transcription (Whisper)
@@ -888,7 +922,7 @@ const MEDIA_YAML_RECORD = 'media:record;textable;yaml';
888
922
  const MEDIA_YAML_LIST = 'media:list;textable;yaml';
889
923
  const MEDIA_YAML_LIST_RECORD = 'media:list;record;textable;yaml';
890
924
  const MEDIA_CSV = 'media:csv;list;record;textable';
891
- const MEDIA_CSV_LIST = 'media:csv;list;textable';
925
+ const MEDIA_CSV_LIST = 'media:csv;list;record;textable';
892
926
 
893
927
  // File path type — for arguments that represent filesystem paths.
894
928
  // There is a single media URN; cardinality (single file vs many files)
@@ -930,6 +964,16 @@ const MEDIA_TRANSCRIPTION_OUTPUT = 'media:record;textable;transcription';
930
964
  const MEDIA_DECISION = 'media:decision;json;record;textable';
931
965
  // Media URN for textable page output
932
966
  const MEDIA_TEXTABLE_PAGE = 'media:textable;page';
967
+ // Media URN for Hugging Face API token (secret, textable)
968
+ const MEDIA_HF_TOKEN = 'media:hf-token;secret;textable';
969
+ // Media URN for a list of model architectures — JSON record
970
+ const MEDIA_MODEL_ARCH_LIST = 'media:model-arch-list;json;record;textable';
971
+ // Media URN for a model search request — JSON record
972
+ const MEDIA_MODEL_SEARCH_REQUEST = 'media:model-search-request;json;record;textable';
973
+ // Media URN for a model search response — JSON record
974
+ const MEDIA_MODEL_SEARCH_RESPONSE = 'media:model-search-response;json;record;textable';
975
+ // Media URN for model filter resolution — JSON record
976
+ const MEDIA_MODEL_FILTER_RESOLUTION = 'media:model-filter-resolution;json;record;textable';
933
977
  // Collection types
934
978
  const MEDIA_COLLECTION = 'media:collection;record';
935
979
  const MEDIA_COLLECTION_LIST = 'media:collection;list;record';
@@ -2031,6 +2075,8 @@ class Cap {
2031
2075
  this.output = null;
2032
2076
  this.metadata_json = metadataJson;
2033
2077
  this.registered_by = null; // Registration attribution
2078
+ this.supported_model_types = []; // Model types this cap supports (omitted when empty)
2079
+ this.default_model_spec = null; // Default model spec string (omitted when null)
2034
2080
  }
2035
2081
 
2036
2082
  /**
@@ -2247,7 +2293,9 @@ class Cap {
2247
2293
  JSON.stringify(this.args.map(a => a.toJSON())) === JSON.stringify(other.args.map(a => a.toJSON())) &&
2248
2294
  JSON.stringify(this.output) === JSON.stringify(other.output) &&
2249
2295
  JSON.stringify(this.metadata_json) === JSON.stringify(other.metadata_json) &&
2250
- JSON.stringify(this.registered_by) === JSON.stringify(other.registered_by);
2296
+ JSON.stringify(this.registered_by) === JSON.stringify(other.registered_by) &&
2297
+ JSON.stringify(this.supported_model_types) === JSON.stringify(other.supported_model_types) &&
2298
+ this.default_model_spec === other.default_model_spec;
2251
2299
  }
2252
2300
 
2253
2301
  /**
@@ -2276,6 +2324,16 @@ class Cap {
2276
2324
  result.metadata_json = this.metadata_json;
2277
2325
  }
2278
2326
 
2327
+ // supported_model_types: omit when empty, matching Rust skip_serializing_if = is_empty
2328
+ if (Array.isArray(this.supported_model_types) && this.supported_model_types.length > 0) {
2329
+ result.supported_model_types = this.supported_model_types;
2330
+ }
2331
+
2332
+ // default_model_spec: omit when null, matching Rust skip_serializing_if = is_none
2333
+ if (this.default_model_spec !== null && this.default_model_spec !== undefined) {
2334
+ result.default_model_spec = this.default_model_spec;
2335
+ }
2336
+
2279
2337
  return result;
2280
2338
  }
2281
2339
 
@@ -2304,6 +2362,8 @@ class Cap {
2304
2362
  }
2305
2363
  cap.output = json.output;
2306
2364
  cap.registered_by = json.registered_by ? RegisteredBy.fromJSON(json.registered_by) : null;
2365
+ cap.supported_model_types = Array.isArray(json.supported_model_types) ? json.supported_model_types : [];
2366
+ cap.default_model_spec = (typeof json.default_model_spec === 'string') ? json.default_model_spec : null;
2307
2367
  return cap;
2308
2368
  }
2309
2369
 
@@ -2331,6 +2391,183 @@ class Cap {
2331
2391
  }
2332
2392
  }
2333
2393
 
2394
+ /**
2395
+ * A cap group bundles caps and adapter URNs as an atomic registration unit.
2396
+ *
2397
+ * If any adapter in the group creates ambiguity with an already-registered
2398
+ * adapter, the entire group is rejected — none of its caps or adapters get
2399
+ * registered. Mirrors CSCapGroup / capdag::cap_group::CapGroup.
2400
+ */
2401
+ class CapGroup {
2402
+ /**
2403
+ * @param {string} name - Group name (for diagnostics and error messages)
2404
+ * @param {Cap[]} caps - Caps in this group
2405
+ * @param {string[]} adapterUrns - Media URNs this group's adapter handles
2406
+ */
2407
+ constructor(name, caps = [], adapterUrns = []) {
2408
+ if (!name || typeof name !== 'string') {
2409
+ throw new Error('CapGroup name is required and must be a string');
2410
+ }
2411
+ this.name = name;
2412
+ this.caps = caps;
2413
+ this.adapter_urns = adapterUrns;
2414
+ }
2415
+
2416
+ /**
2417
+ * Create a CapGroup from JSON representation
2418
+ * @param {Object} json - The JSON data
2419
+ * @returns {CapGroup} The CapGroup instance
2420
+ */
2421
+ static fromJSON(json) {
2422
+ if (!json.name) {
2423
+ throw new Error('CapGroup missing required field: name');
2424
+ }
2425
+ const caps = Array.isArray(json.caps) ? json.caps.map(c => Cap.fromJSON(c)) : [];
2426
+ const adapterUrns = Array.isArray(json.adapter_urns) ? json.adapter_urns : [];
2427
+ return new CapGroup(json.name, caps, adapterUrns);
2428
+ }
2429
+
2430
+ /**
2431
+ * Convert to JSON representation
2432
+ * @returns {Object} The JSON representation
2433
+ */
2434
+ toJSON() {
2435
+ return {
2436
+ name: this.name,
2437
+ caps: this.caps.map(c => c.toJSON()),
2438
+ adapter_urns: this.adapter_urns
2439
+ };
2440
+ }
2441
+ }
2442
+
2443
+ /**
2444
+ * Unified cap-based manifest for components (providers and cartridges).
2445
+ *
2446
+ * `(registry_url, channel, name, version)` is the cartridge's full
2447
+ * identity — each (registry, channel) pair is an independent namespace.
2448
+ * Mirrors CSCapManifest / capdag::cap_manifest::CapManifest.
2449
+ */
2450
+ class CapManifest {
2451
+ /**
2452
+ * @param {string} name - Component name
2453
+ * @param {string} version - Semver version string
2454
+ * @param {string} channel - Distribution channel: 'release' or 'nightly'
2455
+ * @param {string|null} registryUrl - Verbatim registry URL, or null for dev builds
2456
+ * @param {string} description - Short plain-text description
2457
+ * @param {CapGroup[]} capGroups - Cap groups (all caps must be in a group)
2458
+ */
2459
+ constructor(name, version, channel, registryUrl, description, capGroups = []) {
2460
+ if (!name || typeof name !== 'string') {
2461
+ throw new Error('CapManifest name is required and must be a string');
2462
+ }
2463
+ if (!version || typeof version !== 'string') {
2464
+ throw new Error('CapManifest version is required and must be a string');
2465
+ }
2466
+ if (channel !== 'release' && channel !== 'nightly') {
2467
+ throw new Error(`CapManifest channel must be 'release' or 'nightly', got: '${channel}'`);
2468
+ }
2469
+ if (registryUrl !== null && registryUrl !== undefined && typeof registryUrl !== 'string') {
2470
+ throw new Error("CapManifest registry_url must be null (dev build) or a string");
2471
+ }
2472
+ if (!description || typeof description !== 'string') {
2473
+ throw new Error('CapManifest description is required and must be a string');
2474
+ }
2475
+
2476
+ this.name = name;
2477
+ this.version = version;
2478
+ this.channel = channel;
2479
+ this.registry_url = registryUrl !== undefined ? registryUrl : null;
2480
+ this.description = description;
2481
+ this.cap_groups = capGroups;
2482
+ this.author = null;
2483
+ this.page_url = null;
2484
+ }
2485
+
2486
+ /**
2487
+ * Returns all caps flattened across all cap groups.
2488
+ * @returns {Cap[]}
2489
+ */
2490
+ allCaps() {
2491
+ return this.cap_groups.flatMap(g => g.caps);
2492
+ }
2493
+
2494
+ /**
2495
+ * Create a CapManifest from JSON / dictionary representation.
2496
+ *
2497
+ * `registry_url` must be present as a key; it may be `null` (dev build)
2498
+ * or a non-empty string (registry build). A missing key is a hard parse
2499
+ * error so old-schema payloads never silently pass.
2500
+ *
2501
+ * @param {Object} json - The JSON data
2502
+ * @returns {CapManifest} The CapManifest instance
2503
+ * @throws {Error} If required fields are missing or invalid
2504
+ */
2505
+ static fromJSON(json) {
2506
+ if (!json.name) throw new Error('CapManifest missing required field: name');
2507
+ if (!json.version) throw new Error('CapManifest missing required field: version');
2508
+ if (!json.channel) throw new Error('CapManifest missing required field: channel');
2509
+ if (!json.description) throw new Error('CapManifest missing required field: description');
2510
+ if (!Array.isArray(json.cap_groups)) throw new Error('CapManifest missing required field: cap_groups');
2511
+
2512
+ // registry_url must be present as a key (may be null for dev builds)
2513
+ if (!Object.prototype.hasOwnProperty.call(json, 'registry_url')) {
2514
+ throw new Error(
2515
+ 'CapManifest missing required field: registry_url. ' +
2516
+ 'It must be present with value null for dev builds or a URL string for registry builds.'
2517
+ );
2518
+ }
2519
+
2520
+ if (json.channel !== 'release' && json.channel !== 'nightly') {
2521
+ throw new Error(`CapManifest channel must be 'release' or 'nightly', got: '${json.channel}'`);
2522
+ }
2523
+
2524
+ const registryUrl = (json.registry_url !== null && json.registry_url !== undefined)
2525
+ ? json.registry_url
2526
+ : null;
2527
+
2528
+ if (registryUrl !== null && typeof registryUrl !== 'string') {
2529
+ throw new Error("CapManifest registry_url must be null or a string");
2530
+ }
2531
+
2532
+ const capGroups = json.cap_groups.map(g => CapGroup.fromJSON(g));
2533
+ const manifest = new CapManifest(
2534
+ json.name,
2535
+ json.version,
2536
+ json.channel,
2537
+ registryUrl,
2538
+ json.description,
2539
+ capGroups
2540
+ );
2541
+
2542
+ if (json.author && typeof json.author === 'string') {
2543
+ manifest.author = json.author;
2544
+ }
2545
+ if (json.page_url && typeof json.page_url === 'string') {
2546
+ manifest.page_url = json.page_url;
2547
+ }
2548
+
2549
+ return manifest;
2550
+ }
2551
+
2552
+ /**
2553
+ * Convert to JSON representation
2554
+ * @returns {Object} The JSON representation
2555
+ */
2556
+ toJSON() {
2557
+ const result = {
2558
+ name: this.name,
2559
+ version: this.version,
2560
+ channel: this.channel,
2561
+ registry_url: this.registry_url,
2562
+ description: this.description,
2563
+ cap_groups: this.cap_groups.map(g => g.toJSON())
2564
+ };
2565
+ if (this.author) result.author = this.author;
2566
+ if (this.page_url) result.page_url = this.page_url;
2567
+ return result;
2568
+ }
2569
+ }
2570
+
2334
2571
  /**
2335
2572
  * Helper functions for creating capabilities
2336
2573
  */
@@ -4283,6 +4520,195 @@ class CartridgeRepoServer {
4283
4520
  }
4284
4521
  }
4285
4522
 
4523
+ // ============================================================================
4524
+ // Bifaci — cartridge attachment & runtime identity types
4525
+ // ============================================================================
4526
+
4527
+ /**
4528
+ * Reasons why a cartridge attachment attempt failed.
4529
+ * Mirrors Rust CartridgeAttachmentErrorKind.
4530
+ */
4531
+ const CartridgeAttachmentErrorKind = Object.freeze({
4532
+ INCOMPATIBLE: 'incompatible',
4533
+ MANIFEST_INVALID: 'manifest_invalid',
4534
+ HANDSHAKE_FAILED: 'handshake_failed',
4535
+ IDENTITY_REJECTED: 'identity_rejected',
4536
+ ENTRY_POINT_MISSING: 'entry_point_missing',
4537
+ QUARANTINED: 'quarantined',
4538
+ });
4539
+
4540
+ /**
4541
+ * Describes a failed cartridge attachment attempt.
4542
+ * Mirrors Rust CartridgeAttachmentError.
4543
+ */
4544
+ class CartridgeAttachmentError {
4545
+ /**
4546
+ * @param {string} kind - CartridgeAttachmentErrorKind value
4547
+ * @param {string} message - Human-readable error message
4548
+ * @param {number|null} detectedAtUnixSeconds - Unix timestamp of detection
4549
+ */
4550
+ constructor(kind, message, detectedAtUnixSeconds) {
4551
+ this.kind = kind;
4552
+ this.message = message;
4553
+ this.detected_at_unix_seconds = detectedAtUnixSeconds;
4554
+ }
4555
+
4556
+ toJSON() {
4557
+ return {
4558
+ kind: this.kind,
4559
+ message: this.message,
4560
+ detected_at_unix_seconds: this.detected_at_unix_seconds,
4561
+ };
4562
+ }
4563
+
4564
+ static fromJSON(d) {
4565
+ return new CartridgeAttachmentError(d.kind, d.message, d.detected_at_unix_seconds);
4566
+ }
4567
+ }
4568
+
4569
+ /**
4570
+ * Runtime statistics for a running (or stopped) cartridge process.
4571
+ * Mirrors Rust CartridgeRuntimeStats.
4572
+ */
4573
+ class CartridgeRuntimeStats {
4574
+ /**
4575
+ * @param {Object} opts
4576
+ * @param {boolean} [opts.running=false]
4577
+ * @param {number|null} [opts.pid=null]
4578
+ * @param {number} [opts.activeRequestCount=0]
4579
+ * @param {number} [opts.peerRequestCount=0]
4580
+ * @param {number} [opts.memoryFootprintMb=0]
4581
+ * @param {number} [opts.memoryRssMb=0]
4582
+ * @param {number|null} [opts.lastHeartbeatUnixSeconds=null]
4583
+ * @param {number} [opts.restartCount=0]
4584
+ */
4585
+ constructor({
4586
+ running = false,
4587
+ pid = null,
4588
+ activeRequestCount = 0,
4589
+ peerRequestCount = 0,
4590
+ memoryFootprintMb = 0,
4591
+ memoryRssMb = 0,
4592
+ lastHeartbeatUnixSeconds = null,
4593
+ restartCount = 0,
4594
+ } = {}) {
4595
+ this.running = running;
4596
+ this.pid = pid;
4597
+ this.active_request_count = activeRequestCount;
4598
+ this.peer_request_count = peerRequestCount;
4599
+ this.memory_footprint_mb = memoryFootprintMb;
4600
+ this.memory_rss_mb = memoryRssMb;
4601
+ this.last_heartbeat_unix_seconds = lastHeartbeatUnixSeconds;
4602
+ this.restart_count = restartCount;
4603
+ }
4604
+
4605
+ /** Convenience constructor for a cartridge that is not running. */
4606
+ static notRunning() {
4607
+ return new CartridgeRuntimeStats();
4608
+ }
4609
+
4610
+ toJSON() {
4611
+ const obj = { running: this.running };
4612
+ if (this.pid !== null) obj.pid = this.pid;
4613
+ if (this.active_request_count) obj.active_request_count = this.active_request_count;
4614
+ if (this.peer_request_count) obj.peer_request_count = this.peer_request_count;
4615
+ if (this.memory_footprint_mb) obj.memory_footprint_mb = this.memory_footprint_mb;
4616
+ if (this.memory_rss_mb) obj.memory_rss_mb = this.memory_rss_mb;
4617
+ if (this.last_heartbeat_unix_seconds !== null) obj.last_heartbeat_unix_seconds = this.last_heartbeat_unix_seconds;
4618
+ if (this.restart_count) obj.restart_count = this.restart_count;
4619
+ return obj;
4620
+ }
4621
+
4622
+ static fromJSON(d) {
4623
+ return new CartridgeRuntimeStats({
4624
+ running: d.running || false,
4625
+ pid: d.pid !== undefined ? d.pid : null,
4626
+ activeRequestCount: d.active_request_count || 0,
4627
+ peerRequestCount: d.peer_request_count || 0,
4628
+ memoryFootprintMb: d.memory_footprint_mb || 0,
4629
+ memoryRssMb: d.memory_rss_mb || 0,
4630
+ lastHeartbeatUnixSeconds: d.last_heartbeat_unix_seconds !== undefined ? d.last_heartbeat_unix_seconds : null,
4631
+ restartCount: d.restart_count || 0,
4632
+ });
4633
+ }
4634
+ }
4635
+
4636
+ /**
4637
+ * Full identity of an installed cartridge, including optional attachment error
4638
+ * and runtime statistics.
4639
+ * Mirrors Rust InstalledCartridgeIdentity.
4640
+ */
4641
+ class InstalledCartridgeIdentity {
4642
+ /**
4643
+ * @param {Object} opts
4644
+ * @param {string|null} [opts.registryUrl=null]
4645
+ * @param {string} opts.channel
4646
+ * @param {string} opts.id
4647
+ * @param {string} opts.version
4648
+ * @param {string} opts.sha256
4649
+ * @param {Array<Object>} [opts.capGroups=[]] - Cartridge's manifest cap_groups; each element is `{name, caps, adapter_urns}`.
4650
+ * @param {CartridgeAttachmentError|null} [opts.attachmentError=null]
4651
+ * @param {CartridgeRuntimeStats|null} [opts.runtimeStats=null]
4652
+ */
4653
+ constructor({ registryUrl = null, channel, id, version, sha256, capGroups = [], attachmentError = null, runtimeStats = null }) {
4654
+ this.registry_url = registryUrl;
4655
+ this.channel = channel;
4656
+ this.id = id;
4657
+ this.version = version;
4658
+ this.sha256 = sha256;
4659
+ this.cap_groups = capGroups;
4660
+ this.attachment_error = attachmentError;
4661
+ this.runtime_stats = runtimeStats;
4662
+ }
4663
+
4664
+ toJSON() {
4665
+ const obj = {
4666
+ channel: this.channel,
4667
+ id: this.id,
4668
+ version: this.version,
4669
+ sha256: this.sha256,
4670
+ };
4671
+ if (this.registry_url !== null) obj.registry_url = this.registry_url;
4672
+ if (this.cap_groups && this.cap_groups.length > 0) obj.cap_groups = this.cap_groups;
4673
+ if (this.attachment_error !== null) obj.attachment_error = this.attachment_error.toJSON();
4674
+ if (this.runtime_stats !== null) obj.runtime_stats = this.runtime_stats.toJSON();
4675
+ return obj;
4676
+ }
4677
+
4678
+ static fromJSON(d) {
4679
+ return new InstalledCartridgeIdentity({
4680
+ registryUrl: d.registry_url !== undefined ? d.registry_url : null,
4681
+ channel: d.channel,
4682
+ id: d.id,
4683
+ version: d.version,
4684
+ sha256: d.sha256,
4685
+ capGroups: Array.isArray(d.cap_groups) ? d.cap_groups : [],
4686
+ attachmentError: d.attachment_error ? CartridgeAttachmentError.fromJSON(d.attachment_error) : null,
4687
+ runtimeStats: d.runtime_stats ? CartridgeRuntimeStats.fromJSON(d.runtime_stats) : null,
4688
+ });
4689
+ }
4690
+
4691
+ /**
4692
+ * Flat de-duplicated cap-URN view across this cartridge's groups,
4693
+ * preserving first-seen order.
4694
+ * @returns {Array<string>}
4695
+ */
4696
+ capUrns() {
4697
+ const seen = new Set();
4698
+ const out = [];
4699
+ for (const group of this.cap_groups) {
4700
+ const caps = (group && Array.isArray(group.caps)) ? group.caps : [];
4701
+ for (const cap of caps) {
4702
+ const urn = (cap && typeof cap.urn === 'string') ? cap.urn : '';
4703
+ if (!urn || seen.has(urn)) continue;
4704
+ seen.add(urn);
4705
+ out.push(urn);
4706
+ }
4707
+ }
4708
+ return out;
4709
+ }
4710
+ }
4711
+
4286
4712
  // ============================================================================
4287
4713
  // Machine Notation — compact, round-trippable DAG path identifiers
4288
4714
  //
@@ -5393,6 +5819,8 @@ module.exports = {
5393
5819
  MediaUrnError,
5394
5820
  MediaUrnErrorCodes,
5395
5821
  Cap,
5822
+ CapGroup,
5823
+ CapManifest,
5396
5824
  CapArg,
5397
5825
  ArgSource,
5398
5826
  RegisteredBy,
@@ -5437,8 +5865,25 @@ module.exports = {
5437
5865
  MEDIA_IDENTITY,
5438
5866
  MEDIA_VOID,
5439
5867
  MEDIA_PNG,
5868
+ MEDIA_JPEG,
5869
+ MEDIA_GIF,
5870
+ MEDIA_BMP,
5871
+ MEDIA_TIFF,
5872
+ MEDIA_WEBP,
5440
5873
  MEDIA_AUDIO,
5874
+ MEDIA_MP3,
5875
+ MEDIA_WAV,
5876
+ MEDIA_FLAC,
5877
+ MEDIA_OGG,
5878
+ MEDIA_AAC,
5879
+ MEDIA_M4A,
5880
+ MEDIA_AIFF,
5881
+ MEDIA_OPUS,
5441
5882
  MEDIA_VIDEO,
5883
+ MEDIA_MP4,
5884
+ MEDIA_MOV,
5885
+ MEDIA_WEBM,
5886
+ MEDIA_MKV,
5442
5887
  MEDIA_AUDIO_SPEECH,
5443
5888
  // Document types (PRIMARY naming)
5444
5889
  MEDIA_PDF,
@@ -5484,6 +5929,12 @@ module.exports = {
5484
5929
  // File path type — single URN; cardinality lives on is_sequence.
5485
5930
  MEDIA_FILE_PATH,
5486
5931
  MEDIA_MLX_MODEL_PATH,
5932
+ // HF token and model search types
5933
+ MEDIA_HF_TOKEN,
5934
+ MEDIA_MODEL_ARCH_LIST,
5935
+ MEDIA_MODEL_SEARCH_REQUEST,
5936
+ MEDIA_MODEL_SEARCH_RESPONSE,
5937
+ MEDIA_MODEL_FILTER_RESOLUTION,
5487
5938
  // Collection types
5488
5939
  MEDIA_COLLECTION,
5489
5940
  MEDIA_COLLECTION_LIST,
@@ -5514,6 +5965,11 @@ module.exports = {
5514
5965
  CartridgeRepoClient,
5515
5966
  CartridgeRepoServer,
5516
5967
  CartridgeChannel,
5968
+ // Bifaci — cartridge attachment & runtime identity
5969
+ CartridgeAttachmentErrorKind,
5970
+ CartridgeAttachmentError,
5971
+ CartridgeRuntimeStats,
5972
+ InstalledCartridgeIdentity,
5517
5973
  // Registry slug
5518
5974
  DEV_SLUG,
5519
5975
  SLUG_HEX_LEN,