@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
@@ -1,7 +1,42 @@
1
1
  import { useCallback, useMemo, useEffect } from 'react';
2
- import type { LlmRequestParams, InferenceProps } from './Types';
2
+ import type { InferenceProps, LlmRequestParams } from './Types';
3
3
  import { useLLMContext } from './AmaryllisContext';
4
4
  import { createLLMObservable } from './AmaryllisRx';
5
+ import { useContextEngine } from './ContextEngineContext';
6
+ import type { ContextEngine, ContextQuery } from './ContextTypes';
7
+
8
+ export type ContextInferenceProps = InferenceProps & {
9
+ contextEngine?: ContextEngine;
10
+ query?: ContextQuery;
11
+ };
12
+
13
+ const useContextAugmentation = (
14
+ contextEngine?: ContextEngine,
15
+ query?: ContextQuery
16
+ ) => {
17
+ const engineFromProvider = useContextEngine();
18
+ const engine = contextEngine ?? engineFromProvider;
19
+
20
+ return useCallback(
21
+ async (params: LlmRequestParams): Promise<LlmRequestParams> => {
22
+ if (!engine) {
23
+ return params;
24
+ }
25
+ const resolvedQuery = query ?? engine.deriveQuery(params.prompt, params);
26
+ if (!resolvedQuery) {
27
+ return params;
28
+ }
29
+ const items = await engine.search(resolvedQuery);
30
+ return engine.formatRequest({
31
+ prompt: params.prompt,
32
+ items,
33
+ query: resolvedQuery,
34
+ request: params,
35
+ });
36
+ },
37
+ [engine, query]
38
+ );
39
+ };
5
40
 
6
41
  export const useInferenceAsync = (props: InferenceProps = {}) => {
7
42
  const { controller } = useLLMContext();
@@ -11,16 +46,23 @@ export const useInferenceAsync = (props: InferenceProps = {}) => {
11
46
 
12
47
  const generate = useCallback(
13
48
  async (params: LlmRequestParams) => {
49
+ if (!controller) {
50
+ onError?.(new Error('Controller not initialized'));
51
+ return () => {
52
+ onComplete?.();
53
+ };
54
+ }
55
+
14
56
  try {
15
57
  onGenerate?.();
16
- await controller?.generateAsync(params, llm$.callbacks);
58
+ await controller.generateAsync(params, llm$.callbacks);
17
59
  } catch (err) {
18
60
  onError?.(
19
61
  err instanceof Error ? err : new Error('An unknown error occurred')
20
62
  );
21
63
  }
22
64
  return () => {
23
- controller?.cancelAsync();
65
+ controller.cancelAsync();
24
66
  onComplete?.();
25
67
  };
26
68
  },
@@ -56,9 +98,16 @@ export const useInference = (props: InferenceProps = {}) => {
56
98
 
57
99
  const generate = useCallback(
58
100
  async (params: LlmRequestParams) => {
101
+ if (!controller) {
102
+ onError?.(new Error('Controller not initialized'));
103
+ return () => {
104
+ onComplete?.();
105
+ };
106
+ }
107
+
59
108
  try {
60
109
  onGenerate?.();
61
- const response = await controller?.generate(params);
110
+ const response = await controller.generate(params);
62
111
  onResult?.(response ?? '', true);
63
112
  } catch (err) {
64
113
  onError?.(
@@ -67,7 +116,7 @@ export const useInference = (props: InferenceProps = {}) => {
67
116
  }
68
117
 
69
118
  return () => {
70
- controller?.cancelAsync();
119
+ controller.cancelAsync();
71
120
  onComplete?.();
72
121
  };
73
122
  },
@@ -76,3 +125,55 @@ export const useInference = (props: InferenceProps = {}) => {
76
125
 
77
126
  return generate;
78
127
  };
128
+
129
+ export const useContextInferenceAsync = (props: ContextInferenceProps = {}) => {
130
+ const { contextEngine, query, ...inferenceProps } = props;
131
+ const { onComplete, onError } = inferenceProps;
132
+ const augmentRequest = useContextAugmentation(contextEngine, query);
133
+ const generateBase = useInferenceAsync(inferenceProps);
134
+
135
+ const generate = useCallback(
136
+ async (params: LlmRequestParams) => {
137
+ try {
138
+ const augmented = await augmentRequest(params);
139
+ return await generateBase(augmented);
140
+ } catch (err) {
141
+ onError?.(
142
+ err instanceof Error ? err : new Error('An unknown error occurred')
143
+ );
144
+ return () => {
145
+ onComplete?.();
146
+ };
147
+ }
148
+ },
149
+ [augmentRequest, generateBase, onComplete, onError]
150
+ );
151
+
152
+ return generate;
153
+ };
154
+
155
+ export const useContextInference = (props: ContextInferenceProps = {}) => {
156
+ const { contextEngine, query, ...inferenceProps } = props;
157
+ const { onComplete, onError } = inferenceProps;
158
+ const augmentRequest = useContextAugmentation(contextEngine, query);
159
+ const generateBase = useInference(inferenceProps);
160
+
161
+ const generate = useCallback(
162
+ async (params: LlmRequestParams) => {
163
+ try {
164
+ const augmented = await augmentRequest(params);
165
+ return await generateBase(augmented);
166
+ } catch (err) {
167
+ onError?.(
168
+ err instanceof Error ? err : new Error('An unknown error occurred')
169
+ );
170
+ return () => {
171
+ onComplete?.();
172
+ };
173
+ }
174
+ },
175
+ [augmentRequest, generateBase, onComplete, onError]
176
+ );
177
+
178
+ return generate;
179
+ };
@@ -2,21 +2,25 @@ import type { LlmCallbacks, LLMObservableResult, LLMResult } from './Types';
2
2
  import { Observable, Subscriber } from 'rxjs';
3
3
 
4
4
  export function createLLMObservable(): LLMObservableResult {
5
- let subscriber: Subscriber<LLMResult>;
5
+ let subscriber: Subscriber<LLMResult> | null = null;
6
6
 
7
7
  const observable = new Observable<LLMResult>((sub) => {
8
8
  subscriber = sub;
9
+ return () => {
10
+ subscriber = null;
11
+ };
9
12
  });
10
13
 
11
14
  const callbacks: LlmCallbacks = {
12
- onPartialResult: (partial) => {
13
- subscriber?.next({ text: partial, isFinal: false });
14
- },
15
- onFinalResult: (final) => {
16
- subscriber?.next({ text: final, isFinal: true });
17
- },
18
- onError: (error) => {
19
- subscriber?.error(error);
15
+ onEvent: (event) => {
16
+ if (!subscriber) {
17
+ return;
18
+ }
19
+ if (event.type === 'error') {
20
+ subscriber.error(event.error);
21
+ return;
22
+ }
23
+ subscriber.next({ text: event.text, isFinal: event.type === 'final' });
20
24
  },
21
25
  };
22
26
 
@@ -0,0 +1,133 @@
1
+ import type {
2
+ ContextEngine as ContextEngineContract,
3
+ ContextEngineOptions,
4
+ ContextFormatParams,
5
+ ContextItem,
6
+ ContextQuery,
7
+ ContextScorer,
8
+ ContextDefaultQueryFactory,
9
+ ContextRequestFormatter,
10
+ } from './ContextTypes';
11
+ import type { LlmRequestParams } from './Types';
12
+ import { defaultContextScorer } from './ContextPolicies';
13
+ import {
14
+ validateContextItems,
15
+ validateContextPolicy,
16
+ validateContextQuery,
17
+ } from './ContextValidation';
18
+
19
+ const formatMetadata = (metadata: ContextItem['metadata']): string | null => {
20
+ if (!metadata) {
21
+ return null;
22
+ }
23
+ const entries = Object.entries(metadata);
24
+ if (entries.length === 0) {
25
+ return null;
26
+ }
27
+ const pairs = entries.map(([key, value]) => `${key}=${value}`);
28
+ return `meta=${pairs.join(', ')}`;
29
+ };
30
+
31
+ const formatTags = (tags: ContextItem['tags']): string | null => {
32
+ if (!tags || tags.length === 0) {
33
+ return null;
34
+ }
35
+ return `tags=${tags.join(', ')}`;
36
+ };
37
+
38
+ const formatContextItem = (item: ContextItem): string => {
39
+ const parts = [
40
+ item.text.trim(),
41
+ formatTags(item.tags),
42
+ formatMetadata(item.metadata),
43
+ ];
44
+ return parts
45
+ .filter((part): part is string => Boolean(part && part.length > 0))
46
+ .join(' | ');
47
+ };
48
+
49
+ export const defaultContextFormatter: ContextRequestFormatter = (
50
+ params: ContextFormatParams
51
+ ) => {
52
+ if (params.items.length === 0) {
53
+ return params.request;
54
+ }
55
+ const lines = params.items.map((item) => `- ${formatContextItem(item)}`);
56
+ const contextBlock = ['Context:', ...lines].join('\n');
57
+ const augmentedPrompt = [contextBlock, params.prompt]
58
+ .map((entry) => entry.trim())
59
+ .filter((entry) => entry.length > 0)
60
+ .join('\n\n');
61
+ return {
62
+ ...params.request,
63
+ prompt: augmentedPrompt,
64
+ };
65
+ };
66
+
67
+ export class ContextEngine implements ContextEngineContract {
68
+ private readonly store: ContextEngineOptions['store'];
69
+ private readonly scorer: ContextScorer;
70
+ private policy: ContextEngineOptions['policy'];
71
+ private readonly formatter: ContextRequestFormatter;
72
+ private readonly defaultQueryFactory?: ContextDefaultQueryFactory;
73
+
74
+ constructor(options: ContextEngineOptions) {
75
+ validateContextPolicy(options.policy);
76
+ this.store = options.store;
77
+ this.scorer = options.scorer ?? defaultContextScorer;
78
+ this.policy = options.policy ?? {};
79
+ this.formatter = options.formatter ?? defaultContextFormatter;
80
+ this.defaultQueryFactory = options.defaultQueryFactory;
81
+ }
82
+
83
+ async add(items: ContextItem[]): Promise<void> {
84
+ validateContextItems(items, this.policy);
85
+ await this.store.put(items);
86
+ }
87
+
88
+ async search(query: ContextQuery): Promise<ContextItem[]> {
89
+ validateContextQuery(query);
90
+ const results = await this.store.query(query);
91
+ if (results.length <= 1) {
92
+ return results;
93
+ }
94
+ const scored = results.map((item, index) => ({
95
+ item,
96
+ index,
97
+ score: this.scorer.score(item, query),
98
+ }));
99
+ scored.sort((a, b) => {
100
+ if (a.score === b.score) {
101
+ return a.index - b.index;
102
+ }
103
+ return b.score - a.score;
104
+ });
105
+ return scored.map((entry) => entry.item);
106
+ }
107
+
108
+ setPolicy(policy: ContextEngineOptions['policy']): void {
109
+ validateContextPolicy(policy);
110
+ this.policy = policy ?? {};
111
+ }
112
+
113
+ async compact(): Promise<void> {
114
+ await this.store.compact(this.policy ?? {});
115
+ }
116
+
117
+ formatRequest(params: ContextFormatParams): LlmRequestParams {
118
+ return this.formatter(params);
119
+ }
120
+
121
+ deriveQuery(
122
+ prompt: string,
123
+ request: LlmRequestParams
124
+ ): ContextQuery | undefined {
125
+ return this.defaultQueryFactory?.(prompt, request);
126
+ }
127
+ }
128
+
129
+ export const createContextEngine = (
130
+ options: ContextEngineOptions
131
+ ): ContextEngineContract => {
132
+ return new ContextEngine(options);
133
+ };
@@ -0,0 +1,31 @@
1
+ import { createContext, useContext } from 'react';
2
+ import type { ReactNode } from 'react';
3
+ import type { ContextEngine } from './ContextTypes';
4
+
5
+ export interface ContextEngineProviderProps {
6
+ engine: ContextEngine;
7
+ children: ReactNode;
8
+ }
9
+
10
+ interface ContextEngineContextValue {
11
+ engine: ContextEngine | null;
12
+ }
13
+
14
+ const ContextEngineContext = createContext<ContextEngineContextValue>({
15
+ engine: null,
16
+ });
17
+
18
+ export const ContextEngineProvider = ({
19
+ engine,
20
+ children,
21
+ }: ContextEngineProviderProps) => {
22
+ return (
23
+ <ContextEngineContext.Provider value={{ engine }}>
24
+ {children}
25
+ </ContextEngineContext.Provider>
26
+ );
27
+ };
28
+
29
+ export const useContextEngine = (): ContextEngine | null => {
30
+ return useContext(ContextEngineContext).engine;
31
+ };
@@ -0,0 +1,54 @@
1
+ import type { ContextItem, ContextQuery, ContextScorer } from './ContextTypes';
2
+
3
+ export const DEFAULT_RECENCY_BIAS = 0.35;
4
+
5
+ const clamp01 = (value: number): number => {
6
+ if (value < 0) {
7
+ return 0;
8
+ }
9
+ if (value > 1) {
10
+ return 1;
11
+ }
12
+ return value;
13
+ };
14
+
15
+ const tokenize = (text: string): string[] => {
16
+ return text
17
+ .toLowerCase()
18
+ .split(/\\s+/)
19
+ .map((token) => token.trim())
20
+ .filter((token) => token.length > 0);
21
+ };
22
+
23
+ const keywordScore = (item: ContextItem, query: ContextQuery): number => {
24
+ const queryTokens = tokenize(query.text);
25
+ if (queryTokens.length === 0) {
26
+ return 0;
27
+ }
28
+ const haystack = item.text.toLowerCase();
29
+ let hits = 0;
30
+ for (const token of queryTokens) {
31
+ if (haystack.includes(token)) {
32
+ hits += 1;
33
+ }
34
+ }
35
+ return hits / queryTokens.length;
36
+ };
37
+
38
+ const recencyScore = (item: ContextItem): number => {
39
+ const ageMs = Date.now() - item.createdAt;
40
+ if (!Number.isFinite(ageMs) || ageMs < 0) {
41
+ return 0;
42
+ }
43
+ const ageHours = ageMs / (1000 * 60 * 60);
44
+ return 1 / (1 + ageHours);
45
+ };
46
+
47
+ export const defaultContextScorer: ContextScorer = {
48
+ score: (item: ContextItem, query: ContextQuery): number => {
49
+ const bias = clamp01(query.recencyBias ?? DEFAULT_RECENCY_BIAS);
50
+ const recency = recencyScore(item);
51
+ const keyword = keywordScore(item, query);
52
+ return bias * recency + (1 - bias) * keyword;
53
+ },
54
+ };
@@ -0,0 +1,122 @@
1
+ import type { LlmRequestParams } from './Types';
2
+
3
+ export type EvictionStrategy = 'lru' | 'recency' | 'size';
4
+
5
+ export interface MediaReference {
6
+ // Absolute path or URI depending on app policy.
7
+ uri: string;
8
+ mimeType?: string;
9
+ sizeBytes?: number;
10
+ width?: number;
11
+ height?: number;
12
+ }
13
+
14
+ export interface MediaValidationPolicy {
15
+ // Require absolute URIs (recommended for security).
16
+ requireAbsoluteUri?: boolean;
17
+ // Whitelist of allowed URI schemes (e.g., ['file', 'content']).
18
+ allowedUriSchemes?: string[];
19
+ // Size and dimension limits for referenced media.
20
+ maxMediaBytes?: number;
21
+ maxMediaWidth?: number;
22
+ maxMediaHeight?: number;
23
+ }
24
+
25
+ export interface ContextItem {
26
+ id: string;
27
+ text: string;
28
+ metadata?: Record<string, string>;
29
+ tags?: string[];
30
+ media?: MediaReference[];
31
+ createdAt: number;
32
+ updatedAt?: number;
33
+ ttlSeconds?: number;
34
+ }
35
+
36
+ export interface ContextQuery {
37
+ text: string;
38
+ limit?: number;
39
+ filters?: Record<string, string>;
40
+ tags?: string[];
41
+ // 0..1 weighting for recency in scoring.
42
+ recencyBias?: number;
43
+ useSemantic?: boolean;
44
+ }
45
+
46
+ export interface ContextPolicy {
47
+ maxBytes?: number;
48
+ maxItems?: number;
49
+ defaultTtlSeconds?: number;
50
+ evictionStrategy?: EvictionStrategy;
51
+ media?: MediaValidationPolicy;
52
+ }
53
+
54
+ export interface ContextStoreStats {
55
+ itemCount: number;
56
+ totalBytes?: number;
57
+ }
58
+
59
+ export interface ContextStore {
60
+ put(items: ContextItem[]): Promise<void>;
61
+ query(query: ContextQuery): Promise<ContextItem[]>;
62
+ delete(ids: string[]): Promise<void>;
63
+ compact(policy: ContextPolicy): Promise<void>;
64
+ stats(): Promise<ContextStoreStats>;
65
+ }
66
+
67
+ export interface EmbeddingProvider {
68
+ embed(texts: string[]): Promise<number[][]>;
69
+ dimension?: number;
70
+ }
71
+
72
+ export interface ContextEngine {
73
+ add(items: ContextItem[]): Promise<void>;
74
+ search(query: ContextQuery): Promise<ContextItem[]>;
75
+ setPolicy(policy: ContextPolicy): void;
76
+ compact(): Promise<void>;
77
+ formatRequest(params: ContextFormatParams): LlmRequestParams;
78
+ deriveQuery(
79
+ prompt: string,
80
+ request: LlmRequestParams
81
+ ): ContextQuery | undefined;
82
+ }
83
+
84
+ export interface ContextScorer {
85
+ score(item: ContextItem, query: ContextQuery): number;
86
+ }
87
+
88
+ export interface ContextEngineOptions {
89
+ store: ContextStore;
90
+ scorer?: ContextScorer;
91
+ policy?: ContextPolicy;
92
+ formatter?: ContextRequestFormatter;
93
+ defaultQueryFactory?: ContextDefaultQueryFactory;
94
+ }
95
+
96
+ export interface ContextFormatParams {
97
+ prompt: string;
98
+ items: ContextItem[];
99
+ query?: ContextQuery;
100
+ request: LlmRequestParams;
101
+ }
102
+
103
+ export type ContextRequestFormatter = (
104
+ params: ContextFormatParams
105
+ ) => LlmRequestParams;
106
+
107
+ export type ContextDefaultQueryFactory = (
108
+ prompt: string,
109
+ request: LlmRequestParams
110
+ ) => ContextQuery | undefined;
111
+
112
+ export interface ToolDefinition {
113
+ name: string;
114
+ description?: string;
115
+ // JSON schema or similar contract for tool input.
116
+ inputSchema?: Record<string, unknown>;
117
+ }
118
+
119
+ export interface ToolRegistry {
120
+ register(tool: ToolDefinition): void;
121
+ invoke(name: string, input: unknown): Promise<unknown>;
122
+ }