mdi-llmkit 0.1.0 → 1.0.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 (36) hide show
  1. package/README.md +116 -34
  2. package/dist/src/comparison/compareLists.d.ts +97 -0
  3. package/dist/src/comparison/compareLists.js +375 -0
  4. package/dist/src/comparison/index.d.ts +1 -0
  5. package/dist/src/comparison/index.js +1 -0
  6. package/dist/src/gptApi/functions.d.ts +21 -0
  7. package/dist/src/gptApi/functions.js +154 -0
  8. package/dist/src/gptApi/gptConversation.d.ts +43 -0
  9. package/dist/src/gptApi/gptConversation.js +146 -0
  10. package/dist/src/gptApi/index.d.ts +3 -0
  11. package/dist/src/gptApi/index.js +3 -0
  12. package/dist/src/gptApi/jsonSchemaFormat.d.ts +14 -0
  13. package/dist/src/gptApi/jsonSchemaFormat.js +198 -0
  14. package/dist/src/index.d.ts +3 -0
  15. package/dist/src/index.js +3 -0
  16. package/dist/src/jsonSurgery/jsonSurgery.d.ts +81 -0
  17. package/dist/src/jsonSurgery/jsonSurgery.js +776 -0
  18. package/dist/src/jsonSurgery/placemarkedJSON.d.ts +57 -0
  19. package/dist/src/jsonSurgery/placemarkedJSON.js +151 -0
  20. package/dist/tests/comparison/compareLists.test.d.ts +1 -0
  21. package/dist/tests/comparison/compareLists.test.js +434 -0
  22. package/dist/tests/gptApi/gptConversation.test.d.ts +1 -0
  23. package/dist/tests/gptApi/gptConversation.test.js +157 -0
  24. package/dist/tests/gptApi/gptSubmit.test.d.ts +1 -0
  25. package/dist/tests/gptApi/gptSubmit.test.js +161 -0
  26. package/dist/tests/gptApi/jsonSchemaFormat.test.d.ts +1 -0
  27. package/dist/tests/gptApi/jsonSchemaFormat.test.js +372 -0
  28. package/dist/tests/jsonSurgery/jsonSurgery.test.d.ts +1 -0
  29. package/dist/tests/jsonSurgery/jsonSurgery.test.js +729 -0
  30. package/dist/tests/jsonSurgery/placemarkedJSON.test.d.ts +1 -0
  31. package/dist/tests/jsonSurgery/placemarkedJSON.test.js +209 -0
  32. package/dist/tests/setupEnv.d.ts +1 -0
  33. package/dist/tests/setupEnv.js +4 -0
  34. package/dist/tests/subpathExports.test.d.ts +1 -0
  35. package/dist/tests/subpathExports.test.js +47 -0
  36. package/package.json +18 -5
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,209 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { navigateToJSONPath, placemarkedJSONStringify, } from '../../src/jsonSurgery/placemarkedJSON.js';
3
+ describe('placemarkedJSONStringify', () => {
4
+ describe('root annotation and path placemarks', () => {
5
+ it('adds root and nested placemark comments', () => {
6
+ const value = {
7
+ items: [{ title: 'Inception' }],
8
+ metadata: { createdBy: 'Admin' },
9
+ };
10
+ const output = placemarkedJSONStringify(value);
11
+ expect(output).toContain('// root');
12
+ expect(output).toContain('// root["items"]');
13
+ expect(output).toContain('// root["items"][0]');
14
+ expect(output).toContain('// root["metadata"]');
15
+ });
16
+ it('annotates deeply nested object and array paths', () => {
17
+ const value = {
18
+ groups: [
19
+ {
20
+ members: [{ profile: { name: 'A' } }],
21
+ },
22
+ ],
23
+ };
24
+ const output = placemarkedJSONStringify(value);
25
+ expect(output).toContain('// root["groups"]');
26
+ expect(output).toContain('// root["groups"][0]');
27
+ expect(output).toContain('// root["groups"][0]["members"]');
28
+ expect(output).toContain('// root["groups"][0]["members"][0]');
29
+ expect(output).toContain('// root["groups"][0]["members"][0]["profile"]');
30
+ });
31
+ });
32
+ describe('value serialization by type', () => {
33
+ it('serializes primitive values and null in JSON form', () => {
34
+ const value = {
35
+ text: 'hello',
36
+ count: 42,
37
+ isActive: true,
38
+ empty: null,
39
+ };
40
+ const output = placemarkedJSONStringify(value);
41
+ expect(output).toContain('"text": "hello"');
42
+ expect(output).toContain('"count": 42');
43
+ expect(output).toContain('"isActive": true');
44
+ expect(output).toContain('"empty": null');
45
+ });
46
+ it('escapes special characters in strings using JSON escaping', () => {
47
+ const value = {
48
+ quoted: 'He said "hello"',
49
+ multiline: 'line1\nline2',
50
+ };
51
+ const output = placemarkedJSONStringify(value);
52
+ expect(output).toContain('"quoted": "He said \\"hello\\""');
53
+ expect(output).toContain('"multiline": "line1\\nline2"');
54
+ });
55
+ });
56
+ describe('array formatting and element annotations', () => {
57
+ it('formats arrays with bracket lines and per-index comments', () => {
58
+ const output = placemarkedJSONStringify(['alpha', 'beta']);
59
+ expect(output).toContain('[\n');
60
+ expect(output).toContain('// root[0]');
61
+ expect(output).toContain('// root[1]');
62
+ expect(output).toContain('"alpha",');
63
+ expect(output).toContain('"beta"');
64
+ });
65
+ });
66
+ describe('object formatting and skipped keys filtering', () => {
67
+ it('omits keys listed in skippedKeys while keeping other object fields', () => {
68
+ const value = {
69
+ keep: { enabled: true },
70
+ skip: { enabled: false },
71
+ };
72
+ const output = placemarkedJSONStringify(value, 2, ['skip']);
73
+ expect(output).toContain('"keep":');
74
+ expect(output).toContain('// root["keep"]');
75
+ expect(output).not.toContain('"skip":');
76
+ expect(output).not.toContain('// root["skip"]');
77
+ });
78
+ it('skips matching keys at multiple nesting levels', () => {
79
+ const value = {
80
+ skip: 'root-value',
81
+ nested: {
82
+ keep: true,
83
+ skip: 'nested-value',
84
+ },
85
+ rows: [
86
+ { id: 1, skip: 'row-a' },
87
+ { id: 2, skip: 'row-b' },
88
+ ],
89
+ };
90
+ const output = placemarkedJSONStringify(value, 2, ['skip']);
91
+ expect(output).toContain('"nested":');
92
+ expect(output).toContain('"rows":');
93
+ expect(output).toContain('"id": 1');
94
+ expect(output).toContain('"id": 2');
95
+ expect(output).not.toContain('"skip":');
96
+ expect(output).not.toContain('root-value');
97
+ expect(output).not.toContain('nested-value');
98
+ expect(output).not.toContain('row-a');
99
+ expect(output).not.toContain('row-b');
100
+ });
101
+ });
102
+ describe('property value line-combining for primitives', () => {
103
+ it('keeps primitive object properties on a single line', () => {
104
+ const output = placemarkedJSONStringify({ name: 'Kaiizen', index: 7 });
105
+ expect(output).toContain('"name": "Kaiizen"');
106
+ expect(output).toContain('"index": 7');
107
+ expect(output).not.toContain('"name": \n');
108
+ expect(output).not.toContain('"index": \n');
109
+ });
110
+ });
111
+ describe('indent handling and final output trimming', () => {
112
+ it('applies custom indentation and returns trimmed output', () => {
113
+ const output = placemarkedJSONStringify({ nested: { value: 1 } }, 4);
114
+ expect(output).toContain('\n "nested":');
115
+ expect(output).toContain('\n "value": 1');
116
+ expect(output.endsWith('\n')).toBe(false);
117
+ expect(output).toBe(output.trim());
118
+ });
119
+ it('falls back to 2 spaces when indent is 0 or undefined', () => {
120
+ const zeroIndentOutput = placemarkedJSONStringify({ nested: { value: 1 } }, 0);
121
+ const undefinedIndentOutput = placemarkedJSONStringify({ nested: { value: 1 } });
122
+ expect(zeroIndentOutput).toContain('\n "nested":');
123
+ expect(zeroIndentOutput).toContain('\n "value": 1');
124
+ expect(undefinedIndentOutput).toContain('\n "nested":');
125
+ expect(undefinedIndentOutput).toContain('\n "value": 1');
126
+ });
127
+ it('produces stable multiline structure for mixed nested values', () => {
128
+ const output = placemarkedJSONStringify({
129
+ name: 'Example',
130
+ config: {
131
+ enabled: true,
132
+ levels: [1, 2],
133
+ },
134
+ });
135
+ expect(output).toBe(`// root
136
+ {
137
+ "name": "Example",
138
+
139
+ // root["config"]
140
+ "config":
141
+ {
142
+ "enabled": true,
143
+
144
+ // root["config"]["levels"]
145
+ "levels":
146
+ [
147
+ // root["config"]["levels"][0]
148
+ 1,
149
+
150
+ // root["config"]["levels"][1]
151
+ 2
152
+ ]
153
+ }
154
+ }`);
155
+ });
156
+ });
157
+ });
158
+ describe('navigateToJSONPath', () => {
159
+ describe('path traversal through object keys and array indexes', () => {
160
+ it('resolves paths that mix object keys and array indexes', () => {
161
+ const obj = {
162
+ sections: [{ title: 'Intro' }, { title: 'Details' }],
163
+ };
164
+ const result = navigateToJSONPath(obj, ['sections', 1, 'title']);
165
+ expect(result.pathTarget).toBe('Details');
166
+ expect(result.pathKeyOrIndex).toBe('title');
167
+ });
168
+ });
169
+ describe('returned pathParent, pathKeyOrIndex, and pathTarget tuple', () => {
170
+ it('returns parent reference, last key/index, and target value', () => {
171
+ const obj = {
172
+ sections: [{ title: 'Intro' }, { title: 'Details' }],
173
+ };
174
+ const result = navigateToJSONPath(obj, ['sections', 1]);
175
+ expect(result.pathParent).toBe(obj.sections);
176
+ expect(result.pathKeyOrIndex).toBe(1);
177
+ expect(result.pathTarget).toBe(obj.sections[1]);
178
+ });
179
+ it('returns root tuple values for an empty jsonPath', () => {
180
+ const obj = { a: 1 };
181
+ const result = navigateToJSONPath(obj, []);
182
+ expect(result.pathParent).toBeNull();
183
+ expect(result.pathKeyOrIndex).toBeNull();
184
+ expect(result.pathTarget).toBe(obj);
185
+ });
186
+ });
187
+ describe('error when an intermediate parent is null or undefined', () => {
188
+ it('returns an existing null leaf target when the parent exists', () => {
189
+ const obj = {
190
+ metadata: {
191
+ optional: null,
192
+ },
193
+ };
194
+ const result = navigateToJSONPath(obj, ['metadata', 'optional']);
195
+ expect(result.pathParent).toBe(obj.metadata);
196
+ expect(result.pathKeyOrIndex).toBe('optional');
197
+ expect(result.pathTarget).toBeNull();
198
+ });
199
+ it('throws with a helpful message when traversal goes past undefined', () => {
200
+ const obj = {
201
+ metadata: {},
202
+ };
203
+ const action = () => navigateToJSONPath(obj, ['metadata', 'missing', 'leaf']);
204
+ expect(action).toThrowError(/Could not navigate to path/);
205
+ expect(action).toThrowError(/"metadata","missing","leaf"/);
206
+ expect(action).toThrowError(/"leaf" is null or undefined/);
207
+ });
208
+ });
209
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ // Loads environment variables from .env for Vitest tests
2
+ import dotenv from 'dotenv';
3
+ console.log(`Loading .env from CWD=${process.cwd()}`);
4
+ dotenv.config();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { readFile } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { beforeAll, describe, expect, it } from 'vitest';
6
+ const DIST_GPTAPI_INDEX = path.resolve('dist/src/gptApi/index.js');
7
+ const DIST_JSON_SURGERY = path.resolve('dist/src/jsonSurgery/jsonSurgery.js');
8
+ const DIST_COMPARISON_INDEX = path.resolve('dist/src/comparison/index.js');
9
+ beforeAll(() => {
10
+ if (!existsSync(DIST_GPTAPI_INDEX) ||
11
+ !existsSync(DIST_JSON_SURGERY) ||
12
+ !existsSync(DIST_COMPARISON_INDEX)) {
13
+ execSync('npm run build', { stdio: 'inherit' });
14
+ }
15
+ });
16
+ describe('package subpath exports', () => {
17
+ it('declares gptApi, jsonSurgery, and comparison in package exports', async () => {
18
+ const packageJsonPath = path.resolve('package.json');
19
+ const packageJsonRaw = await readFile(packageJsonPath, 'utf8');
20
+ const packageJson = JSON.parse(packageJsonRaw);
21
+ expect(packageJson.exports?.['./gptApi']).toBeDefined();
22
+ expect(packageJson.exports?.['./gptApi']?.types).toBe('./dist/src/gptApi/index.d.ts');
23
+ expect(packageJson.exports?.['./gptApi']?.import).toBe('./dist/src/gptApi/index.js');
24
+ expect(packageJson.exports?.['./jsonSurgery']).toBeDefined();
25
+ expect(packageJson.exports?.['./jsonSurgery']?.types).toBe('./dist/src/jsonSurgery/jsonSurgery.d.ts');
26
+ expect(packageJson.exports?.['./jsonSurgery']?.import).toBe('./dist/src/jsonSurgery/jsonSurgery.js');
27
+ expect(packageJson.exports?.['./comparison']).toBeDefined();
28
+ expect(packageJson.exports?.['./comparison']?.types).toBe('./dist/src/comparison/index.d.ts');
29
+ expect(packageJson.exports?.['./comparison']?.import).toBe('./dist/src/comparison/index.js');
30
+ });
31
+ it('imports GPT API symbols from "mdi-llmkit/gptApi"', async () => {
32
+ const mod = await import('mdi-llmkit/gptApi');
33
+ expect(typeof mod.gptSubmit).toBe('function');
34
+ expect(typeof mod.GptConversation).toBe('function');
35
+ expect(typeof mod.JSONSchemaFormat).toBe('function');
36
+ });
37
+ it('imports jsonSurgery symbols from "mdi-llmkit/jsonSurgery"', async () => {
38
+ const mod = await import('mdi-llmkit/jsonSurgery');
39
+ expect(typeof mod.jsonSurgery).toBe('function');
40
+ expect(typeof mod.JSONSurgeryError).toBe('function');
41
+ });
42
+ it('imports comparison symbols from "mdi-llmkit/comparison"', async () => {
43
+ const mod = await import('mdi-llmkit/comparison');
44
+ expect(typeof mod.compareItemLists).toBe('function');
45
+ expect(typeof mod.ItemComparisonResult).toBe('object');
46
+ });
47
+ });
package/package.json CHANGED
@@ -1,17 +1,29 @@
1
1
  {
2
2
  "name": "mdi-llmkit",
3
- "version": "0.1.0",
3
+ "version": "1.0.1",
4
4
  "description": "Utilities for managing multi-shot conversations and structured data handling in LLM applications",
5
5
  "author": "Mikhail Voloshin",
6
6
  "license": "MIT",
7
7
  "type": "module",
8
8
  "sideEffects": false,
9
- "main": "dist/index.js",
10
- "types": "dist/index.d.ts",
9
+ "main": "dist/src/index.js",
10
+ "types": "dist/src/index.d.ts",
11
11
  "exports": {
12
12
  ".": {
13
- "types": "./dist/index.d.ts",
14
- "import": "./dist/index.js"
13
+ "types": "./dist/src/index.d.ts",
14
+ "import": "./dist/src/index.js"
15
+ },
16
+ "./gptApi": {
17
+ "types": "./dist/src/gptApi/index.d.ts",
18
+ "import": "./dist/src/gptApi/index.js"
19
+ },
20
+ "./jsonSurgery": {
21
+ "types": "./dist/src/jsonSurgery/jsonSurgery.d.ts",
22
+ "import": "./dist/src/jsonSurgery/jsonSurgery.js"
23
+ },
24
+ "./comparison": {
25
+ "types": "./dist/src/comparison/index.d.ts",
26
+ "import": "./dist/src/comparison/index.js"
15
27
  }
16
28
  },
17
29
  "files": [
@@ -30,6 +42,7 @@
30
42
  "chat"
31
43
  ],
32
44
  "dependencies": {
45
+ "dotenv": "^17.3.1",
33
46
  "openai": "^6.2.0"
34
47
  },
35
48
  "devDependencies": {