@micrantha/react-native-amaryllis 0.1.0 → 0.1.5

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.
Files changed (72) hide show
  1. package/README.md +43 -8
  2. package/android/src/main/java/com/micrantha/amaryllis/Amaryllis.kt +83 -8
  3. package/lib/module/Amaryllis.js +75 -20
  4. package/lib/module/Amaryllis.js.map +1 -1
  5. package/lib/module/AmaryllisContext.js +14 -4
  6. package/lib/module/AmaryllisContext.js.map +1 -1
  7. package/lib/module/AmaryllisHooks.js +87 -4
  8. package/lib/module/AmaryllisHooks.js.map +1 -1
  9. package/lib/module/AmaryllisRx.js +15 -14
  10. package/lib/module/AmaryllisRx.js.map +1 -1
  11. package/lib/module/ContextEngine.js +87 -0
  12. package/lib/module/ContextEngine.js.map +1 -0
  13. package/lib/module/ContextEngineContext.js +22 -0
  14. package/lib/module/ContextEngineContext.js.map +1 -0
  15. package/lib/module/ContextPolicies.js +46 -0
  16. package/lib/module/ContextPolicies.js.map +1 -0
  17. package/lib/module/ContextTypes.js +4 -0
  18. package/lib/module/ContextTypes.js.map +1 -0
  19. package/lib/module/ContextValidation.js +189 -0
  20. package/lib/module/ContextValidation.js.map +1 -0
  21. package/lib/module/Errors.js +46 -0
  22. package/lib/module/Errors.js.map +1 -0
  23. package/lib/module/NativeAmaryllis.js.map +1 -1
  24. package/lib/module/TypeConverters.js +80 -0
  25. package/lib/module/TypeConverters.js.map +1 -0
  26. package/lib/module/context.js +8 -0
  27. package/lib/module/context.js.map +1 -0
  28. package/lib/module/index.js +4 -0
  29. package/lib/module/index.js.map +1 -1
  30. package/lib/typescript/src/Amaryllis.d.ts.map +1 -1
  31. package/lib/typescript/src/AmaryllisContext.d.ts.map +1 -1
  32. package/lib/typescript/src/AmaryllisHooks.d.ts +8 -1
  33. package/lib/typescript/src/AmaryllisHooks.d.ts.map +1 -1
  34. package/lib/typescript/src/AmaryllisRx.d.ts.map +1 -1
  35. package/lib/typescript/src/ContextEngine.d.ts +19 -0
  36. package/lib/typescript/src/ContextEngine.d.ts.map +1 -0
  37. package/lib/typescript/src/ContextEngineContext.d.ts +9 -0
  38. package/lib/typescript/src/ContextEngineContext.d.ts.map +1 -0
  39. package/lib/typescript/src/ContextPolicies.d.ts +4 -0
  40. package/lib/typescript/src/ContextPolicies.d.ts.map +1 -0
  41. package/lib/typescript/src/ContextTypes.d.ts +92 -0
  42. package/lib/typescript/src/ContextTypes.d.ts.map +1 -0
  43. package/lib/typescript/src/ContextValidation.d.ts +9 -0
  44. package/lib/typescript/src/ContextValidation.d.ts.map +1 -0
  45. package/lib/typescript/src/Errors.d.ts +22 -0
  46. package/lib/typescript/src/Errors.d.ts.map +1 -0
  47. package/lib/typescript/src/NativeAmaryllis.d.ts +24 -4
  48. package/lib/typescript/src/NativeAmaryllis.d.ts.map +1 -1
  49. package/lib/typescript/src/TypeConverters.d.ts +43 -0
  50. package/lib/typescript/src/TypeConverters.d.ts.map +1 -0
  51. package/lib/typescript/src/Types.d.ts +14 -0
  52. package/lib/typescript/src/Types.d.ts.map +1 -1
  53. package/lib/typescript/src/context.d.ts +8 -0
  54. package/lib/typescript/src/context.d.ts.map +1 -0
  55. package/lib/typescript/src/index.d.ts +5 -0
  56. package/lib/typescript/src/index.d.ts.map +1 -1
  57. package/package.json +34 -28
  58. package/src/Amaryllis.ts +75 -24
  59. package/src/AmaryllisContext.tsx +19 -4
  60. package/src/AmaryllisHooks.tsx +106 -5
  61. package/src/AmaryllisRx.ts +13 -9
  62. package/src/ContextEngine.ts +133 -0
  63. package/src/ContextEngineContext.tsx +31 -0
  64. package/src/ContextPolicies.ts +54 -0
  65. package/src/ContextTypes.ts +122 -0
  66. package/src/ContextValidation.ts +279 -0
  67. package/src/Errors.ts +55 -0
  68. package/src/NativeAmaryllis.ts +18 -4
  69. package/src/TypeConverters.ts +87 -0
  70. package/src/Types.ts +9 -0
  71. package/src/context.ts +14 -0
  72. package/src/index.tsx +5 -0
@@ -0,0 +1,279 @@
1
+ import type {
2
+ ContextItem,
3
+ ContextPolicy,
4
+ ContextQuery,
5
+ MediaReference,
6
+ MediaValidationPolicy,
7
+ } from './ContextTypes';
8
+ import { ValidationError } from './Errors';
9
+
10
+ const SCHEME_REGEX = /^([a-zA-Z][a-zA-Z0-9+.-]*):/;
11
+
12
+ const isAbsolutePath = (uri: string): boolean => {
13
+ return uri.startsWith('/') || /^[a-zA-Z]:\\\\/.test(uri);
14
+ };
15
+
16
+ const getScheme = (uri: string): string | undefined => {
17
+ const match = uri.match(SCHEME_REGEX);
18
+ return match?.[1]?.toLowerCase();
19
+ };
20
+
21
+ const isPositiveNumber = (value: number | undefined): value is number => {
22
+ return value !== undefined && Number.isFinite(value) && value > 0;
23
+ };
24
+
25
+ const isNonEmptyString = (value: string | undefined): value is string => {
26
+ return value !== undefined && value.trim().length > 0;
27
+ };
28
+
29
+ const assertNonEmpty = (value: string, field: string): void => {
30
+ if (!isNonEmptyString(value)) {
31
+ throw new ValidationError(`${field} must be a non-empty string`);
32
+ }
33
+ };
34
+
35
+ const validateStringList = (values: string[], field: string): void => {
36
+ if (values.length === 0) {
37
+ throw new ValidationError(`${field} must not be empty`);
38
+ }
39
+ for (const value of values) {
40
+ assertNonEmpty(value, field);
41
+ }
42
+ };
43
+
44
+ export const validateContextPolicy = (
45
+ policy: ContextPolicy | undefined
46
+ ): void => {
47
+ if (!policy) {
48
+ return;
49
+ }
50
+
51
+ const { maxBytes, maxItems, defaultTtlSeconds, evictionStrategy, media } =
52
+ policy;
53
+
54
+ if (maxBytes !== undefined && !isPositiveNumber(maxBytes)) {
55
+ throw new ValidationError('maxBytes must be a positive number');
56
+ }
57
+ if (maxItems !== undefined && !isPositiveNumber(maxItems)) {
58
+ throw new ValidationError('maxItems must be a positive number');
59
+ }
60
+ if (defaultTtlSeconds !== undefined && !isPositiveNumber(defaultTtlSeconds)) {
61
+ throw new ValidationError('defaultTtlSeconds must be a positive number');
62
+ }
63
+ const hasEvictionStrategy = Object.prototype.hasOwnProperty.call(
64
+ policy,
65
+ 'evictionStrategy'
66
+ );
67
+ if (hasEvictionStrategy && evictionStrategy === undefined) {
68
+ throw new ValidationError('evictionStrategy is not supported');
69
+ }
70
+ if (
71
+ evictionStrategy !== undefined &&
72
+ evictionStrategy !== 'lru' &&
73
+ evictionStrategy !== 'recency' &&
74
+ evictionStrategy !== 'size'
75
+ ) {
76
+ throw new ValidationError('evictionStrategy is not supported');
77
+ }
78
+
79
+ validateMediaPolicy(media);
80
+ };
81
+
82
+ export const validateMediaPolicy = (
83
+ policy: MediaValidationPolicy | undefined
84
+ ): void => {
85
+ if (!policy) {
86
+ return;
87
+ }
88
+
89
+ const { allowedUriSchemes, maxMediaBytes, maxMediaWidth, maxMediaHeight } =
90
+ policy;
91
+
92
+ if (allowedUriSchemes) {
93
+ if (allowedUriSchemes.length === 0) {
94
+ throw new ValidationError('allowedUriSchemes must not be empty');
95
+ }
96
+ for (const scheme of allowedUriSchemes) {
97
+ assertNonEmpty(scheme, 'allowedUriSchemes item');
98
+ }
99
+ }
100
+
101
+ if (maxMediaBytes !== undefined && !isPositiveNumber(maxMediaBytes)) {
102
+ throw new ValidationError('maxMediaBytes must be a positive number');
103
+ }
104
+ if (maxMediaWidth !== undefined && !isPositiveNumber(maxMediaWidth)) {
105
+ throw new ValidationError('maxMediaWidth must be a positive number');
106
+ }
107
+ if (maxMediaHeight !== undefined && !isPositiveNumber(maxMediaHeight)) {
108
+ throw new ValidationError('maxMediaHeight must be a positive number');
109
+ }
110
+ };
111
+
112
+ export const validateMediaReference = (
113
+ reference: MediaReference,
114
+ policy?: MediaValidationPolicy
115
+ ): void => {
116
+ assertNonEmpty(reference.uri, 'MediaReference.uri');
117
+
118
+ if (policy?.requireAbsoluteUri) {
119
+ const scheme = getScheme(reference.uri);
120
+ if (!scheme && !isAbsolutePath(reference.uri)) {
121
+ throw new ValidationError(
122
+ 'MediaReference.uri must be an absolute path or URI'
123
+ );
124
+ }
125
+ }
126
+
127
+ if (policy?.allowedUriSchemes) {
128
+ const scheme = getScheme(reference.uri);
129
+ if (!scheme) {
130
+ throw new ValidationError(
131
+ 'MediaReference.uri must include a scheme when allowedUriSchemes is set'
132
+ );
133
+ }
134
+ const allowed = policy.allowedUriSchemes.map((item) => item.toLowerCase());
135
+ if (!allowed.includes(scheme)) {
136
+ throw new ValidationError(
137
+ `MediaReference.uri scheme not allowed: ${scheme}`
138
+ );
139
+ }
140
+ }
141
+
142
+ if (
143
+ reference.sizeBytes !== undefined &&
144
+ !isPositiveNumber(reference.sizeBytes)
145
+ ) {
146
+ throw new ValidationError(
147
+ 'MediaReference.sizeBytes must be a positive number'
148
+ );
149
+ }
150
+ if (reference.width !== undefined && !isPositiveNumber(reference.width)) {
151
+ throw new ValidationError('MediaReference.width must be a positive number');
152
+ }
153
+ if (reference.height !== undefined && !isPositiveNumber(reference.height)) {
154
+ throw new ValidationError(
155
+ 'MediaReference.height must be a positive number'
156
+ );
157
+ }
158
+
159
+ if (
160
+ policy?.maxMediaBytes !== undefined &&
161
+ reference.sizeBytes !== undefined &&
162
+ reference.sizeBytes > policy.maxMediaBytes
163
+ ) {
164
+ throw new ValidationError(
165
+ 'MediaReference.sizeBytes exceeds policy maxMediaBytes'
166
+ );
167
+ }
168
+ if (
169
+ policy?.maxMediaWidth !== undefined &&
170
+ reference.width !== undefined &&
171
+ reference.width > policy.maxMediaWidth
172
+ ) {
173
+ throw new ValidationError(
174
+ 'MediaReference.width exceeds policy maxMediaWidth'
175
+ );
176
+ }
177
+ if (
178
+ policy?.maxMediaHeight !== undefined &&
179
+ reference.height !== undefined &&
180
+ reference.height > policy.maxMediaHeight
181
+ ) {
182
+ throw new ValidationError(
183
+ 'MediaReference.height exceeds policy maxMediaHeight'
184
+ );
185
+ }
186
+ };
187
+
188
+ export const validateMediaReferences = (
189
+ references: MediaReference[] | undefined,
190
+ policy?: MediaValidationPolicy
191
+ ): void => {
192
+ if (!references) {
193
+ return;
194
+ }
195
+ if (references.length === 0) {
196
+ throw new ValidationError('media must not be an empty array');
197
+ }
198
+ validateMediaPolicy(policy);
199
+ for (const reference of references) {
200
+ validateMediaReference(reference, policy);
201
+ }
202
+ };
203
+
204
+ export const validateContextItem = (
205
+ item: ContextItem,
206
+ policy?: ContextPolicy
207
+ ): void => {
208
+ assertNonEmpty(item.id, 'ContextItem.id');
209
+ assertNonEmpty(item.text, 'ContextItem.text');
210
+
211
+ if (!Number.isFinite(item.createdAt) || item.createdAt <= 0) {
212
+ throw new ValidationError(
213
+ 'ContextItem.createdAt must be a positive number'
214
+ );
215
+ }
216
+ if (
217
+ item.updatedAt !== undefined &&
218
+ (!Number.isFinite(item.updatedAt) || item.updatedAt <= 0)
219
+ ) {
220
+ throw new ValidationError(
221
+ 'ContextItem.updatedAt must be a positive number'
222
+ );
223
+ }
224
+ if (item.ttlSeconds !== undefined && !isPositiveNumber(item.ttlSeconds)) {
225
+ throw new ValidationError(
226
+ 'ContextItem.ttlSeconds must be a positive number'
227
+ );
228
+ }
229
+
230
+ if (item.tags) {
231
+ validateStringList(item.tags, 'ContextItem.tags');
232
+ }
233
+
234
+ if (item.metadata) {
235
+ const entries = Object.entries(item.metadata);
236
+ for (const [key, value] of entries) {
237
+ assertNonEmpty(key, 'ContextItem.metadata key');
238
+ assertNonEmpty(value, 'ContextItem.metadata value');
239
+ }
240
+ }
241
+
242
+ validateMediaReferences(item.media, policy?.media);
243
+ };
244
+
245
+ export const validateContextItems = (
246
+ items: ContextItem[],
247
+ policy?: ContextPolicy
248
+ ): void => {
249
+ if (items.length === 0) {
250
+ throw new ValidationError('items must not be empty');
251
+ }
252
+ validateContextPolicy(policy);
253
+ for (const item of items) {
254
+ validateContextItem(item, policy);
255
+ }
256
+ };
257
+
258
+ export const validateContextQuery = (query: ContextQuery): void => {
259
+ assertNonEmpty(query.text, 'ContextQuery.text');
260
+
261
+ if (query.limit !== undefined && !isPositiveNumber(query.limit)) {
262
+ throw new ValidationError('ContextQuery.limit must be a positive number');
263
+ }
264
+ if (query.recencyBias !== undefined && !Number.isFinite(query.recencyBias)) {
265
+ throw new ValidationError(
266
+ 'ContextQuery.recencyBias must be a finite number'
267
+ );
268
+ }
269
+ if (query.tags) {
270
+ validateStringList(query.tags, 'ContextQuery.tags');
271
+ }
272
+ if (query.filters) {
273
+ const entries = Object.entries(query.filters);
274
+ for (const [key, value] of entries) {
275
+ assertNonEmpty(key, 'ContextQuery.filters key');
276
+ assertNonEmpty(value, 'ContextQuery.filters value');
277
+ }
278
+ }
279
+ };
package/src/Errors.ts ADDED
@@ -0,0 +1,55 @@
1
+ export class AmaryllisError extends Error {
2
+ constructor(
3
+ message: string,
4
+ public readonly code: string
5
+ ) {
6
+ super(message);
7
+ this.name = 'AmaryllisError';
8
+ }
9
+ }
10
+
11
+ export class InitializationError extends AmaryllisError {
12
+ constructor(message: string) {
13
+ super(message, 'INIT_ERROR');
14
+ this.name = 'InitializationError';
15
+ }
16
+ }
17
+
18
+ export class GenerationError extends AmaryllisError {
19
+ constructor(message: string) {
20
+ super(message, 'GENERATION_ERROR');
21
+ this.name = 'GenerationError';
22
+ }
23
+ }
24
+
25
+ export class SessionError extends AmaryllisError {
26
+ constructor(message: string) {
27
+ super(message, 'SESSION_ERROR');
28
+ this.name = 'SessionError';
29
+ }
30
+ }
31
+
32
+ export class ValidationError extends AmaryllisError {
33
+ constructor(message: string) {
34
+ super(message, 'VALIDATION_ERROR');
35
+ this.name = 'ValidationError';
36
+ }
37
+ }
38
+
39
+ export class ResourceError extends AmaryllisError {
40
+ constructor(message: string) {
41
+ super(message, 'RESOURCE_ERROR');
42
+ this.name = 'ResourceError';
43
+ }
44
+ }
45
+
46
+ export function isAmaryllisError(error: unknown): error is AmaryllisError {
47
+ return error instanceof AmaryllisError;
48
+ }
49
+
50
+ export function createError(
51
+ message: string,
52
+ code: string = 'UNKNOWN_ERROR'
53
+ ): AmaryllisError {
54
+ return new AmaryllisError(message, code);
55
+ }
@@ -2,13 +2,27 @@ import type { TurboModule } from 'react-native';
2
2
  import { TurboModuleRegistry } from 'react-native';
3
3
 
4
4
  export interface Spec extends TurboModule {
5
- init(params: Object): Promise<void>;
5
+ init(params: {
6
+ modelPath: string;
7
+ maxTopK?: number;
8
+ maxNumImages?: number;
9
+ maxTokens?: number;
10
+ visionEncoderPath?: string;
11
+ visionAdapterPath?: string;
12
+ }): Promise<void>;
6
13
 
7
- newSession(params: Object): Promise<void>;
14
+ newSession(params?: {
15
+ topK?: number;
16
+ topP?: number;
17
+ temperature?: number;
18
+ randomSeed?: number;
19
+ loraPath?: string;
20
+ enableVisionModality?: boolean;
21
+ }): Promise<void>;
8
22
 
9
- generate(params: Object): Promise<string>;
23
+ generate(params: { prompt: string; images?: string[] }): Promise<string>;
10
24
 
11
- generateAsync(params: Object): Promise<void>;
25
+ generateAsync(params: { prompt: string; images?: string[] }): Promise<void>;
12
26
 
13
27
  close(): void;
14
28
 
@@ -0,0 +1,87 @@
1
+ import type {
2
+ LlmEngineConfig,
3
+ LlmSessionParams,
4
+ LlmRequestParams,
5
+ } from './Types';
6
+
7
+ /**
8
+ * Convert rich LlmEngineConfig to codegen-compatible object structure
9
+ */
10
+ export function toNativeEngineConfig(config: LlmEngineConfig) {
11
+ return {
12
+ modelPath: config.modelPath,
13
+ maxTopK: config.maxTopK,
14
+ maxNumImages: config.maxNumImages,
15
+ maxTokens: config.maxTokens,
16
+ visionEncoderPath: config.visionEncoderPath,
17
+ visionAdapterPath: config.visionAdapterPath,
18
+ };
19
+ }
20
+
21
+ /**
22
+ * Convert codegen-compatible config object to rich LlmEngineConfig
23
+ */
24
+ export function fromNativeEngineConfig(native: any): LlmEngineConfig {
25
+ return {
26
+ modelPath: native.modelPath,
27
+ maxTopK: native.maxTopK,
28
+ maxNumImages: native.maxNumImages,
29
+ maxTokens: native.maxTokens,
30
+ visionEncoderPath: native.visionEncoderPath,
31
+ visionAdapterPath: native.visionAdapterPath,
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Convert rich LlmSessionParams to codegen-compatible object structure
37
+ */
38
+ export function toNativeSessionParams(params?: LlmSessionParams) {
39
+ if (!params) return undefined;
40
+
41
+ return {
42
+ topK: params.topK,
43
+ topP: params.topP,
44
+ temperature: params.temperature,
45
+ randomSeed: params.randomSeed,
46
+ loraPath: params.loraPath,
47
+ enableVisionModality: params.enableVisionModality,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Convert codegen-compatible session object to rich LlmSessionParams
53
+ */
54
+ export function fromNativeSessionParams(
55
+ native?: any
56
+ ): LlmSessionParams | undefined {
57
+ if (!native) return undefined;
58
+
59
+ return {
60
+ topK: native.topK,
61
+ topP: native.topP,
62
+ temperature: native.temperature,
63
+ randomSeed: native.randomSeed,
64
+ loraPath: native.loraPath,
65
+ enableVisionModality: native.enableVisionModality,
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Convert rich LlmRequestParams to codegen-compatible object structure
71
+ */
72
+ export function toNativeRequestParams(params: LlmRequestParams) {
73
+ return {
74
+ prompt: params.prompt,
75
+ images: params.images,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Convert codegen-compatible request object to rich LlmRequestParams
81
+ */
82
+ export function fromNativeRequestParams(native: any): LlmRequestParams {
83
+ return {
84
+ prompt: native.prompt,
85
+ images: native.images,
86
+ };
87
+ }
package/src/Types.ts CHANGED
@@ -3,10 +3,19 @@ import type { Observable } from 'rxjs';
3
3
 
4
4
  export type LlmNativeEngine = Spec;
5
5
 
6
+ export type LlmEvent =
7
+ | { type: 'partial'; text: string }
8
+ | { type: 'final'; text: string }
9
+ | { type: 'error'; error: Error };
10
+
6
11
  export type LlmCallbacks = {
7
12
  // Async streaming callbacks
13
+ onEvent?: (event: LlmEvent) => void;
14
+ /** @deprecated Use onEvent instead. */
8
15
  onPartialResult?: (result: string) => void;
16
+ /** @deprecated Use onEvent instead. */
9
17
  onFinalResult?: (result: string) => void;
18
+ /** @deprecated Use onEvent instead. */
10
19
  onError?: (err: Error) => void;
11
20
  };
12
21
 
package/src/context.ts ADDED
@@ -0,0 +1,14 @@
1
+ export {
2
+ ContextEngine,
3
+ createContextEngine,
4
+ defaultContextFormatter,
5
+ } from './ContextEngine';
6
+ export * from './ContextEngineContext';
7
+ export * from './ContextPolicies';
8
+ export * from './ContextValidation';
9
+ export {
10
+ useContextInference,
11
+ useContextInferenceAsync,
12
+ } from './AmaryllisHooks';
13
+ export type { ContextInferenceProps } from './AmaryllisHooks';
14
+ export type * from './ContextTypes';
package/src/index.tsx CHANGED
@@ -2,4 +2,9 @@ export { default as Amaryllis, LlmPipe } from './Amaryllis';
2
2
  export * from './AmaryllisHooks';
3
3
  export * from './AmaryllisContext';
4
4
  export * from './AmaryllisRx';
5
+ export { createContextEngine } from './ContextEngine';
6
+ export * from './ContextEngineContext';
7
+ export * from './ContextPolicies';
8
+ export * from './ContextValidation';
9
+ export type * from './ContextTypes';
5
10
  export type * from './Types';