@ddwang/magnitude-core 0.3.1-ddwang.1

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 (142) hide show
  1. package/dist/actions/desktopActions.d.ts +58 -0
  2. package/dist/actions/desktopActions.js +154 -0
  3. package/dist/actions/index.d.ts +26 -0
  4. package/dist/actions/index.js +11 -0
  5. package/dist/actions/taskActions.d.ts +8 -0
  6. package/dist/actions/taskActions.js +27 -0
  7. package/dist/actions/types.d.ts +31 -0
  8. package/dist/actions/types.js +5 -0
  9. package/dist/actions/util.d.ts +20 -0
  10. package/dist/actions/util.js +297 -0
  11. package/dist/actions/webActions.d.ts +79 -0
  12. package/dist/actions/webActions.js +190 -0
  13. package/dist/agent/browserAgent.d.ts +29 -0
  14. package/dist/agent/browserAgent.js +122 -0
  15. package/dist/agent/errors.d.ts +8 -0
  16. package/dist/agent/errors.js +68 -0
  17. package/dist/agent/index.d.ts +53 -0
  18. package/dist/agent/index.js +377 -0
  19. package/dist/agent/narrator.d.ts +4 -0
  20. package/dist/agent/narrator.js +63 -0
  21. package/dist/agent/pause.test.d.ts +1 -0
  22. package/dist/agent/pause.test.js +194 -0
  23. package/dist/ai/baml_client/async_client.d.ts +73 -0
  24. package/dist/ai/baml_client/async_client.js +152 -0
  25. package/dist/ai/baml_client/async_request.d.ts +35 -0
  26. package/dist/ai/baml_client/async_request.js +98 -0
  27. package/dist/ai/baml_client/config.d.ts +11 -0
  28. package/dist/ai/baml_client/config.js +20 -0
  29. package/dist/ai/baml_client/globals.d.ts +16 -0
  30. package/dist/ai/baml_client/globals.js +37 -0
  31. package/dist/ai/baml_client/index.d.ts +16 -0
  32. package/dist/ai/baml_client/index.js +41 -0
  33. package/dist/ai/baml_client/inlinedbaml.d.ts +19 -0
  34. package/dist/ai/baml_client/inlinedbaml.js +31 -0
  35. package/dist/ai/baml_client/parser.d.ts +53 -0
  36. package/dist/ai/baml_client/parser.js +86 -0
  37. package/dist/ai/baml_client/partial_types.d.ts +61 -0
  38. package/dist/ai/baml_client/partial_types.js +10 -0
  39. package/dist/ai/baml_client/sync_client.d.ts +50 -0
  40. package/dist/ai/baml_client/sync_client.js +100 -0
  41. package/dist/ai/baml_client/sync_request.d.ts +35 -0
  42. package/dist/ai/baml_client/sync_request.js +98 -0
  43. package/dist/ai/baml_client/tracing.d.ts +16 -0
  44. package/dist/ai/baml_client/tracing.js +18 -0
  45. package/dist/ai/baml_client/type_builder.d.ts +38 -0
  46. package/dist/ai/baml_client/type_builder.js +96 -0
  47. package/dist/ai/baml_client/types.d.ts +69 -0
  48. package/dist/ai/baml_client/types.js +15 -0
  49. package/dist/ai/claudeCode.d.ts +1 -0
  50. package/dist/ai/claudeCode.js +186 -0
  51. package/dist/ai/modelHarness.d.ts +39 -0
  52. package/dist/ai/modelHarness.js +189 -0
  53. package/dist/ai/multiModelHarness.d.ts +27 -0
  54. package/dist/ai/multiModelHarness.js +52 -0
  55. package/dist/ai/types.d.ts +90 -0
  56. package/dist/ai/types.js +5 -0
  57. package/dist/ai/util.d.ts +13 -0
  58. package/dist/ai/util.js +165 -0
  59. package/dist/cli.d.ts +0 -0
  60. package/dist/cli.js +3 -0
  61. package/dist/common/actions.d.ts +0 -0
  62. package/dist/common/actions.js +14 -0
  63. package/dist/common/events.d.ts +15 -0
  64. package/dist/common/events.js +3 -0
  65. package/dist/common/failure.d.ts +68 -0
  66. package/dist/common/failure.js +1 -0
  67. package/dist/common/index.d.ts +3 -0
  68. package/dist/common/index.js +3 -0
  69. package/dist/common/retry.d.ts +16 -0
  70. package/dist/common/retry.js +30 -0
  71. package/dist/common/util.d.ts +23 -0
  72. package/dist/common/util.js +132 -0
  73. package/dist/connectors/browserConnector.d.ts +40 -0
  74. package/dist/connectors/browserConnector.js +127 -0
  75. package/dist/connectors/desktopConnector.d.ts +58 -0
  76. package/dist/connectors/desktopConnector.js +68 -0
  77. package/dist/connectors/index.d.ts +10 -0
  78. package/dist/connectors/index.js +2 -0
  79. package/dist/index.cjs +6143 -0
  80. package/dist/index.d.cts +825 -0
  81. package/dist/index.d.ts +20 -0
  82. package/dist/index.js +26 -0
  83. package/dist/index.mjs +6097 -0
  84. package/dist/logger.d.ts +3 -0
  85. package/dist/logger.js +15 -0
  86. package/dist/memory/agentMemory.d.ts +37 -0
  87. package/dist/memory/agentMemory.js +121 -0
  88. package/dist/memory/image.d.ts +26 -0
  89. package/dist/memory/image.js +106 -0
  90. package/dist/memory/index.d.ts +3 -0
  91. package/dist/memory/index.js +3 -0
  92. package/dist/memory/masking.d.ts +7 -0
  93. package/dist/memory/masking.js +141 -0
  94. package/dist/memory/masking.test.d.ts +1 -0
  95. package/dist/memory/masking.test.js +117 -0
  96. package/dist/memory/observation.d.ts +38 -0
  97. package/dist/memory/observation.js +79 -0
  98. package/dist/memory/rendering/index.d.ts +10 -0
  99. package/dist/memory/rendering/index.js +13 -0
  100. package/dist/memory/rendering/renderJsonParts.d.ts +3 -0
  101. package/dist/memory/rendering/renderJsonParts.js +115 -0
  102. package/dist/memory/rendering/renderJsonParts.test.d.ts +1 -0
  103. package/dist/memory/rendering/renderJsonParts.test.js +217 -0
  104. package/dist/memory/rendering/renderXmlParts.d.ts +7 -0
  105. package/dist/memory/rendering/renderXmlParts.js +127 -0
  106. package/dist/memory/serde.d.ts +20 -0
  107. package/dist/memory/serde.js +81 -0
  108. package/dist/memory/util.d.ts +2 -0
  109. package/dist/memory/util.js +17 -0
  110. package/dist/telemetry/events.d.ts +9 -0
  111. package/dist/telemetry/events.js +80 -0
  112. package/dist/telemetry/index.d.ts +6 -0
  113. package/dist/telemetry/index.js +91 -0
  114. package/dist/types.d.ts +30 -0
  115. package/dist/types.js +1 -0
  116. package/dist/util.d.ts +1 -0
  117. package/dist/util.js +12 -0
  118. package/dist/version.d.ts +1 -0
  119. package/dist/version.js +3 -0
  120. package/dist/web/browserProvider.d.ts +23 -0
  121. package/dist/web/browserProvider.js +153 -0
  122. package/dist/web/harness.d.ts +102 -0
  123. package/dist/web/harness.js +340 -0
  124. package/dist/web/stability.d.ts +57 -0
  125. package/dist/web/stability.js +316 -0
  126. package/dist/web/tabs.d.ts +58 -0
  127. package/dist/web/tabs.js +293 -0
  128. package/dist/web/transformer.d.ts +7 -0
  129. package/dist/web/transformer.js +41 -0
  130. package/dist/web/types.d.ts +32 -0
  131. package/dist/web/types.js +2 -0
  132. package/dist/web/util.d.ts +3 -0
  133. package/dist/web/util.js +81 -0
  134. package/dist/web/visualizer/cursor.d.ts +16 -0
  135. package/dist/web/visualizer/cursor.js +152 -0
  136. package/dist/web/visualizer/index.d.ts +22 -0
  137. package/dist/web/visualizer/index.js +47 -0
  138. package/dist/web/visualizer/mouseEffects.d.ts +18 -0
  139. package/dist/web/visualizer/mouseEffects.js +287 -0
  140. package/dist/web/visualizer/typeEffects.d.ts +10 -0
  141. package/dist/web/visualizer/typeEffects.js +190 -0
  142. package/package.json +71 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Observations and declarations for what kind of data is "observable" - i.e. can be processed into BAML context or JSON.
3
+ */
4
+ import { fnv1a32Hex } from '@/util';
5
+ import { observableDataToJson } from './serde';
6
+ import { renderContentParts } from './rendering';
7
+ export class Observation {
8
+ source; // where this observation came from
9
+ role;
10
+ timestamp; // time this observation was made
11
+ content; // the arbitrarily-structured multimedia content of this observation
12
+ retention;
13
+ constructor(source, role, content, retention, timestamp) {
14
+ this.source = source;
15
+ this.role = role;
16
+ this.content = content;
17
+ this.timestamp = timestamp ?? Date.now();
18
+ this.retention = retention;
19
+ }
20
+ static fromConnector(connectorId, content, options) {
21
+ return new Observation(`connector:${connectorId}`, 'user', content, options);
22
+ }
23
+ static fromActionTaken(actionId, content, options) {
24
+ // see notes on fromThought
25
+ return new Observation(`action:taken:${actionId}`, 'user', content, options);
26
+ }
27
+ static fromActionResult(actionId, content, options) {
28
+ return new Observation(`action:result:${actionId}`, 'user', content, options);
29
+ }
30
+ static fromThought(content, options) {
31
+ // may want to reconsider this structure - probably want to show something that actually matches what the assistant sent else it might pattern match.
32
+ // or just do these as user messages.
33
+ // changed to user for now
34
+ return new Observation(`thought`, 'user', content, options);
35
+ }
36
+ toString() {
37
+ // will be pretty ugly usually
38
+ return JSON.stringify(this.content);
39
+ }
40
+ async toJson() {
41
+ return await observableDataToJson(this.content);
42
+ }
43
+ async render(options) {
44
+ return {
45
+ role: this.role,
46
+ cacheControl: options?.cacheControl ?? false,
47
+ content: [...(options?.prefix ?? []), ...(await renderContentParts(this.content, { mode: 'json', indent: 2 })), ...(options?.postfix ?? [])]
48
+ };
49
+ }
50
+ // async renderContentParts(): Promise<MultiMediaContentPart[]> {
51
+ // return await renderParts(this.content);
52
+ // }
53
+ // async toContext(): Promise<BamlRenderable[]> {
54
+ // return await observableDataToContext(this.data);
55
+ // }
56
+ async hash() {
57
+ // build str of all text+img content
58
+ const stringifiedContent = JSON.stringify(await this.toJson());
59
+ return fnv1a32Hex(stringifiedContent);
60
+ }
61
+ async equals(obs) {
62
+ // TODO: deep eq for both text and img content
63
+ return (await this.hash()) == (await obs.hash());
64
+ }
65
+ }
66
+ // export async function renderObservations(observations: Observation[]): Promise<BamlRenderable[]> {
67
+ // /**
68
+ // * Refines observations (according to observation culling options) and converts to BAML-renderable content
69
+ // */
70
+ // const filteredObservations = await filterObservations(observations);
71
+ // let content: BamlRenderable[] = [];
72
+ // for (const obs of filteredObservations) {
73
+ // if (obs.source.startsWith('action:taken') || obs.source.startsWith('thought')) {
74
+ // content.push(`[${new Date(obs.timestamp).toTimeString().split(' ')[0]}]: `);
75
+ // }
76
+ // content = [...content, ...await obs.toContext(), "\n"];
77
+ // }
78
+ // return content;
79
+ // }
@@ -0,0 +1,10 @@
1
+ import { RenderableContent } from "@/memory/observation";
2
+ import { Image as BamlImage } from '@boundaryml/baml';
3
+ export type MultiMediaContentPart = BamlImage | string;
4
+ export type RenderOptions = {
5
+ mode: 'json';
6
+ indent: 0 | 2 | 4;
7
+ } | {
8
+ mode: 'xml';
9
+ };
10
+ export declare function renderContentParts(data: RenderableContent, options: RenderOptions): Promise<import("./renderXmlParts").MultiMediaContentPart[]>;
@@ -0,0 +1,13 @@
1
+ import { renderXmlParts } from "./renderXmlParts";
2
+ import { renderJsonParts } from "./renderJsonParts";
3
+ export async function renderContentParts(data, options) {
4
+ if (options.mode === 'json') {
5
+ return await renderJsonParts(data, options.indent);
6
+ }
7
+ else if (options.mode === 'xml') {
8
+ return await renderXmlParts(data);
9
+ }
10
+ else {
11
+ throw new Error(`Invalid render mode ${options.mode}`);
12
+ }
13
+ }
@@ -0,0 +1,3 @@
1
+ import { RenderableContent } from "@/memory/observation";
2
+ import { MultiMediaContentPart } from "@/memory/rendering";
3
+ export declare function renderJsonParts(data: RenderableContent, indent: number): Promise<MultiMediaContentPart[]>;
@@ -0,0 +1,115 @@
1
+ import { Image } from "@/memory/image";
2
+ import { Image as BamlImage } from '@boundaryml/baml';
3
+ async function buildJsonPartsRecursive(data, partsList, indent, currentLevel = 0, isRoot = false) {
4
+ if (data instanceof Image) {
5
+ const bamlImg = await data.toBaml();
6
+ if (bamlImg) {
7
+ partsList.push(bamlImg);
8
+ }
9
+ return;
10
+ }
11
+ if (data instanceof BamlImage) {
12
+ partsList.push(data);
13
+ return;
14
+ }
15
+ if (data === null) {
16
+ partsList.push('null');
17
+ return;
18
+ }
19
+ if (data === undefined) {
20
+ return; // Undefined values are omitted in JSON
21
+ }
22
+ if (typeof data === 'string') {
23
+ if (isRoot) {
24
+ partsList.push(data);
25
+ }
26
+ else {
27
+ partsList.push(JSON.stringify(data));
28
+ }
29
+ return;
30
+ }
31
+ if (typeof data === 'number' || typeof data === 'boolean') {
32
+ partsList.push(String(data));
33
+ return;
34
+ }
35
+ const newline = indent > 0 ? '\n' : '';
36
+ const spacing = indent > 0 ? ' '.repeat(currentLevel * indent) : '';
37
+ const nextSpacing = indent > 0 ? ' '.repeat((currentLevel + 1) * indent) : '';
38
+ if (Array.isArray(data)) {
39
+ partsList.push('[');
40
+ for (let index = 0; index < data.length; index++) {
41
+ const item = data[index];
42
+ if (item !== undefined) { // Skip undefined values in arrays
43
+ if (index > 0) {
44
+ partsList.push(',');
45
+ }
46
+ if (indent > 0) {
47
+ partsList.push(newline + nextSpacing);
48
+ }
49
+ else if (index > 0) {
50
+ partsList.push(' ');
51
+ }
52
+ await buildJsonPartsRecursive(item, partsList, indent, currentLevel + 1, false);
53
+ }
54
+ }
55
+ if (data.length > 0 && indent > 0) {
56
+ partsList.push(newline + spacing);
57
+ }
58
+ partsList.push(']');
59
+ return;
60
+ }
61
+ if (typeof data === 'object' && data !== null) {
62
+ partsList.push('{');
63
+ const entries = Object.entries(data).filter(([, val]) => val !== undefined);
64
+ for (let index = 0; index < entries.length; index++) {
65
+ const [key, value] = entries[index];
66
+ if (index > 0) {
67
+ partsList.push(',');
68
+ }
69
+ if (indent > 0) {
70
+ partsList.push(newline + nextSpacing);
71
+ }
72
+ else if (index > 0) {
73
+ partsList.push(' ');
74
+ }
75
+ // Add the key
76
+ partsList.push(JSON.stringify(key));
77
+ partsList.push(': ');
78
+ // Add the value
79
+ await buildJsonPartsRecursive(value, partsList, indent, currentLevel + 1, false);
80
+ }
81
+ if (entries.length > 0 && indent > 0) {
82
+ partsList.push(newline + spacing);
83
+ }
84
+ partsList.push('}');
85
+ return;
86
+ }
87
+ throw new Error(`Object type not supported for JSON rendering: '${typeof data}'. Data: ${data}`);
88
+ }
89
+ export async function renderJsonParts(data, indent) {
90
+ const rawList = [];
91
+ await buildJsonPartsRecursive(data, rawList, indent, 0, true);
92
+ // Merge adjacent strings
93
+ if (rawList.length === 0) {
94
+ return [];
95
+ }
96
+ const mergedList = [];
97
+ let currentString = "";
98
+ for (const item of rawList) {
99
+ if (typeof item === 'string') {
100
+ currentString += item;
101
+ }
102
+ else { // It's a BamlImage
103
+ if (currentString.length > 0) {
104
+ mergedList.push(currentString);
105
+ }
106
+ currentString = ""; // Reset accumulator
107
+ mergedList.push(item); // Push the BamlImage
108
+ }
109
+ }
110
+ // Add any trailing string
111
+ if (currentString.length > 0) {
112
+ mergedList.push(currentString);
113
+ }
114
+ return mergedList;
115
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,217 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { renderJsonParts } from './renderJsonParts';
3
+ import { Image } from '@/memory/image';
4
+ // Create a mock Image that will convert to BamlImage
5
+ class MockImage extends Image {
6
+ id;
7
+ constructor(id) {
8
+ // Pass a dummy Sharp instance
9
+ super(null);
10
+ this.id = id;
11
+ }
12
+ async toBaml() {
13
+ // Return a mock BamlImage
14
+ return {
15
+ __mockBamlImage: true,
16
+ id: this.id
17
+ };
18
+ }
19
+ }
20
+ describe('renderJsonParts', () => {
21
+ test('renders primitive types correctly', async () => {
22
+ expect(await renderJsonParts('hello', 0)).toEqual(['hello']);
23
+ expect(await renderJsonParts(42, 0)).toEqual(['42']);
24
+ expect(await renderJsonParts(true, 0)).toEqual(['true']);
25
+ expect(await renderJsonParts(false, 0)).toEqual(['false']);
26
+ expect(await renderJsonParts(null, 0)).toEqual(['null']);
27
+ });
28
+ test('renders root-level strings without quotes', async () => {
29
+ expect(await renderJsonParts('CLICK THE UNICORN', 0)).toEqual(['CLICK THE UNICORN']);
30
+ expect(await renderJsonParts('Hello World', 0)).toEqual(['Hello World']);
31
+ expect(await renderJsonParts('{"already":"json"}', 0)).toEqual(['{"already":"json"}']);
32
+ });
33
+ test('properly quotes strings inside objects and arrays', async () => {
34
+ // Strings in objects should be quoted
35
+ expect(await renderJsonParts({ message: 'hello' }, 0)).toEqual(['{"message": "hello"}']);
36
+ expect(await renderJsonParts({ name: 'John', city: 'NYC' }, 0)).toEqual(['{"name": "John", "city": "NYC"}']);
37
+ // Strings in arrays should be quoted
38
+ expect(await renderJsonParts(['apple', 'banana'], 0)).toEqual(['["apple", "banana"]']);
39
+ expect(await renderJsonParts(['hello', 123, 'world'], 0)).toEqual(['["hello", 123, "world"]']);
40
+ // Nested structures
41
+ expect(await renderJsonParts({ items: ['a', 'b'], text: 'test' }, 0)).toEqual(['{"items": ["a", "b"], "text": "test"}']);
42
+ });
43
+ test('renders empty structures', async () => {
44
+ expect(await renderJsonParts([], 0)).toEqual(['[]']);
45
+ expect(await renderJsonParts({}, 0)).toEqual(['{}']);
46
+ });
47
+ test('renders simple arrays', async () => {
48
+ expect(await renderJsonParts([1, 2, 3], 0)).toEqual(['[1, 2, 3]']);
49
+ expect(await renderJsonParts(['a', 'b', 'c'], 0)).toEqual(['["a", "b", "c"]']);
50
+ expect(await renderJsonParts([true, false, null], 0)).toEqual(['[true, false, null]']);
51
+ });
52
+ test('renders simple objects', async () => {
53
+ expect(await renderJsonParts({ name: 'John', age: 30 }, 0)).toEqual(['{"name": "John", "age": 30}']);
54
+ expect(await renderJsonParts({ a: 1, b: true, c: null }, 0)).toEqual(['{"a": 1, "b": true, "c": null}']);
55
+ });
56
+ test('handles undefined values correctly', async () => {
57
+ // Undefined at root level returns empty array
58
+ expect(await renderJsonParts(undefined, 0)).toEqual([]);
59
+ // Undefined in arrays is skipped
60
+ expect(await renderJsonParts([1, undefined, 3], 0)).toEqual(['[1, 3]']);
61
+ // Undefined in objects is omitted
62
+ expect(await renderJsonParts({ a: 1, b: undefined, c: 3 }, 0)).toEqual(['{"a": 1, "c": 3}']);
63
+ });
64
+ test('renders nested structures', async () => {
65
+ const nested = {
66
+ user: {
67
+ name: 'Alice',
68
+ scores: [95, 87, 92],
69
+ active: true
70
+ },
71
+ metadata: {
72
+ created: '2024-01-01',
73
+ tags: ['important', 'verified']
74
+ }
75
+ };
76
+ const result = await renderJsonParts(nested, 0);
77
+ expect(result).toEqual(['{"user": {"name": "Alice", "scores": [95, 87, 92], "active": true}, "metadata": {"created": "2024-01-01", "tags": ["important", "verified"]}}']);
78
+ });
79
+ test('splits JSON around BamlImage in object', async () => {
80
+ const mockImage = new MockImage('img1');
81
+ const data = {
82
+ title: 'Product',
83
+ image: mockImage,
84
+ price: 29.99
85
+ };
86
+ const result = await renderJsonParts(data, 0);
87
+ expect(result.length).toBe(3);
88
+ expect(result[0]).toBe('{"title": "Product", "image": ');
89
+ expect(result[1]).toHaveProperty('__mockBamlImage', true);
90
+ expect(result[2]).toBe(', "price": 29.99}');
91
+ });
92
+ test('splits JSON around multiple BamlImages in object', async () => {
93
+ const mockImage1 = new MockImage('img1');
94
+ const mockImage2 = new MockImage('img2');
95
+ const data = {
96
+ before: 'text',
97
+ image1: mockImage1,
98
+ middle: 'more text',
99
+ image2: mockImage2,
100
+ after: 'final'
101
+ };
102
+ const result = await renderJsonParts(data, 0);
103
+ expect(result.length).toBe(5);
104
+ expect(result[0]).toBe('{"before": "text", "image1": ');
105
+ expect(result[1]).toHaveProperty('__mockBamlImage', true);
106
+ expect(result[2]).toBe(', "middle": "more text", "image2": ');
107
+ expect(result[3]).toHaveProperty('__mockBamlImage', true);
108
+ expect(result[4]).toBe(', "after": "final"}');
109
+ });
110
+ test('splits JSON around BamlImage in array', async () => {
111
+ const mockImage = new MockImage('img1');
112
+ const data = ['start', mockImage, 'end'];
113
+ const result = await renderJsonParts(data, 0);
114
+ expect(result.length).toBe(3);
115
+ expect(result[0]).toBe('["start", ');
116
+ expect(result[1]).toHaveProperty('__mockBamlImage', true);
117
+ expect(result[2]).toBe(', "end"]');
118
+ });
119
+ test('handles array with only BamlImages', async () => {
120
+ const mockImage1 = new MockImage('img1');
121
+ const mockImage2 = new MockImage('img2');
122
+ const data = [mockImage1, mockImage2];
123
+ const result = await renderJsonParts(data, 0);
124
+ expect(result.length).toBe(5);
125
+ expect(result[0]).toBe('[');
126
+ expect(result[1]).toHaveProperty('__mockBamlImage', true);
127
+ expect(result[2]).toBe(', ');
128
+ expect(result[3]).toHaveProperty('__mockBamlImage', true);
129
+ expect(result[4]).toBe(']');
130
+ });
131
+ test('handles nested structures with images', async () => {
132
+ const mockImage = new MockImage('img1');
133
+ const data = {
134
+ user: {
135
+ name: 'Bob',
136
+ avatar: mockImage,
137
+ settings: {
138
+ theme: 'dark'
139
+ }
140
+ },
141
+ posts: [
142
+ { id: 1, image: mockImage, text: 'Hello' }
143
+ ]
144
+ };
145
+ const result = await renderJsonParts(data, 0);
146
+ // Should have multiple parts due to images
147
+ expect(result.length).toBeGreaterThan(1);
148
+ // First part should start with JSON
149
+ expect(result[0]).toMatch(/^{"user": /);
150
+ // Should contain the mock images
151
+ const imageParts = result.filter(part => part && typeof part === 'object' && '__mockBamlImage' in part);
152
+ expect(imageParts.length).toBe(2);
153
+ });
154
+ test('handles edge case with image as first array element', async () => {
155
+ const mockImage = new MockImage('img1');
156
+ const data = [mockImage, 'text'];
157
+ const result = await renderJsonParts(data, 0);
158
+ expect(result.length).toBe(3);
159
+ expect(result[0]).toBe('[');
160
+ expect(result[1]).toHaveProperty('__mockBamlImage', true);
161
+ expect(result[2]).toBe(', "text"]');
162
+ });
163
+ test('handles edge case with image as last array element', async () => {
164
+ const mockImage = new MockImage('img1');
165
+ const data = ['text', mockImage];
166
+ const result = await renderJsonParts(data, 0);
167
+ expect(result.length).toBe(3);
168
+ expect(result[0]).toBe('["text", ');
169
+ expect(result[1]).toHaveProperty('__mockBamlImage', true);
170
+ expect(result[2]).toBe(']');
171
+ });
172
+ test('properly escapes special characters in strings', async () => {
173
+ const data = {
174
+ quote: 'He said "Hello"',
175
+ newline: 'Line 1\nLine 2',
176
+ tab: 'Col1\tCol2',
177
+ backslash: 'C:\\Users\\Path'
178
+ };
179
+ const result = await renderJsonParts(data, 0);
180
+ expect(result[0]).toContain('\\"Hello\\"');
181
+ expect(result[0]).toContain('\\n');
182
+ expect(result[0]).toContain('\\t');
183
+ expect(result[0]).toContain('\\\\');
184
+ });
185
+ test('renders with proper indentation when indent is 2', async () => {
186
+ const data = {
187
+ name: 'John',
188
+ age: 30,
189
+ hobbies: ['reading', 'coding']
190
+ };
191
+ const result = await renderJsonParts(data, 2);
192
+ expect(result).toEqual(['{\n "name": "John",\n "age": 30,\n "hobbies": [\n "reading",\n "coding"\n ]\n}']);
193
+ });
194
+ test('renders with proper indentation when indent is 4', async () => {
195
+ const data = {
196
+ user: {
197
+ name: 'Alice',
198
+ active: true
199
+ }
200
+ };
201
+ const result = await renderJsonParts(data, 4);
202
+ expect(result).toEqual(['{\n "user": {\n "name": "Alice",\n "active": true\n }\n}']);
203
+ });
204
+ test('handles indentation with images', async () => {
205
+ const mockImage = new MockImage('img1');
206
+ const data = {
207
+ title: 'Product',
208
+ image: mockImage,
209
+ price: 29.99
210
+ };
211
+ const result = await renderJsonParts(data, 2);
212
+ expect(result.length).toBe(3);
213
+ expect(result[0]).toBe('{\n "title": "Product",\n "image": ');
214
+ expect(result[1]).toHaveProperty('__mockBamlImage', true);
215
+ expect(result[2]).toBe(',\n "price": 29.99\n}');
216
+ });
217
+ });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Convert observations to form that can be passed as BAML context, rendered as a custom XML-like structure.
3
+ */
4
+ import { Image as BamlImage } from '@boundaryml/baml';
5
+ import { RenderableContent } from '@/memory/observation';
6
+ export type MultiMediaContentPart = BamlImage | string;
7
+ export declare function renderXmlParts(data: RenderableContent): Promise<MultiMediaContentPart[]>;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Convert observations to form that can be passed as BAML context, rendered as a custom XML-like structure.
3
+ */
4
+ import { Image as BamlImage } from '@boundaryml/baml';
5
+ import { Image } from '@/memory/image';
6
+ //export type BamlRenderable = BamlImage | string;
7
+ // export type BamlRenderable = {
8
+ // role: ''
9
+ // content: (BamlImage | string)[]
10
+ // }
11
+ async function buildXmlPartsRecursive(data, indentLevel, partsList, isInsideList = false) {
12
+ const indent = ' '.repeat(indentLevel);
13
+ if (data instanceof Image) {
14
+ const bamlImg = await data.toBaml();
15
+ if (bamlImg) {
16
+ partsList.push(bamlImg);
17
+ }
18
+ return;
19
+ }
20
+ if (data instanceof BamlImage) {
21
+ partsList.push(data);
22
+ return;
23
+ }
24
+ if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' || data === null) {
25
+ partsList.push(String(data)); // No XML escaping, direct string conversion
26
+ return;
27
+ }
28
+ if (data === undefined) {
29
+ return; // Undefined values are omitted
30
+ }
31
+ if (Array.isArray(data)) {
32
+ // data.forEach(async (item, index) => {
33
+ // await buildXmlPartsRecursive(item, indentLevel, partsList, true);
34
+ // if (index < data.length - 1) {
35
+ // partsList.push("\n");
36
+ // }
37
+ // });
38
+ for (let index = 0; index < data.length; index++) {
39
+ await buildXmlPartsRecursive(data[index], indentLevel, partsList, true);
40
+ if (index < data.length - 1) {
41
+ partsList.push("\n");
42
+ }
43
+ }
44
+ return;
45
+ }
46
+ if (typeof data === 'object' && data !== null) {
47
+ const objectEntries = Object.entries(data).filter(([, val]) => val !== undefined);
48
+ objectEntries.forEach(async ([key, value], entryIndex) => {
49
+ const tagName = key; // Use key directly as tag name
50
+ const currentValueParts = [];
51
+ await buildXmlPartsRecursive(value, indentLevel + 1, currentValueParts, false);
52
+ // Merge adjacent strings in currentValueParts for easier analysis
53
+ const mergedValueParts = [];
54
+ let currentStr = "";
55
+ for (const part of currentValueParts) {
56
+ if (typeof part === 'string') {
57
+ currentStr += part;
58
+ }
59
+ else {
60
+ if (currentStr)
61
+ mergedValueParts.push(currentStr);
62
+ currentStr = "";
63
+ mergedValueParts.push(part);
64
+ }
65
+ }
66
+ if (currentStr)
67
+ mergedValueParts.push(currentStr);
68
+ // Apply styling rules
69
+ if (mergedValueParts.length === 1 && mergedValueParts[0] instanceof BamlImage) {
70
+ partsList.push(`${indent}<${tagName}>`);
71
+ partsList.push(mergedValueParts[0]); // The BamlImage
72
+ partsList.push(`</${tagName}>`);
73
+ }
74
+ else if (mergedValueParts.length === 1 && typeof mergedValueParts[0] === 'string') {
75
+ const contentStr = mergedValueParts[0];
76
+ if (contentStr.includes('\n')) {
77
+ partsList.push(`${indent}<${tagName}>\n${contentStr}\n${indent}</${tagName}>`);
78
+ }
79
+ else {
80
+ partsList.push(`${indent}<${tagName}>${contentStr}</${tagName}>`);
81
+ }
82
+ }
83
+ else { // Empty content or multiple parts (complex/nested)
84
+ partsList.push(`${indent}<${tagName}>\n`);
85
+ mergedValueParts.forEach((part, partIdx) => {
86
+ partsList.push(part);
87
+ if (partIdx < mergedValueParts.length - 1) {
88
+ partsList.push("\n");
89
+ }
90
+ });
91
+ partsList.push(`\n${indent}</${tagName}>`);
92
+ }
93
+ if (entryIndex < objectEntries.length - 1) {
94
+ partsList.push("\n"); // Newline between sibling XML elements
95
+ }
96
+ });
97
+ return;
98
+ }
99
+ throw new Error(`Object type not supported for LLM context: ${typeof data}`);
100
+ }
101
+ export async function renderXmlParts(data) {
102
+ const rawList = [];
103
+ await buildXmlPartsRecursive(data, 0, rawList);
104
+ // Merge adjacent strings
105
+ if (rawList.length === 0) {
106
+ return [];
107
+ }
108
+ const mergedList = [];
109
+ let currentString = "";
110
+ for (const item of rawList) {
111
+ if (typeof item === 'string') {
112
+ currentString += item;
113
+ }
114
+ else { // It's a BamlImage
115
+ if (currentString.length > 0) {
116
+ mergedList.push(currentString);
117
+ }
118
+ currentString = ""; // Reset accumulator
119
+ mergedList.push(item); // Push the BamlImage
120
+ }
121
+ }
122
+ // Add any trailing string
123
+ if (currentString.length > 0) {
124
+ mergedList.push(currentString);
125
+ }
126
+ return mergedList;
127
+ }
@@ -0,0 +1,20 @@
1
+ import { RenderableContent } from "./observation";
2
+ export type StoredMedia = {
3
+ type: 'media';
4
+ format: string;
5
+ storage: 'base64';
6
+ base64: string;
7
+ };
8
+ export interface StoredPrimitive {
9
+ type: 'primitive';
10
+ content: string | boolean | number;
11
+ }
12
+ type MultiMediaPrimitive = StoredMedia | StoredPrimitive | undefined | null;
13
+ type MultiMediaArray = Array<MultiMediaJson>;
14
+ export type MultiMediaObject = {
15
+ [key: string]: MultiMediaJson;
16
+ };
17
+ export type MultiMediaJson = MultiMediaPrimitive | MultiMediaArray | MultiMediaObject;
18
+ export declare function observableDataToJson(data: RenderableContent): Promise<MultiMediaJson>;
19
+ export declare function jsonToObservableData(data: MultiMediaJson): Promise<RenderableContent>;
20
+ export {};