augmem.cortext 1.1.6__py3-none-any.whl

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.
@@ -0,0 +1,1310 @@
1
+ from __future__ import annotations
2
+
3
+ import ctypes
4
+ import ctypes.util
5
+ import json
6
+ import os
7
+ import platform
8
+ from array import array
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Any, Iterable
12
+
13
+ __all__ = [
14
+ "Config",
15
+ "Cortext",
16
+ "CortextError",
17
+ "DBProvider",
18
+ "Media",
19
+ "ObjectStoreProvider",
20
+ "last_error",
21
+ "load_library",
22
+ "version",
23
+ ]
24
+
25
+
26
+ class CortextError(RuntimeError):
27
+ pass
28
+
29
+
30
+ @dataclass(slots=True)
31
+ class Config:
32
+ focus: float = 0.5
33
+ sensitivity: float = 0.5
34
+ stability: float = 0.5
35
+ affect_interrupt: bool = True
36
+ affect_retrieval: bool = True
37
+ reinforcement_enabled: bool = True
38
+ procedural_enabled: bool = True
39
+ sequential_edges_enabled: bool = True
40
+ signal_filter_audio_enabled: bool = True
41
+ signal_filter_image_enabled: bool = True
42
+ signal_filter_text_enabled: bool = False
43
+
44
+
45
+ @dataclass(frozen=True, slots=True)
46
+ class Media:
47
+ data: bytes | bytearray | memoryview
48
+ mimetype: str
49
+
50
+
51
+ class _NativeConfig(ctypes.Structure):
52
+ _fields_ = [
53
+ ("struct_size", ctypes.c_size_t),
54
+ ("focus", ctypes.c_double),
55
+ ("sensitivity", ctypes.c_double),
56
+ ("stability", ctypes.c_double),
57
+ ("affect_interrupt", ctypes.c_int),
58
+ ("affect_retrieval", ctypes.c_int),
59
+ ("reinforcement_enabled", ctypes.c_int),
60
+ ("procedural_enabled", ctypes.c_int),
61
+ ("sequential_edges_enabled", ctypes.c_int),
62
+ ("signal_filter_audio_enabled", ctypes.c_int),
63
+ ("signal_filter_image_enabled", ctypes.c_int),
64
+ ("signal_filter_text_enabled", ctypes.c_int),
65
+ ]
66
+
67
+
68
+ class _NativeMedia(ctypes.Structure):
69
+ _fields_ = [
70
+ ("data", ctypes.POINTER(ctypes.c_uint8)),
71
+ ("size", ctypes.c_size_t),
72
+ ("mimetype", ctypes.c_char_p),
73
+ ]
74
+
75
+
76
+ class _NativeProcessJsonOptions(ctypes.Structure):
77
+ _fields_ = [
78
+ ("struct_size", ctypes.c_size_t),
79
+ ("include_embedding", ctypes.c_int),
80
+ ]
81
+
82
+
83
+ CORTEXT_DB_VALUE_NULL = 0
84
+ CORTEXT_DB_VALUE_INT64 = 1
85
+ CORTEXT_DB_VALUE_DOUBLE = 2
86
+ CORTEXT_DB_VALUE_TEXT = 3
87
+ CORTEXT_DB_VALUE_BLOB = 4
88
+
89
+
90
+ class _DBValue(ctypes.Structure):
91
+ _fields_ = [
92
+ ("type", ctypes.c_int),
93
+ ("int64_value", ctypes.c_int64),
94
+ ("double_value", ctypes.c_double),
95
+ ("text_value", ctypes.c_char_p),
96
+ ("blob_data", ctypes.POINTER(ctypes.c_uint8)),
97
+ ("blob_size", ctypes.c_size_t),
98
+ ]
99
+
100
+
101
+ class _DBCell(ctypes.Structure):
102
+ pass
103
+
104
+
105
+ class _DBRow(ctypes.Structure):
106
+ pass
107
+
108
+
109
+ class _DBResult(ctypes.Structure):
110
+ pass
111
+
112
+
113
+ _DBCell._fields_ = [("column_name", ctypes.c_char_p), ("value", _DBValue)]
114
+ _DBRow._fields_ = [
115
+ ("cells", ctypes.POINTER(_DBCell)),
116
+ ("cell_count", ctypes.c_size_t),
117
+ ]
118
+ _DBResult._fields_ = [
119
+ ("rows", ctypes.POINTER(_DBRow)),
120
+ ("row_count", ctypes.c_size_t),
121
+ ]
122
+
123
+
124
+ _ExecuteCallback = ctypes.CFUNCTYPE(
125
+ ctypes.c_int,
126
+ ctypes.c_void_p,
127
+ ctypes.c_void_p,
128
+ ctypes.c_char_p,
129
+ ctypes.POINTER(_DBValue),
130
+ ctypes.c_size_t,
131
+ ctypes.POINTER(_DBResult),
132
+ ctypes.POINTER(ctypes.c_char_p),
133
+ )
134
+ _BeginCallback = ctypes.CFUNCTYPE(
135
+ ctypes.c_int,
136
+ ctypes.c_void_p,
137
+ ctypes.c_void_p,
138
+ ctypes.POINTER(ctypes.c_void_p),
139
+ ctypes.POINTER(ctypes.c_char_p),
140
+ )
141
+ _TransactionCallback = ctypes.CFUNCTYPE(
142
+ ctypes.c_int,
143
+ ctypes.c_void_p,
144
+ ctypes.c_void_p,
145
+ ctypes.POINTER(ctypes.c_char_p),
146
+ )
147
+ _ReleaseTransactionCallback = ctypes.CFUNCTYPE(
148
+ None,
149
+ ctypes.c_void_p,
150
+ ctypes.c_void_p,
151
+ )
152
+ _ReleaseResultCallback = ctypes.CFUNCTYPE(
153
+ None,
154
+ ctypes.c_void_p,
155
+ ctypes.POINTER(_DBResult),
156
+ )
157
+ _ObjectBeginCallback = ctypes.CFUNCTYPE(
158
+ ctypes.c_int,
159
+ ctypes.c_void_p,
160
+ ctypes.c_void_p,
161
+ ctypes.POINTER(ctypes.c_void_p),
162
+ ctypes.POINTER(ctypes.c_char_p),
163
+ )
164
+ _ObjectPutCallback = ctypes.CFUNCTYPE(
165
+ ctypes.c_int,
166
+ ctypes.c_void_p,
167
+ ctypes.c_void_p,
168
+ ctypes.POINTER(ctypes.c_uint8),
169
+ ctypes.c_size_t,
170
+ ctypes.POINTER(ctypes.c_uint8),
171
+ ctypes.c_size_t,
172
+ ctypes.POINTER(ctypes.c_char_p),
173
+ )
174
+ _ObjectGetCallback = ctypes.CFUNCTYPE(
175
+ ctypes.c_int,
176
+ ctypes.c_void_p,
177
+ ctypes.c_void_p,
178
+ ctypes.POINTER(ctypes.c_uint8),
179
+ ctypes.c_size_t,
180
+ ctypes.POINTER(ctypes.POINTER(ctypes.c_uint8)),
181
+ ctypes.POINTER(ctypes.c_size_t),
182
+ ctypes.POINTER(ctypes.c_char_p),
183
+ )
184
+ _ObjectExistsCallback = ctypes.CFUNCTYPE(
185
+ ctypes.c_int,
186
+ ctypes.c_void_p,
187
+ ctypes.c_void_p,
188
+ ctypes.POINTER(ctypes.c_uint8),
189
+ ctypes.c_size_t,
190
+ ctypes.POINTER(ctypes.c_int),
191
+ ctypes.POINTER(ctypes.c_char_p),
192
+ )
193
+ _ObjectDeleteCallback = ctypes.CFUNCTYPE(
194
+ ctypes.c_int,
195
+ ctypes.c_void_p,
196
+ ctypes.c_void_p,
197
+ ctypes.POINTER(ctypes.c_uint8),
198
+ ctypes.c_size_t,
199
+ ctypes.POINTER(ctypes.c_int),
200
+ ctypes.POINTER(ctypes.c_char_p),
201
+ )
202
+ _ObjectTransactionCallback = ctypes.CFUNCTYPE(
203
+ ctypes.c_int,
204
+ ctypes.c_void_p,
205
+ ctypes.c_void_p,
206
+ ctypes.POINTER(ctypes.c_char_p),
207
+ )
208
+ _ObjectReleaseTransactionCallback = ctypes.CFUNCTYPE(
209
+ None,
210
+ ctypes.c_void_p,
211
+ ctypes.c_void_p,
212
+ )
213
+ _ObjectReleaseBlobCallback = ctypes.CFUNCTYPE(
214
+ None,
215
+ ctypes.c_void_p,
216
+ ctypes.POINTER(ctypes.c_uint8),
217
+ ctypes.c_size_t,
218
+ )
219
+
220
+
221
+ class _DBCallbacks(ctypes.Structure):
222
+ _fields_ = [
223
+ ("struct_size", ctypes.c_size_t),
224
+ ("execute", _ExecuteCallback),
225
+ ("begin", _BeginCallback),
226
+ ("commit", _TransactionCallback),
227
+ ("rollback", _TransactionCallback),
228
+ ("release_transaction", _ReleaseTransactionCallback),
229
+ ("release_result", _ReleaseResultCallback),
230
+ ]
231
+
232
+
233
+ class _ObjectCallbacks(ctypes.Structure):
234
+ _fields_ = [
235
+ ("struct_size", ctypes.c_size_t),
236
+ ("begin", _ObjectBeginCallback),
237
+ ("put", _ObjectPutCallback),
238
+ ("get", _ObjectGetCallback),
239
+ ("exists", _ObjectExistsCallback),
240
+ ("delete_object", _ObjectDeleteCallback),
241
+ ("commit", _ObjectTransactionCallback),
242
+ ("rollback", _ObjectTransactionCallback),
243
+ ("release_transaction", _ObjectReleaseTransactionCallback),
244
+ ("release_blob", _ObjectReleaseBlobCallback),
245
+ ]
246
+
247
+
248
+ class DBProvider:
249
+ def execute(self, query: str, params: list[Any], transaction: Any | None) -> list[dict[str, Any]]:
250
+ raise NotImplementedError
251
+
252
+ def begin(self, parent_transaction: Any | None) -> Any:
253
+ raise NotImplementedError
254
+
255
+ def commit(self, transaction: Any) -> None:
256
+ raise NotImplementedError
257
+
258
+ def rollback(self, transaction: Any) -> None:
259
+ raise NotImplementedError
260
+
261
+ def release_transaction(self, transaction: Any) -> None:
262
+ pass
263
+
264
+
265
+ class ObjectStoreProvider:
266
+ def begin(self, parent_transaction: Any | None) -> Any:
267
+ raise NotImplementedError
268
+
269
+ def put(self, object_id: bytes, data: bytes, transaction: Any) -> None:
270
+ raise NotImplementedError
271
+
272
+ def get(self, object_id: bytes, transaction: Any) -> bytes | None:
273
+ raise NotImplementedError
274
+
275
+ def exists(self, object_id: bytes, transaction: Any) -> bool:
276
+ raise NotImplementedError
277
+
278
+ def delete(self, object_id: bytes, transaction: Any) -> bool:
279
+ raise NotImplementedError
280
+
281
+ def commit(self, transaction: Any) -> None:
282
+ raise NotImplementedError
283
+
284
+ def rollback(self, transaction: Any) -> None:
285
+ raise NotImplementedError
286
+
287
+ def release_transaction(self, transaction: Any) -> None:
288
+ pass
289
+
290
+
291
+ _SUPPORTED_NATIVE_TARGETS = {
292
+ "linux-aarch64",
293
+ "linux-x86_64",
294
+ "macos-aarch64",
295
+ "macos-x86_64",
296
+ "windows-aarch64",
297
+ "windows-x86_64",
298
+ }
299
+
300
+ _DLL_DIRECTORY_HANDLES: list[Any] = []
301
+
302
+
303
+ def _package_dir() -> Path:
304
+ return Path(__file__).resolve().parent
305
+
306
+
307
+ def _platform_tag() -> str:
308
+ system = platform.system().lower()
309
+ if system == "darwin":
310
+ os_tag = "macos"
311
+ elif system == "linux":
312
+ os_tag = "linux"
313
+ elif system == "windows":
314
+ os_tag = "windows"
315
+ else:
316
+ os_tag = system or "unknown"
317
+
318
+ machine = platform.machine().lower()
319
+ if machine in {"amd64", "x64"}:
320
+ arch = "x86_64"
321
+ elif machine == "arm64":
322
+ arch = "aarch64"
323
+ else:
324
+ arch = machine or "unknown"
325
+
326
+ return f"{os_tag}-{arch}"
327
+
328
+
329
+ def _library_name_for_tag(tag: str) -> str:
330
+ os_tag = tag.split("-", 1)[0]
331
+ if os_tag == "macos":
332
+ return "libcortext.dylib"
333
+ if os_tag == "windows":
334
+ return "cortext.dll"
335
+ return "libcortext.so"
336
+
337
+
338
+ def _repo_root() -> Path | None:
339
+ for parent in (_package_dir(), *_package_dir().parents):
340
+ if (parent / "build.zig").is_file() and (parent / "CMakeLists.txt").is_file():
341
+ return parent
342
+ return None
343
+
344
+
345
+ def _candidate_library_paths() -> list[Path]:
346
+ names = ("libcortext.dylib", "libcortext.so", "cortext.dll", "libcortext.dll")
347
+ candidates: list[Path] = []
348
+
349
+ env_path = os.environ.get("CORTEXT_LIBRARY_PATH")
350
+ if env_path:
351
+ candidates.append(Path(env_path).expanduser())
352
+
353
+ tag = _platform_tag()
354
+ if tag in _SUPPORTED_NATIVE_TARGETS:
355
+ candidates.append(_package_dir() / "native" / tag / _library_name_for_tag(tag))
356
+
357
+ for name in names:
358
+ candidates.append(_package_dir() / name)
359
+
360
+ root = _repo_root()
361
+ if root is not None:
362
+ for directory in (
363
+ root / "build" / "ffi-release",
364
+ root / "build" / "ffi-release" / "lib",
365
+ root / "zig-out" / "lib",
366
+ root / "zig-out" / "bin",
367
+ root / "install" / "lib",
368
+ root / "install" / "bin",
369
+ ):
370
+ for name in names:
371
+ candidates.append(directory / name)
372
+
373
+ return candidates
374
+
375
+
376
+ def _load_cdll(path: Path | str) -> ctypes.CDLL:
377
+ library_path = Path(path) if not isinstance(path, str) else Path(path)
378
+ if os.name == "nt" and library_path.parent and hasattr(os, "add_dll_directory"):
379
+ _DLL_DIRECTORY_HANDLES.append(os.add_dll_directory(os.fspath(library_path.parent)))
380
+ return ctypes.CDLL(os.fspath(path))
381
+
382
+
383
+ def _configure_library(lib: ctypes.CDLL) -> ctypes.CDLL:
384
+ lib.cortext_config_init.argtypes = [ctypes.POINTER(_NativeConfig)]
385
+ lib.cortext_config_init.restype = None
386
+
387
+ lib.cortext_create_with_config.argtypes = [
388
+ ctypes.POINTER(_NativeConfig),
389
+ ctypes.c_char_p,
390
+ ctypes.c_char_p,
391
+ ]
392
+ lib.cortext_create_with_config.restype = ctypes.c_void_p
393
+
394
+ lib.cortext_create_with_store_callbacks.argtypes = [
395
+ ctypes.POINTER(_NativeConfig),
396
+ ctypes.POINTER(_DBCallbacks),
397
+ ctypes.c_void_p,
398
+ ctypes.c_char_p,
399
+ ]
400
+ lib.cortext_create_with_store_callbacks.restype = ctypes.c_void_p
401
+
402
+ lib.cortext_create_with_store_and_object_callbacks.argtypes = [
403
+ ctypes.POINTER(_NativeConfig),
404
+ ctypes.POINTER(_DBCallbacks),
405
+ ctypes.c_void_p,
406
+ ctypes.POINTER(_ObjectCallbacks),
407
+ ctypes.c_void_p,
408
+ ctypes.c_char_p,
409
+ ]
410
+ lib.cortext_create_with_store_and_object_callbacks.restype = ctypes.c_void_p
411
+
412
+ lib.cortext_create_with_config_and_object_callbacks.argtypes = [
413
+ ctypes.POINTER(_NativeConfig),
414
+ ctypes.c_char_p,
415
+ ctypes.POINTER(_ObjectCallbacks),
416
+ ctypes.c_void_p,
417
+ ctypes.c_char_p,
418
+ ]
419
+ lib.cortext_create_with_config_and_object_callbacks.restype = ctypes.c_void_p
420
+
421
+ lib.cortext_free.argtypes = [ctypes.c_void_p]
422
+ lib.cortext_free.restype = None
423
+
424
+ lib.cortext_process_text_json.argtypes = [
425
+ ctypes.c_void_p,
426
+ ctypes.c_char_p,
427
+ ctypes.c_char_p,
428
+ ]
429
+ lib.cortext_process_text_json.restype = ctypes.c_void_p
430
+
431
+ lib.cortext_process_text_json_with_options.argtypes = [
432
+ ctypes.c_void_p,
433
+ ctypes.c_char_p,
434
+ ctypes.c_char_p,
435
+ ctypes.POINTER(_NativeProcessJsonOptions),
436
+ ]
437
+ lib.cortext_process_text_json_with_options.restype = ctypes.c_void_p
438
+
439
+ lib.cortext_process_audio_json.argtypes = [
440
+ ctypes.c_void_p,
441
+ ctypes.POINTER(ctypes.c_float),
442
+ ctypes.c_size_t,
443
+ ctypes.c_char_p,
444
+ ]
445
+ lib.cortext_process_audio_json.restype = ctypes.c_void_p
446
+
447
+ lib.cortext_process_audio_json_with_options.argtypes = [
448
+ ctypes.c_void_p,
449
+ ctypes.POINTER(ctypes.c_float),
450
+ ctypes.c_size_t,
451
+ ctypes.c_char_p,
452
+ ctypes.POINTER(_NativeProcessJsonOptions),
453
+ ]
454
+ lib.cortext_process_audio_json_with_options.restype = ctypes.c_void_p
455
+
456
+ lib.cortext_process_audio_with_media_json.argtypes = [
457
+ ctypes.c_void_p,
458
+ ctypes.POINTER(ctypes.c_float),
459
+ ctypes.c_size_t,
460
+ ctypes.c_char_p,
461
+ ctypes.POINTER(_NativeMedia),
462
+ ]
463
+ lib.cortext_process_audio_with_media_json.restype = ctypes.c_void_p
464
+
465
+ lib.cortext_process_audio_with_media_json_with_options.argtypes = [
466
+ ctypes.c_void_p,
467
+ ctypes.POINTER(ctypes.c_float),
468
+ ctypes.c_size_t,
469
+ ctypes.c_char_p,
470
+ ctypes.POINTER(_NativeMedia),
471
+ ctypes.POINTER(_NativeProcessJsonOptions),
472
+ ]
473
+ lib.cortext_process_audio_with_media_json_with_options.restype = ctypes.c_void_p
474
+
475
+ lib.cortext_process_image_json.argtypes = [
476
+ ctypes.c_void_p,
477
+ ctypes.POINTER(ctypes.c_uint8),
478
+ ctypes.c_int,
479
+ ctypes.c_int,
480
+ ctypes.c_int,
481
+ ctypes.c_char_p,
482
+ ]
483
+ lib.cortext_process_image_json.restype = ctypes.c_void_p
484
+
485
+ lib.cortext_process_image_json_with_options.argtypes = [
486
+ ctypes.c_void_p,
487
+ ctypes.POINTER(ctypes.c_uint8),
488
+ ctypes.c_int,
489
+ ctypes.c_int,
490
+ ctypes.c_int,
491
+ ctypes.c_char_p,
492
+ ctypes.POINTER(_NativeProcessJsonOptions),
493
+ ]
494
+ lib.cortext_process_image_json_with_options.restype = ctypes.c_void_p
495
+
496
+ lib.cortext_process_image_with_media_json.argtypes = [
497
+ ctypes.c_void_p,
498
+ ctypes.POINTER(ctypes.c_uint8),
499
+ ctypes.c_int,
500
+ ctypes.c_int,
501
+ ctypes.c_int,
502
+ ctypes.c_char_p,
503
+ ctypes.POINTER(_NativeMedia),
504
+ ]
505
+ lib.cortext_process_image_with_media_json.restype = ctypes.c_void_p
506
+
507
+ lib.cortext_process_image_with_media_json_with_options.argtypes = [
508
+ ctypes.c_void_p,
509
+ ctypes.POINTER(ctypes.c_uint8),
510
+ ctypes.c_int,
511
+ ctypes.c_int,
512
+ ctypes.c_int,
513
+ ctypes.c_char_p,
514
+ ctypes.POINTER(_NativeMedia),
515
+ ctypes.POINTER(_NativeProcessJsonOptions),
516
+ ]
517
+ lib.cortext_process_image_with_media_json_with_options.restype = ctypes.c_void_p
518
+
519
+ lib.cortext_embed_text_json.argtypes = [
520
+ ctypes.c_void_p,
521
+ ctypes.c_char_p,
522
+ ]
523
+ lib.cortext_embed_text_json.restype = ctypes.c_void_p
524
+
525
+ lib.cortext_embed_audio_json.argtypes = [
526
+ ctypes.c_void_p,
527
+ ctypes.POINTER(ctypes.c_float),
528
+ ctypes.c_size_t,
529
+ ]
530
+ lib.cortext_embed_audio_json.restype = ctypes.c_void_p
531
+
532
+ lib.cortext_embed_image_json.argtypes = [
533
+ ctypes.c_void_p,
534
+ ctypes.POINTER(ctypes.c_uint8),
535
+ ctypes.c_int,
536
+ ctypes.c_int,
537
+ ctypes.c_int,
538
+ ]
539
+ lib.cortext_embed_image_json.restype = ctypes.c_void_p
540
+
541
+ lib.cortext_consolidate_json.argtypes = [ctypes.c_void_p]
542
+ lib.cortext_consolidate_json.restype = ctypes.c_void_p
543
+
544
+ lib.cortext_flush.argtypes = [ctypes.c_void_p]
545
+ lib.cortext_flush.restype = ctypes.c_int
546
+
547
+ lib.cortext_reset.argtypes = [ctypes.c_void_p]
548
+ lib.cortext_reset.restype = ctypes.c_int
549
+
550
+ lib.cortext_version.argtypes = []
551
+ lib.cortext_version.restype = ctypes.c_char_p
552
+
553
+ lib.cortext_last_error.argtypes = []
554
+ lib.cortext_last_error.restype = ctypes.c_char_p
555
+
556
+ lib.cortext_string_free.argtypes = [ctypes.c_void_p]
557
+ lib.cortext_string_free.restype = None
558
+ return lib
559
+
560
+
561
+ def load_library(path: str | os.PathLike[str] | None = None) -> ctypes.CDLL:
562
+ if path is not None:
563
+ return _configure_library(_load_cdll(Path(path)))
564
+
565
+ for candidate in _candidate_library_paths():
566
+ if candidate.exists():
567
+ return _configure_library(_load_cdll(candidate))
568
+
569
+ discovered = ctypes.util.find_library("cortext")
570
+ if discovered:
571
+ return _configure_library(ctypes.CDLL(discovered))
572
+
573
+ searched = "\n".join(str(candidate) for candidate in _candidate_library_paths())
574
+ supported = ", ".join(sorted(_SUPPORTED_NATIVE_TARGETS))
575
+ raise CortextError(
576
+ "Could not locate the Cortext shared library. "
577
+ f"Current platform tag is {_platform_tag()}; bundled wheels support: {supported}. "
578
+ "Build a wheel with `python scripts/build_python_package.py`, build a local shared "
579
+ "library with `cmake --preset ffi-release && cmake --build --preset ffi-release --target cortext`, "
580
+ "or set CORTEXT_LIBRARY_PATH.\n"
581
+ f"Searched:\n{searched}"
582
+ )
583
+
584
+
585
+ _LIB = load_library()
586
+
587
+
588
+ def last_error() -> str:
589
+ raw = _LIB.cortext_last_error()
590
+ return raw.decode("utf-8") if raw else ""
591
+
592
+
593
+ def version() -> str:
594
+ raw = _LIB.cortext_version()
595
+ return raw.decode("utf-8") if raw else ""
596
+
597
+
598
+ def _raise_last_error(prefix: str) -> None:
599
+ detail = last_error()
600
+ raise CortextError(f"{prefix}: {detail}" if detail else prefix)
601
+
602
+
603
+ def _native_media(
604
+ media: Media | bytes | bytearray | memoryview | None,
605
+ mimetype: str | None,
606
+ ) -> tuple[_NativeMedia, Any, bytes | None]:
607
+ if media is None:
608
+ return _NativeMedia(None, 0, None), None, None
609
+ if isinstance(media, Media):
610
+ if mimetype is None:
611
+ mimetype = media.mimetype
612
+ media = media.data
613
+ blob = bytes(media)
614
+ if not blob:
615
+ return _NativeMedia(None, 0, None), None, None
616
+ if not mimetype:
617
+ raise ValueError("media_mimetype is required when media bytes are provided")
618
+ raw = (ctypes.c_uint8 * len(blob)).from_buffer_copy(blob)
619
+ mimetype_bytes = mimetype.encode("utf-8")
620
+ return _NativeMedia(raw, len(blob), mimetype_bytes), raw, mimetype_bytes
621
+
622
+
623
+ def _native_process_json_options(include_embedding: bool) -> _NativeProcessJsonOptions:
624
+ return _NativeProcessJsonOptions(
625
+ ctypes.sizeof(_NativeProcessJsonOptions), 1 if include_embedding else 0
626
+ )
627
+
628
+
629
+ def _bool_to_int(value: bool) -> int:
630
+ return 1 if value else 0
631
+
632
+
633
+ def _encode_optional_string(value: str | None) -> ctypes.Array[ctypes.c_char] | None:
634
+ if value is None:
635
+ return None
636
+ return ctypes.create_string_buffer(value.encode("utf-8"))
637
+
638
+
639
+ class _ProviderBridge:
640
+ def __init__(self, provider: DBProvider) -> None:
641
+ self.provider = provider
642
+ self._transactions: dict[int, Any] = {}
643
+ self._next_transaction = 1
644
+ self._results: dict[int, Any] = {}
645
+ self._errors: list[ctypes.Array[ctypes.c_char]] = []
646
+
647
+ self.execute_cb = _ExecuteCallback(self._execute)
648
+ self.begin_cb = _BeginCallback(self._begin)
649
+ self.commit_cb = _TransactionCallback(self._commit)
650
+ self.rollback_cb = _TransactionCallback(self._rollback)
651
+ self.release_transaction_cb = _ReleaseTransactionCallback(
652
+ self._release_transaction
653
+ )
654
+ self.release_result_cb = _ReleaseResultCallback(self._release_result)
655
+ self.callbacks = _DBCallbacks(
656
+ ctypes.sizeof(_DBCallbacks),
657
+ self.execute_cb,
658
+ self.begin_cb,
659
+ self.commit_cb,
660
+ self.rollback_cb,
661
+ self.release_transaction_cb,
662
+ self.release_result_cb,
663
+ )
664
+
665
+ def _set_error(self, out_error: Any, message: str) -> int:
666
+ encoded = ctypes.create_string_buffer(message.encode("utf-8"))
667
+ self._errors.append(encoded)
668
+ if out_error:
669
+ out_error[0] = ctypes.cast(encoded, ctypes.c_char_p)
670
+ return 1
671
+
672
+ def _transaction(self, handle: int) -> Any | None:
673
+ return self._transactions.get(handle) if handle else None
674
+
675
+ def _value_from_c(self, value: _DBValue) -> Any:
676
+ if value.type == CORTEXT_DB_VALUE_INT64:
677
+ return int(value.int64_value)
678
+ if value.type == CORTEXT_DB_VALUE_DOUBLE:
679
+ return float(value.double_value)
680
+ if value.type == CORTEXT_DB_VALUE_TEXT:
681
+ return value.text_value.decode("utf-8") if value.text_value else ""
682
+ if value.type == CORTEXT_DB_VALUE_BLOB:
683
+ if not value.blob_data or value.blob_size == 0:
684
+ return b""
685
+ return bytes(value.blob_data[: value.blob_size])
686
+ return None
687
+
688
+ def _db_value(self, value: Any, keepalive: list[Any]) -> _DBValue:
689
+ out = _DBValue()
690
+ if value is None:
691
+ out.type = CORTEXT_DB_VALUE_NULL
692
+ elif isinstance(value, bool):
693
+ out.type = CORTEXT_DB_VALUE_INT64
694
+ out.int64_value = int(value)
695
+ elif isinstance(value, int):
696
+ out.type = CORTEXT_DB_VALUE_INT64
697
+ out.int64_value = value
698
+ elif isinstance(value, float):
699
+ out.type = CORTEXT_DB_VALUE_DOUBLE
700
+ out.double_value = value
701
+ elif isinstance(value, str):
702
+ raw = ctypes.create_string_buffer(value.encode("utf-8"))
703
+ keepalive.append(raw)
704
+ out.type = CORTEXT_DB_VALUE_TEXT
705
+ out.text_value = ctypes.cast(raw, ctypes.c_char_p)
706
+ else:
707
+ raw_bytes = bytes(value)
708
+ out.type = CORTEXT_DB_VALUE_BLOB
709
+ out.blob_size = len(raw_bytes)
710
+ if raw_bytes:
711
+ array_type = ctypes.c_uint8 * len(raw_bytes)
712
+ raw = array_type.from_buffer_copy(raw_bytes)
713
+ keepalive.append(raw)
714
+ out.blob_data = ctypes.cast(raw, ctypes.POINTER(ctypes.c_uint8))
715
+ return out
716
+
717
+ def _execute(
718
+ self,
719
+ _user_data: int,
720
+ transaction: int,
721
+ query: bytes,
722
+ params: Any,
723
+ param_count: int,
724
+ out_result: Any,
725
+ out_error: Any,
726
+ ) -> int:
727
+ try:
728
+ py_params = [
729
+ self._value_from_c(params[index]) for index in range(param_count)
730
+ ]
731
+ rows = self.provider.execute(
732
+ query.decode("utf-8"), py_params, self._transaction(transaction)
733
+ )
734
+
735
+ keepalive: list[Any] = []
736
+ row_array = (_DBRow * len(rows))()
737
+ for row_index, row in enumerate(rows):
738
+ items = list(row.items())
739
+ cell_array = (_DBCell * len(items))()
740
+ keepalive.append(cell_array)
741
+ for cell_index, (name, value) in enumerate(items):
742
+ name_raw = ctypes.create_string_buffer(name.encode("utf-8"))
743
+ keepalive.append(name_raw)
744
+ cell_array[cell_index].column_name = ctypes.cast(
745
+ name_raw, ctypes.c_char_p
746
+ )
747
+ cell_array[cell_index].value = self._db_value(value, keepalive)
748
+ row_array[row_index].cells = cell_array
749
+ row_array[row_index].cell_count = len(items)
750
+
751
+ keepalive.append(row_array)
752
+ out_result[0].rows = row_array
753
+ out_result[0].row_count = len(rows)
754
+ self._results[ctypes.addressof(out_result.contents)] = keepalive
755
+ return 0
756
+ except Exception as exc:
757
+ return self._set_error(out_error, str(exc))
758
+
759
+ def _begin(
760
+ self,
761
+ _user_data: int,
762
+ parent_transaction: int,
763
+ out_transaction: Any,
764
+ out_error: Any,
765
+ ) -> int:
766
+ try:
767
+ tx = self.provider.begin(self._transaction(parent_transaction))
768
+ handle = self._next_transaction
769
+ self._next_transaction += 1
770
+ self._transactions[handle] = tx
771
+ out_transaction[0] = handle
772
+ return 0
773
+ except Exception as exc:
774
+ return self._set_error(out_error, str(exc))
775
+
776
+ def _commit(self, _user_data: int, transaction: int, out_error: Any) -> int:
777
+ try:
778
+ self.provider.commit(self._transactions[transaction])
779
+ return 0
780
+ except Exception as exc:
781
+ return self._set_error(out_error, str(exc))
782
+
783
+ def _rollback(self, _user_data: int, transaction: int, out_error: Any) -> int:
784
+ try:
785
+ self.provider.rollback(self._transactions[transaction])
786
+ return 0
787
+ except Exception as exc:
788
+ return self._set_error(out_error, str(exc))
789
+
790
+ def _release_transaction(self, _user_data: int, transaction: int) -> None:
791
+ tx = self._transactions.pop(transaction, None)
792
+ if tx is not None:
793
+ self.provider.release_transaction(tx)
794
+
795
+ def _release_result(self, _user_data: int, result: Any) -> None:
796
+ self._results.pop(ctypes.addressof(result.contents), None)
797
+
798
+
799
+ class _ObjectProviderBridge:
800
+ def __init__(self, provider: ObjectStoreProvider) -> None:
801
+ self.provider = provider
802
+ self._transactions: dict[int, Any] = {}
803
+ self._next_transaction = 1
804
+ self._errors: list[ctypes.Array[ctypes.c_char]] = []
805
+ self._blobs: dict[int, Any] = {}
806
+
807
+ self.begin_cb = _ObjectBeginCallback(self._begin)
808
+ self.put_cb = _ObjectPutCallback(self._put)
809
+ self.get_cb = _ObjectGetCallback(self._get)
810
+ self.exists_cb = _ObjectExistsCallback(self._exists)
811
+ self.delete_cb = _ObjectDeleteCallback(self._delete)
812
+ self.commit_cb = _ObjectTransactionCallback(self._commit)
813
+ self.rollback_cb = _ObjectTransactionCallback(self._rollback)
814
+ self.release_transaction_cb = _ObjectReleaseTransactionCallback(
815
+ self._release_transaction
816
+ )
817
+ self.release_blob_cb = _ObjectReleaseBlobCallback(self._release_blob)
818
+ self.callbacks = _ObjectCallbacks(
819
+ ctypes.sizeof(_ObjectCallbacks),
820
+ self.begin_cb,
821
+ self.put_cb,
822
+ self.get_cb,
823
+ self.exists_cb,
824
+ self.delete_cb,
825
+ self.commit_cb,
826
+ self.rollback_cb,
827
+ self.release_transaction_cb,
828
+ self.release_blob_cb,
829
+ )
830
+
831
+ def _set_error(self, out_error: Any, message: str) -> int:
832
+ encoded = ctypes.create_string_buffer(message.encode("utf-8"))
833
+ self._errors.append(encoded)
834
+ if out_error:
835
+ out_error[0] = ctypes.cast(encoded, ctypes.c_char_p)
836
+ return 1
837
+
838
+ def _transaction(self, handle: int) -> Any | None:
839
+ return self._transactions.get(handle) if handle else None
840
+
841
+ def _bytes_from_c(self, data: Any, size: int) -> bytes:
842
+ if not data or size == 0:
843
+ return b""
844
+ return bytes(data[:size])
845
+
846
+ def _begin(
847
+ self,
848
+ _user_data: int,
849
+ parent_transaction: int,
850
+ out_transaction: Any,
851
+ out_error: Any,
852
+ ) -> int:
853
+ try:
854
+ tx = self.provider.begin(self._transaction(parent_transaction))
855
+ handle = self._next_transaction
856
+ self._next_transaction += 1
857
+ self._transactions[handle] = tx
858
+ out_transaction[0] = handle
859
+ return 0
860
+ except Exception as exc:
861
+ return self._set_error(out_error, str(exc))
862
+
863
+ def _put(
864
+ self,
865
+ _user_data: int,
866
+ transaction: int,
867
+ object_id: Any,
868
+ object_id_size: int,
869
+ data: Any,
870
+ data_size: int,
871
+ out_error: Any,
872
+ ) -> int:
873
+ try:
874
+ self.provider.put(
875
+ self._bytes_from_c(object_id, object_id_size),
876
+ self._bytes_from_c(data, data_size),
877
+ self._transactions[transaction],
878
+ )
879
+ return 0
880
+ except Exception as exc:
881
+ return self._set_error(out_error, str(exc))
882
+
883
+ def _get(
884
+ self,
885
+ _user_data: int,
886
+ transaction: int,
887
+ object_id: Any,
888
+ object_id_size: int,
889
+ out_data: Any,
890
+ out_data_size: Any,
891
+ out_error: Any,
892
+ ) -> int:
893
+ try:
894
+ data = self.provider.get(
895
+ self._bytes_from_c(object_id, object_id_size),
896
+ self._transactions[transaction],
897
+ )
898
+ if data is None:
899
+ out_data[0] = ctypes.cast(None, ctypes.POINTER(ctypes.c_uint8))
900
+ out_data_size[0] = 0
901
+ return 0
902
+ raw_bytes = bytes(data)
903
+ array_type = ctypes.c_uint8 * len(raw_bytes)
904
+ raw = array_type.from_buffer_copy(raw_bytes)
905
+ ptr = ctypes.cast(raw, ctypes.POINTER(ctypes.c_uint8))
906
+ self._blobs[ctypes.addressof(raw)] = raw
907
+ out_data[0] = ptr
908
+ out_data_size[0] = len(raw_bytes)
909
+ return 0
910
+ except Exception as exc:
911
+ return self._set_error(out_error, str(exc))
912
+
913
+ def _exists(
914
+ self,
915
+ _user_data: int,
916
+ transaction: int,
917
+ object_id: Any,
918
+ object_id_size: int,
919
+ out_exists: Any,
920
+ out_error: Any,
921
+ ) -> int:
922
+ try:
923
+ out_exists[0] = int(
924
+ self.provider.exists(
925
+ self._bytes_from_c(object_id, object_id_size),
926
+ self._transactions[transaction],
927
+ )
928
+ )
929
+ return 0
930
+ except Exception as exc:
931
+ return self._set_error(out_error, str(exc))
932
+
933
+ def _delete(
934
+ self,
935
+ _user_data: int,
936
+ transaction: int,
937
+ object_id: Any,
938
+ object_id_size: int,
939
+ out_deleted: Any,
940
+ out_error: Any,
941
+ ) -> int:
942
+ try:
943
+ out_deleted[0] = int(
944
+ self.provider.delete(
945
+ self._bytes_from_c(object_id, object_id_size),
946
+ self._transactions[transaction],
947
+ )
948
+ )
949
+ return 0
950
+ except Exception as exc:
951
+ return self._set_error(out_error, str(exc))
952
+
953
+ def _commit(self, _user_data: int, transaction: int, out_error: Any) -> int:
954
+ try:
955
+ self.provider.commit(self._transactions[transaction])
956
+ return 0
957
+ except Exception as exc:
958
+ return self._set_error(out_error, str(exc))
959
+
960
+ def _rollback(self, _user_data: int, transaction: int, out_error: Any) -> int:
961
+ try:
962
+ self.provider.rollback(self._transactions[transaction])
963
+ return 0
964
+ except Exception as exc:
965
+ return self._set_error(out_error, str(exc))
966
+
967
+ def _release_transaction(self, _user_data: int, transaction: int) -> None:
968
+ tx = self._transactions.pop(transaction, None)
969
+ if tx is not None:
970
+ self.provider.release_transaction(tx)
971
+
972
+ def _release_blob(self, _user_data: int, data: Any, _data_size: int) -> None:
973
+ if data:
974
+ self._blobs.pop(ctypes.addressof(data.contents), None)
975
+
976
+
977
+ class Cortext:
978
+ def __init__(
979
+ self,
980
+ db_path: str = ":memory:",
981
+ *,
982
+ store: DBProvider | None = None,
983
+ object_store: ObjectStoreProvider | None = None,
984
+ models_dir: str | None = None,
985
+ config: Config | None = None,
986
+ library_path: str | os.PathLike[str] | None = None,
987
+ ) -> None:
988
+ if store is not None and db_path != ":memory:":
989
+ raise ValueError("db_path and store are mutually exclusive")
990
+ self._lib = _LIB if library_path is None else load_library(library_path)
991
+ native_cfg = _NativeConfig()
992
+ self._lib.cortext_config_init(ctypes.byref(native_cfg))
993
+
994
+ if config is not None:
995
+ native_cfg.focus = config.focus
996
+ native_cfg.sensitivity = config.sensitivity
997
+ native_cfg.stability = config.stability
998
+ native_cfg.affect_interrupt = _bool_to_int(config.affect_interrupt)
999
+ native_cfg.affect_retrieval = _bool_to_int(config.affect_retrieval)
1000
+ native_cfg.reinforcement_enabled = _bool_to_int(config.reinforcement_enabled)
1001
+ native_cfg.procedural_enabled = _bool_to_int(config.procedural_enabled)
1002
+ native_cfg.sequential_edges_enabled = _bool_to_int(config.sequential_edges_enabled)
1003
+ native_cfg.signal_filter_audio_enabled = _bool_to_int(
1004
+ config.signal_filter_audio_enabled
1005
+ )
1006
+ native_cfg.signal_filter_image_enabled = _bool_to_int(
1007
+ config.signal_filter_image_enabled
1008
+ )
1009
+ native_cfg.signal_filter_text_enabled = _bool_to_int(
1010
+ config.signal_filter_text_enabled
1011
+ )
1012
+
1013
+ models_dir_raw = models_dir.encode("utf-8") if models_dir is not None else None
1014
+ self._provider_bridge = _ProviderBridge(store) if store is not None else None
1015
+ self._object_provider_bridge = (
1016
+ _ObjectProviderBridge(object_store) if object_store is not None else None
1017
+ )
1018
+ if self._provider_bridge is not None and self._object_provider_bridge is not None:
1019
+ self._handle = self._lib.cortext_create_with_store_and_object_callbacks(
1020
+ ctypes.byref(native_cfg),
1021
+ ctypes.byref(self._provider_bridge.callbacks),
1022
+ None,
1023
+ ctypes.byref(self._object_provider_bridge.callbacks),
1024
+ None,
1025
+ models_dir_raw,
1026
+ )
1027
+ elif self._provider_bridge is not None:
1028
+ self._handle = self._lib.cortext_create_with_store_callbacks(
1029
+ ctypes.byref(native_cfg),
1030
+ ctypes.byref(self._provider_bridge.callbacks),
1031
+ None,
1032
+ models_dir_raw,
1033
+ )
1034
+ elif self._object_provider_bridge is not None:
1035
+ db_path_raw = db_path.encode("utf-8")
1036
+ self._handle = self._lib.cortext_create_with_config_and_object_callbacks(
1037
+ ctypes.byref(native_cfg),
1038
+ db_path_raw,
1039
+ ctypes.byref(self._object_provider_bridge.callbacks),
1040
+ None,
1041
+ models_dir_raw,
1042
+ )
1043
+ else:
1044
+ db_path_raw = db_path.encode("utf-8")
1045
+ self._handle = self._lib.cortext_create_with_config(
1046
+ ctypes.byref(native_cfg),
1047
+ db_path_raw,
1048
+ models_dir_raw,
1049
+ )
1050
+ if not self._handle:
1051
+ _raise_last_error("cortext create failed")
1052
+
1053
+ def close(self) -> None:
1054
+ if getattr(self, "_handle", None):
1055
+ self._lib.cortext_free(self._handle)
1056
+ self._handle = None
1057
+
1058
+ def __enter__(self) -> "Cortext":
1059
+ return self
1060
+
1061
+ def __exit__(self, exc_type: object, exc: object, tb: object) -> None:
1062
+ self.close()
1063
+
1064
+ def __del__(self) -> None:
1065
+ try:
1066
+ self.close()
1067
+ except Exception:
1068
+ pass
1069
+
1070
+ def flush(self) -> None:
1071
+ status = self._lib.cortext_flush(self._handle)
1072
+ if status != 0:
1073
+ _raise_last_error("cortext_flush failed")
1074
+
1075
+ def reset(self) -> None:
1076
+ status = self._lib.cortext_reset(self._handle)
1077
+ if status != 0:
1078
+ _raise_last_error("cortext_reset failed")
1079
+
1080
+ def process_text_json(
1081
+ self, text: str, source_id: str, include_embedding: bool = True
1082
+ ) -> str:
1083
+ options = _native_process_json_options(include_embedding)
1084
+ return self._call_json(
1085
+ self._lib.cortext_process_text_json_with_options,
1086
+ text.encode("utf-8"),
1087
+ source_id.encode("utf-8"),
1088
+ ctypes.byref(options),
1089
+ )
1090
+
1091
+ def process_text(
1092
+ self, text: str, source_id: str, include_embedding: bool = True
1093
+ ) -> dict[str, Any]:
1094
+ return json.loads(self.process_text_json(text, source_id, include_embedding))
1095
+
1096
+ def embed_text_json(self, text: str) -> str:
1097
+ return self._call_json(
1098
+ self._lib.cortext_embed_text_json,
1099
+ text.encode("utf-8"),
1100
+ )
1101
+
1102
+ def embed_text(self, text: str) -> list[float]:
1103
+ return list(json.loads(self.embed_text_json(text))["embedding"])
1104
+
1105
+ def process_audio_json(
1106
+ self,
1107
+ pcm: Iterable[float],
1108
+ source_id: str,
1109
+ include_embedding: bool = True,
1110
+ ) -> str:
1111
+ samples = array("f", pcm)
1112
+ raw = (ctypes.c_float * len(samples)).from_buffer(samples)
1113
+ options = _native_process_json_options(include_embedding)
1114
+ return self._call_json(
1115
+ self._lib.cortext_process_audio_json_with_options,
1116
+ raw,
1117
+ len(samples),
1118
+ source_id.encode("utf-8"),
1119
+ ctypes.byref(options),
1120
+ )
1121
+
1122
+ def process_audio_with_media_json(
1123
+ self,
1124
+ pcm: Iterable[float],
1125
+ source_id: str,
1126
+ media: Media | bytes | bytearray | memoryview | None = None,
1127
+ media_mimetype: str | None = None,
1128
+ include_embedding: bool = True,
1129
+ ) -> str:
1130
+ samples = array("f", pcm)
1131
+ raw = (ctypes.c_float * len(samples)).from_buffer(samples)
1132
+ native_media, media_buffer, mimetype_buffer = _native_media(
1133
+ media, media_mimetype
1134
+ )
1135
+ options = _native_process_json_options(include_embedding)
1136
+ _ = (media_buffer, mimetype_buffer)
1137
+ return self._call_json(
1138
+ self._lib.cortext_process_audio_with_media_json_with_options,
1139
+ raw,
1140
+ len(samples),
1141
+ source_id.encode("utf-8"),
1142
+ ctypes.byref(native_media),
1143
+ ctypes.byref(options),
1144
+ )
1145
+
1146
+ def process_audio(
1147
+ self,
1148
+ pcm: Iterable[float],
1149
+ source_id: str,
1150
+ include_embedding: bool = True,
1151
+ ) -> dict[str, Any]:
1152
+ return json.loads(self.process_audio_json(pcm, source_id, include_embedding))
1153
+
1154
+ def process_audio_with_media(
1155
+ self,
1156
+ pcm: Iterable[float],
1157
+ source_id: str,
1158
+ media: Media | bytes | bytearray | memoryview | None = None,
1159
+ media_mimetype: str | None = None,
1160
+ include_embedding: bool = True,
1161
+ ) -> dict[str, Any]:
1162
+ return json.loads(
1163
+ self.process_audio_with_media_json(
1164
+ pcm, source_id, media, media_mimetype, include_embedding
1165
+ )
1166
+ )
1167
+
1168
+ def embed_audio_json(self, pcm: Iterable[float]) -> str:
1169
+ samples = array("f", pcm)
1170
+ raw = (ctypes.c_float * len(samples)).from_buffer(samples)
1171
+ return self._call_json(
1172
+ self._lib.cortext_embed_audio_json,
1173
+ raw,
1174
+ len(samples),
1175
+ )
1176
+
1177
+ def embed_audio(self, pcm: Iterable[float]) -> list[float]:
1178
+ return list(json.loads(self.embed_audio_json(pcm))["embedding"])
1179
+
1180
+ def process_image_json(
1181
+ self,
1182
+ data: bytes | bytearray | memoryview,
1183
+ width: int,
1184
+ height: int,
1185
+ channels: int,
1186
+ source_id: str,
1187
+ include_embedding: bool = True,
1188
+ ) -> str:
1189
+ blob = bytes(data)
1190
+ raw = (ctypes.c_uint8 * len(blob)).from_buffer_copy(blob)
1191
+ options = _native_process_json_options(include_embedding)
1192
+ return self._call_json(
1193
+ self._lib.cortext_process_image_json_with_options,
1194
+ raw,
1195
+ width,
1196
+ height,
1197
+ channels,
1198
+ source_id.encode("utf-8"),
1199
+ ctypes.byref(options),
1200
+ )
1201
+
1202
+ def process_image_with_media_json(
1203
+ self,
1204
+ data: bytes | bytearray | memoryview,
1205
+ width: int,
1206
+ height: int,
1207
+ channels: int,
1208
+ source_id: str,
1209
+ media: Media | bytes | bytearray | memoryview | None = None,
1210
+ media_mimetype: str | None = None,
1211
+ include_embedding: bool = True,
1212
+ ) -> str:
1213
+ blob = bytes(data)
1214
+ raw = (ctypes.c_uint8 * len(blob)).from_buffer_copy(blob)
1215
+ native_media, media_buffer, mimetype_buffer = _native_media(
1216
+ media, media_mimetype
1217
+ )
1218
+ options = _native_process_json_options(include_embedding)
1219
+ _ = (media_buffer, mimetype_buffer)
1220
+ return self._call_json(
1221
+ self._lib.cortext_process_image_with_media_json_with_options,
1222
+ raw,
1223
+ width,
1224
+ height,
1225
+ channels,
1226
+ source_id.encode("utf-8"),
1227
+ ctypes.byref(native_media),
1228
+ ctypes.byref(options),
1229
+ )
1230
+
1231
+ def process_image(
1232
+ self,
1233
+ data: bytes | bytearray | memoryview,
1234
+ width: int,
1235
+ height: int,
1236
+ channels: int,
1237
+ source_id: str,
1238
+ include_embedding: bool = True,
1239
+ ) -> dict[str, Any]:
1240
+ return json.loads(
1241
+ self.process_image_json(
1242
+ data, width, height, channels, source_id, include_embedding
1243
+ )
1244
+ )
1245
+
1246
+ def process_image_with_media(
1247
+ self,
1248
+ data: bytes | bytearray | memoryview,
1249
+ width: int,
1250
+ height: int,
1251
+ channels: int,
1252
+ source_id: str,
1253
+ media: Media | bytes | bytearray | memoryview | None = None,
1254
+ media_mimetype: str | None = None,
1255
+ include_embedding: bool = True,
1256
+ ) -> dict[str, Any]:
1257
+ return json.loads(
1258
+ self.process_image_with_media_json(
1259
+ data,
1260
+ width,
1261
+ height,
1262
+ channels,
1263
+ source_id,
1264
+ media,
1265
+ media_mimetype,
1266
+ include_embedding,
1267
+ )
1268
+ )
1269
+
1270
+ def embed_image_json(
1271
+ self,
1272
+ data: bytes | bytearray | memoryview,
1273
+ width: int,
1274
+ height: int,
1275
+ channels: int,
1276
+ ) -> str:
1277
+ blob = bytes(data)
1278
+ raw = (ctypes.c_uint8 * len(blob)).from_buffer_copy(blob)
1279
+ return self._call_json(
1280
+ self._lib.cortext_embed_image_json,
1281
+ raw,
1282
+ width,
1283
+ height,
1284
+ channels,
1285
+ )
1286
+
1287
+ def embed_image(
1288
+ self,
1289
+ data: bytes | bytearray | memoryview,
1290
+ width: int,
1291
+ height: int,
1292
+ channels: int,
1293
+ ) -> list[float]:
1294
+ payload = self.embed_image_json(data, width, height, channels)
1295
+ return list(json.loads(payload)["embedding"])
1296
+
1297
+ def consolidate_json(self) -> str:
1298
+ return self._call_json(self._lib.cortext_consolidate_json)
1299
+
1300
+ def consolidate(self) -> dict[str, Any]:
1301
+ return json.loads(self.consolidate_json())
1302
+
1303
+ def _call_json(self, fn: Any, *args: Any) -> str:
1304
+ result = fn(self._handle, *args)
1305
+ if not result:
1306
+ _raise_last_error(f"{fn.__name__} failed")
1307
+ try:
1308
+ return ctypes.string_at(result).decode("utf-8")
1309
+ finally:
1310
+ self._lib.cortext_string_free(result)