@vertesia/client 0.78.0 → 0.79.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.
- package/lib/cjs/AppsApi.js +8 -0
- package/lib/cjs/AppsApi.js.map +1 -1
- package/lib/cjs/InteractionBase.js.map +1 -1
- package/lib/cjs/InteractionCatalogApi.js +64 -0
- package/lib/cjs/InteractionCatalogApi.js.map +1 -0
- package/lib/cjs/InteractionOutput.js +300 -0
- package/lib/cjs/InteractionOutput.js.map +1 -0
- package/lib/cjs/InteractionResult.example.js +57 -0
- package/lib/cjs/InteractionResult.example.js.map +1 -0
- package/lib/cjs/InteractionsApi.js +28 -6
- package/lib/cjs/InteractionsApi.js.map +1 -1
- package/lib/cjs/ProjectsApi.js +2 -1
- package/lib/cjs/ProjectsApi.js.map +1 -1
- package/lib/cjs/RunsApi.js +12 -4
- package/lib/cjs/RunsApi.js.map +1 -1
- package/lib/cjs/client.js +122 -39
- package/lib/cjs/client.js.map +1 -1
- package/lib/cjs/execute.js +6 -0
- package/lib/cjs/execute.js.map +1 -1
- package/lib/cjs/index.js +6 -0
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/store/CollectionsApi.js +21 -0
- package/lib/cjs/store/CollectionsApi.js.map +1 -1
- package/lib/cjs/store/FilesApi.js +9 -8
- package/lib/cjs/store/FilesApi.js.map +1 -1
- package/lib/cjs/store/ObjectsApi.js +36 -18
- package/lib/cjs/store/ObjectsApi.js.map +1 -1
- package/lib/cjs/store/TypesApi.js +1 -1
- package/lib/cjs/store/TypesApi.js.map +1 -1
- package/lib/cjs/store/WorkflowsApi.js +151 -2
- package/lib/cjs/store/WorkflowsApi.js.map +1 -1
- package/lib/cjs/store/client.js +17 -1
- package/lib/cjs/store/client.js.map +1 -1
- package/lib/cjs/store/version.js +6 -0
- package/lib/cjs/store/version.js.map +1 -0
- package/lib/esm/AppsApi.js +8 -0
- package/lib/esm/AppsApi.js.map +1 -1
- package/lib/esm/InteractionBase.js.map +1 -1
- package/lib/esm/InteractionCatalogApi.js +60 -0
- package/lib/esm/InteractionCatalogApi.js.map +1 -0
- package/lib/esm/InteractionOutput.js +293 -0
- package/lib/esm/InteractionOutput.js.map +1 -0
- package/lib/esm/InteractionResult.example.js +55 -0
- package/lib/esm/InteractionResult.example.js.map +1 -0
- package/lib/esm/InteractionsApi.js +29 -7
- package/lib/esm/InteractionsApi.js.map +1 -1
- package/lib/esm/ProjectsApi.js +1 -1
- package/lib/esm/ProjectsApi.js.map +1 -1
- package/lib/esm/RunsApi.js +12 -4
- package/lib/esm/RunsApi.js.map +1 -1
- package/lib/esm/client.js +121 -38
- package/lib/esm/client.js.map +1 -1
- package/lib/esm/execute.js +5 -0
- package/lib/esm/execute.js.map +1 -1
- package/lib/esm/index.js +3 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/store/CollectionsApi.js +21 -0
- package/lib/esm/store/CollectionsApi.js.map +1 -1
- package/lib/esm/store/FilesApi.js +9 -8
- package/lib/esm/store/FilesApi.js.map +1 -1
- package/lib/esm/store/ObjectsApi.js +36 -18
- package/lib/esm/store/ObjectsApi.js.map +1 -1
- package/lib/esm/store/TypesApi.js +1 -1
- package/lib/esm/store/TypesApi.js.map +1 -1
- package/lib/esm/store/WorkflowsApi.js +151 -2
- package/lib/esm/store/WorkflowsApi.js.map +1 -1
- package/lib/esm/store/client.js +17 -1
- package/lib/esm/store/client.js.map +1 -1
- package/lib/esm/store/version.js +3 -0
- package/lib/esm/store/version.js.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types/AccountApi.d.ts +1 -0
- package/lib/types/AccountsApi.d.ts +1 -0
- package/lib/types/AnalyticsApi.d.ts +1 -0
- package/lib/types/ApiKeysApi.d.ts +1 -0
- package/lib/types/AppsApi.d.ts +8 -1
- package/lib/types/AppsApi.d.ts.map +1 -1
- package/lib/types/CommandsApi.d.ts +1 -0
- package/lib/types/EnvironmentsApi.d.ts +1 -0
- package/lib/types/GroupsApi.d.ts +1 -0
- package/lib/types/IamApi.d.ts +1 -0
- package/lib/types/InteractionBase.d.ts +3 -2
- package/lib/types/InteractionBase.d.ts.map +1 -1
- package/lib/types/InteractionCatalogApi.d.ts +37 -0
- package/lib/types/InteractionCatalogApi.d.ts.map +1 -0
- package/lib/types/InteractionOutput.d.ts +175 -0
- package/lib/types/InteractionOutput.d.ts.map +1 -0
- package/lib/types/InteractionResult.example.d.ts +7 -0
- package/lib/types/InteractionResult.example.d.ts.map +1 -0
- package/lib/types/InteractionsApi.d.ts +16 -6
- package/lib/types/InteractionsApi.d.ts.map +1 -1
- package/lib/types/ProjectsApi.d.ts +2 -1
- package/lib/types/ProjectsApi.d.ts.map +1 -1
- package/lib/types/PromptsApi.d.ts +1 -0
- package/lib/types/RefsApi.d.ts +1 -0
- package/lib/types/RunsApi.d.ts +7 -4
- package/lib/types/RunsApi.d.ts.map +1 -1
- package/lib/types/StreamSource.d.ts +1 -0
- package/lib/types/TrainingApi.d.ts +1 -0
- package/lib/types/UsersApi.d.ts +1 -0
- package/lib/types/client.d.ts +15 -5
- package/lib/types/client.d.ts.map +1 -1
- package/lib/types/execute.d.ts +5 -3
- package/lib/types/execute.d.ts.map +1 -1
- package/lib/types/index.d.ts +6 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/nodejs/NodeStreamSource.d.ts +1 -0
- package/lib/types/nodejs/index.d.ts +1 -0
- package/lib/types/store/AgentsApi.d.ts +1 -0
- package/lib/types/store/AnalyzeDocApi.d.ts +1 -0
- package/lib/types/store/CollectionsApi.d.ts +8 -0
- package/lib/types/store/CollectionsApi.d.ts.map +1 -1
- package/lib/types/store/CommandsApi.d.ts +1 -0
- package/lib/types/store/EmbeddingsApi.d.ts +1 -0
- package/lib/types/store/FilesApi.d.ts +4 -2
- package/lib/types/store/FilesApi.d.ts.map +1 -1
- package/lib/types/store/ObjectsApi.d.ts +13 -15
- package/lib/types/store/ObjectsApi.d.ts.map +1 -1
- package/lib/types/store/TypesApi.d.ts +1 -0
- package/lib/types/store/WorkflowsApi.d.ts +13 -0
- package/lib/types/store/WorkflowsApi.d.ts.map +1 -1
- package/lib/types/store/client.d.ts +8 -1
- package/lib/types/store/client.d.ts.map +1 -1
- package/lib/types/store/errors.d.ts +1 -0
- package/lib/types/store/index.d.ts +1 -0
- package/lib/types/store/version.d.ts +3 -0
- package/lib/types/store/version.d.ts.map +1 -0
- package/lib/vertesia-client.js +1 -1
- package/lib/vertesia-client.js.map +1 -1
- package/package.json +54 -54
- package/src/AppsApi.ts +10 -1
- package/src/InteractionBase.ts +3 -3
- package/src/InteractionOutput.test.ts +305 -0
- package/src/InteractionOutput.ts +328 -0
- package/src/InteractionResult.example.ts +72 -0
- package/src/InteractionsApi.ts +28 -10
- package/src/RunsApi.ts +7 -4
- package/src/client.ts +18 -0
- package/src/execute.ts +11 -5
- package/src/index.ts +1 -0
- package/src/store/CollectionsApi.ts +24 -0
- package/src/store/ObjectsApi.ts +40 -13
- package/src/store/TypesApi.ts +1 -1
- package/src/store/WorkflowsApi.ts +5 -2
- package/src/store/client.ts +18 -1
- package/src/store/version.ts +2 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { CompletionResult } from '@llumiverse/common';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { InteractionOutput, IS_INTERACTION_OUTPUT } from './InteractionOutput.js';
|
|
4
|
+
|
|
5
|
+
describe('InteractionOutput', () => {
|
|
6
|
+
const sampleResults: CompletionResult[] = [
|
|
7
|
+
{ type: 'text', value: 'Hello, ' },
|
|
8
|
+
{ type: 'text', value: 'World!' },
|
|
9
|
+
{ type: 'json', value: { name: 'Alice', age: 30 } },
|
|
10
|
+
{ type: 'json', value: { title: 'Engineer', level: 'Senior' } },
|
|
11
|
+
{ type: 'image', value: 'data:image/png;base64,iVBORw0K...' },
|
|
12
|
+
{ type: 'image', value: 'https://example.com/image.jpg' }
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
describe('text accessors', () => {
|
|
16
|
+
it('should concatenate all text results', () => {
|
|
17
|
+
const output = InteractionOutput.from(sampleResults);
|
|
18
|
+
expect(output.text('')).toBe('Hello, World!');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should return empty string when no text results exist', () => {
|
|
22
|
+
const output = InteractionOutput.from([
|
|
23
|
+
{ type: 'json', value: { foo: 'bar' } }
|
|
24
|
+
]);
|
|
25
|
+
expect(output.text()).toBe('');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return all text values as array', () => {
|
|
29
|
+
const output = InteractionOutput.from(sampleResults);
|
|
30
|
+
expect(output.texts()).toEqual(['Hello, ', 'World!']);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('object accessors', () => {
|
|
36
|
+
it('should return first JSON object', () => {
|
|
37
|
+
const output = InteractionOutput.from<{ name: string; age: number }>(sampleResults);
|
|
38
|
+
const obj = output.object();
|
|
39
|
+
expect(obj).toEqual({ name: 'Alice', age: 30 });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should return all JSON objects', () => {
|
|
43
|
+
const output = InteractionOutput.from(sampleResults);
|
|
44
|
+
const objects = output.objects();
|
|
45
|
+
expect(objects).toEqual([
|
|
46
|
+
{ name: 'Alice', age: 30 },
|
|
47
|
+
{ title: 'Engineer', level: 'Senior' }
|
|
48
|
+
]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should parse text as JSON when no JSON result exists', () => {
|
|
52
|
+
const output = InteractionOutput.from([
|
|
53
|
+
{ type: 'text', value: '{"foo":"bar"}' }
|
|
54
|
+
]);
|
|
55
|
+
expect(output.object()).toEqual({ foo: 'bar' });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should throw error when no JSON result and text is not valid JSON', () => {
|
|
59
|
+
const output = InteractionOutput.from([
|
|
60
|
+
{ type: 'text', value: 'not json' }
|
|
61
|
+
]);
|
|
62
|
+
expect(() => output.object()).toThrow();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should throw error when no JSON result and no text', () => {
|
|
66
|
+
const output = InteractionOutput.from([
|
|
67
|
+
{ type: 'image', value: 'data:image/png;base64,abc' }
|
|
68
|
+
]);
|
|
69
|
+
expect(() => output.object()).toThrow('No JSON result found and no text available to parse');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('image accessors', () => {
|
|
74
|
+
it('should return first image', () => {
|
|
75
|
+
const output = InteractionOutput.from(sampleResults);
|
|
76
|
+
expect(output.image()).toBe('data:image/png;base64,iVBORw0K...');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should return all images', () => {
|
|
80
|
+
const output = InteractionOutput.from(sampleResults);
|
|
81
|
+
expect(output.images()).toEqual([
|
|
82
|
+
'data:image/png;base64,iVBORw0K...',
|
|
83
|
+
'https://example.com/image.jpg'
|
|
84
|
+
]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should throw error when no image result exists', () => {
|
|
88
|
+
const output = InteractionOutput.from([
|
|
89
|
+
{ type: 'text', value: 'hello' }
|
|
90
|
+
]);
|
|
91
|
+
expect(() => output.image()).toThrow('No image result found');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('utility methods', () => {
|
|
97
|
+
it('should convert to string (concatenated text)', () => {
|
|
98
|
+
const output = InteractionOutput.from(sampleResults);
|
|
99
|
+
expect(output.toString()).toBe('Hello, \nWorld!');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should convert to JSON the resuilts array', () => {
|
|
103
|
+
const output = InteractionOutput.from(sampleResults);
|
|
104
|
+
expect(output.toJSON()).toEqual(sampleResults);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should stringify all parts with default separator and compact JSON', () => {
|
|
108
|
+
const output = InteractionOutput.from(sampleResults);
|
|
109
|
+
const result = output.stringify('\n', 0); // Explicitly request compact JSON
|
|
110
|
+
|
|
111
|
+
expect(result).toBe(
|
|
112
|
+
'Hello, \n' +
|
|
113
|
+
'World!\n' +
|
|
114
|
+
'{"name":"Alice","age":30}\n' +
|
|
115
|
+
'{"title":"Engineer","level":"Senior"}\n' +
|
|
116
|
+
'data:image/png;base64,iVBORw0K...\n' +
|
|
117
|
+
'https://example.com/image.jpg'
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should stringify all parts with formatted JSON', () => {
|
|
122
|
+
const mixed: CompletionResult[] = [
|
|
123
|
+
{ type: 'text', value: 'Result:' },
|
|
124
|
+
{ type: 'json', value: { score: 95, status: 'pass' } }
|
|
125
|
+
];
|
|
126
|
+
const output = InteractionOutput.from(mixed);
|
|
127
|
+
const result = output.stringify('\n', 2);
|
|
128
|
+
|
|
129
|
+
expect(result).toBe(
|
|
130
|
+
'Result:\n' +
|
|
131
|
+
'{\n' +
|
|
132
|
+
' "score": 95,\n' +
|
|
133
|
+
' "status": "pass"\n' +
|
|
134
|
+
'}'
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should stringify with custom separator', () => {
|
|
139
|
+
const mixed: CompletionResult[] = [
|
|
140
|
+
{ type: 'text', value: 'A' },
|
|
141
|
+
{ type: 'json', value: { x: 1 } },
|
|
142
|
+
{ type: 'text', value: 'B' }
|
|
143
|
+
];
|
|
144
|
+
const output = InteractionOutput.from(mixed);
|
|
145
|
+
const result = output.stringify(' | ', 0); // Explicitly request compact JSON
|
|
146
|
+
|
|
147
|
+
expect(result).toBe('A | {"x":1} | B');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should stringify with empty separator', () => {
|
|
151
|
+
const mixed: CompletionResult[] = [
|
|
152
|
+
{ type: 'text', value: 'Hello' },
|
|
153
|
+
{ type: 'text', value: 'World' }
|
|
154
|
+
];
|
|
155
|
+
const output = InteractionOutput.from(mixed);
|
|
156
|
+
const result = output.stringify('');
|
|
157
|
+
|
|
158
|
+
expect(result).toBe('HelloWorld');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('Proxy functionality', () => {
|
|
164
|
+
it('should work as an array', () => {
|
|
165
|
+
const output = InteractionOutput.from(sampleResults);
|
|
166
|
+
|
|
167
|
+
// Array access
|
|
168
|
+
expect(output[0]).toEqual({ type: 'text', value: 'Hello, ' });
|
|
169
|
+
expect(output[1]).toEqual({ type: 'text', value: 'World!' });
|
|
170
|
+
|
|
171
|
+
// Array properties
|
|
172
|
+
expect(output.length).toBe(6);
|
|
173
|
+
|
|
174
|
+
// Array methods
|
|
175
|
+
const types = output.map(r => r.type);
|
|
176
|
+
expect(types).toEqual(['text', 'text', 'json', 'json', 'image', 'image']);
|
|
177
|
+
|
|
178
|
+
const textResults = output.filter(r => r.type === 'text');
|
|
179
|
+
expect(textResults).toHaveLength(2);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should have convenience methods on the array', () => {
|
|
183
|
+
const output = InteractionOutput.from<{ name: string; age: number }>(sampleResults);
|
|
184
|
+
|
|
185
|
+
// Should work as array AND have custom methods
|
|
186
|
+
expect(output.length).toBe(6);
|
|
187
|
+
expect(output.text('')).toBe('Hello, World!');
|
|
188
|
+
expect(output.object()).toEqual({ name: 'Alice', age: 30 });
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should be marked with IS_INTERACTION_OUTPUT symbol', () => {
|
|
192
|
+
const output = InteractionOutput.from(sampleResults);
|
|
193
|
+
|
|
194
|
+
// Check that the symbol marker is present
|
|
195
|
+
expect((output as any)[IS_INTERACTION_OUTPUT]).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should return the same instance when calling from() on an already wrapped array', () => {
|
|
199
|
+
const output1 = InteractionOutput.from(sampleResults);
|
|
200
|
+
|
|
201
|
+
// Calling from() again should return the same instance
|
|
202
|
+
const output2 = InteractionOutput.from(output1);
|
|
203
|
+
|
|
204
|
+
// Should be the exact same reference
|
|
205
|
+
expect(output2).toBe(output1);
|
|
206
|
+
|
|
207
|
+
// Verify it still works correctly
|
|
208
|
+
expect(output2.text()).toBe('Hello, \nWorld!');
|
|
209
|
+
expect(output2.length).toBe(6);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should not double-wrap when passing an InteractionOutputArray', () => {
|
|
213
|
+
const output1 = InteractionOutput.from<{ name: string; age: number }>(sampleResults);
|
|
214
|
+
const output2 = InteractionOutput.from(output1);
|
|
215
|
+
const output3 = InteractionOutput.from(output2);
|
|
216
|
+
|
|
217
|
+
// All should be the same reference
|
|
218
|
+
expect(output1).toBe(output2);
|
|
219
|
+
expect(output2).toBe(output3);
|
|
220
|
+
|
|
221
|
+
// Functionality should remain intact
|
|
222
|
+
expect(output3.object()).toEqual({ name: 'Alice', age: 30 });
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('Direct class usage', () => {
|
|
227
|
+
it('should work when using class directly', () => {
|
|
228
|
+
const wrapper = new InteractionOutput<{ name: string; age: number }>(sampleResults);
|
|
229
|
+
|
|
230
|
+
expect(wrapper.text('')).toBe('Hello, World!');
|
|
231
|
+
expect(wrapper.object()).toEqual({ name: 'Alice', age: 30 });
|
|
232
|
+
expect(wrapper.results).toBe(sampleResults);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe('Edge cases', () => {
|
|
237
|
+
it('should handle empty results array', () => {
|
|
238
|
+
const output = InteractionOutput.from([]);
|
|
239
|
+
|
|
240
|
+
expect(output.text()).toBe('');
|
|
241
|
+
expect(output.texts()).toEqual([]);
|
|
242
|
+
expect(output.objects()).toEqual([]);
|
|
243
|
+
expect(output.images()).toEqual([]);
|
|
244
|
+
expect(() => output.object()).toThrow();
|
|
245
|
+
expect(() => output.image()).toThrow();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should handle mixed content types', () => {
|
|
249
|
+
const mixed: CompletionResult[] = [
|
|
250
|
+
{ type: 'text', value: 'Start' },
|
|
251
|
+
{ type: 'json', value: { count: 5 } },
|
|
252
|
+
{ type: 'text', value: 'End' }
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
const output = InteractionOutput.from(mixed);
|
|
256
|
+
expect(output.text()).toBe('Start\nEnd');
|
|
257
|
+
expect(output.object()).toEqual({ count: 5 });
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should handle results with only one type', () => {
|
|
261
|
+
const textOnly: CompletionResult[] = [
|
|
262
|
+
{ type: 'text', value: 'Only text here' }
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
const output = InteractionOutput.from(textOnly);
|
|
266
|
+
expect(output.text()).toBe('Only text here');
|
|
267
|
+
expect(output.objects()).toEqual([]);
|
|
268
|
+
expect(output.images()).toEqual([]);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('Type safety with generics', () => {
|
|
273
|
+
it('should provide type safety with generic parameter', () => {
|
|
274
|
+
interface User { name: string; age: number; }
|
|
275
|
+
|
|
276
|
+
const results: CompletionResult[] = [
|
|
277
|
+
{ type: 'json', value: { name: 'Bob', age: 25 } }
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
const output = InteractionOutput.from<User>(results);
|
|
281
|
+
const user = output.object(); // TypeScript knows this is User
|
|
282
|
+
|
|
283
|
+
expect(user.name).toBe('Bob');
|
|
284
|
+
expect(user.age).toBe(25);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
it('should allow type override at method level', () => {
|
|
289
|
+
interface Person { name: string; age: number; }
|
|
290
|
+
interface Address { street: string; city: string; }
|
|
291
|
+
|
|
292
|
+
const results: CompletionResult[] = [
|
|
293
|
+
{ type: 'json', value: { name: 'Alice', age: 30 } },
|
|
294
|
+
{ type: 'json', value: { street: '123 Main', city: 'NYC' } }
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
const output = InteractionOutput.from<Person>(results);
|
|
298
|
+
const person = output.objectAt<Person>(0);
|
|
299
|
+
const address = output.objectAt<Address>(1);
|
|
300
|
+
|
|
301
|
+
expect(person.name).toBe('Alice');
|
|
302
|
+
expect(address.city).toBe('NYC');
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
});
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { CompletionResult } from '@llumiverse/common';
|
|
2
|
+
import { ExecutionRun, InteractionExecutionResult } from '@vertesia/common';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Symbol used to mark InteractionOutputArray instances.
|
|
6
|
+
* This allows us to detect if a CompletionResult[] has already been wrapped.
|
|
7
|
+
*/
|
|
8
|
+
export const IS_INTERACTION_OUTPUT = Symbol('InteractionOutput');
|
|
9
|
+
|
|
10
|
+
export function enhanceInteractionExecutionResult<ResultT = any, ParamsT = any>(r: InteractionExecutionResult<ParamsT>): EnhancedInteractionExecutionResult<ResultT, ParamsT> {
|
|
11
|
+
(r as any).result = InteractionOutput.from<ResultT>(r.result);
|
|
12
|
+
return r as EnhancedInteractionExecutionResult<ResultT, ParamsT>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function enhanceExecutionRun<ResultT = any, ParamsT = any>(r: ExecutionRun<ParamsT>): EnhancedExecutionRun<ResultT, ParamsT> {
|
|
16
|
+
(r as any).result = InteractionOutput.from<ResultT>(r.result);
|
|
17
|
+
return r as EnhancedExecutionRun<ResultT, ParamsT>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A convenient wrapper around CompletionResult[] that provides ergonomic accessors
|
|
22
|
+
* for different result types (text, JSON/objects, images) from interaction executions.
|
|
23
|
+
*
|
|
24
|
+
* Use the static from() method to create a proxied array that acts as both
|
|
25
|
+
* an array and has these convenience methods.
|
|
26
|
+
*
|
|
27
|
+
* @template T - The expected type of JSON/object results. Defaults to any.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* // Recommended: Using the static from() method
|
|
32
|
+
* const output = InteractionOutput.from<MyResponse>(run.result);
|
|
33
|
+
* output[0]; // CompletionResult (array access)
|
|
34
|
+
* output.length; // number (array property)
|
|
35
|
+
* const obj = output.object(); // MyResponse (custom method)
|
|
36
|
+
* const text = output.text(); // string (custom getter)
|
|
37
|
+
*
|
|
38
|
+
* // Alternative: Using the class directly (less common)
|
|
39
|
+
* const output = new InteractionOutput<MyResponse>(run.result);
|
|
40
|
+
* const obj = output.object(); // Returns MyResponse
|
|
41
|
+
* const objs = output.objects(); // Returns MyResponse[]
|
|
42
|
+
*
|
|
43
|
+
* // Override type for specific objects
|
|
44
|
+
* interface OtherType { title: string; }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export class InteractionOutput<T = any> {
|
|
48
|
+
/**
|
|
49
|
+
* The raw completion results array.
|
|
50
|
+
* Access this when you need to work with the underlying CompletionResult[] directly.
|
|
51
|
+
*/
|
|
52
|
+
constructor(public readonly results: CompletionResult[]) { }
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create an interaction output that acts as both an array and has convenience methods.
|
|
56
|
+
* This is the recommended way to work with interaction execution results.
|
|
57
|
+
*
|
|
58
|
+
* @template T - The expected type of JSON/object results. Defaults to any.
|
|
59
|
+
* @param results - The raw CompletionResult array from an interaction execution
|
|
60
|
+
* @returns A proxied array with convenience methods
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* interface MyResponse { name: string; age: number; }
|
|
65
|
+
* const output = InteractionOutput.from<MyResponse>(run.result);
|
|
66
|
+
*
|
|
67
|
+
* // Array access
|
|
68
|
+
* output[0]; // CompletionResult
|
|
69
|
+
* output.length; // number
|
|
70
|
+
*
|
|
71
|
+
* // Convenience methods
|
|
72
|
+
* const obj = output.object(); // MyResponse
|
|
73
|
+
* const text = output.text(); // string
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
static from<T = any>(results: CompletionResult[] | InteractionOutputArray<T> | Record<string, any> | string | null | undefined): InteractionOutputArray<T> {
|
|
77
|
+
if (!results) {
|
|
78
|
+
return createInteractionOutput<T>([]);
|
|
79
|
+
}
|
|
80
|
+
// Check if already wrapped using the symbol marker
|
|
81
|
+
if ((results as any)[IS_INTERACTION_OUTPUT]) {
|
|
82
|
+
return results as InteractionOutputArray<T>;
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(results)) {
|
|
85
|
+
return createInteractionOutput<T>(results as CompletionResult[]);
|
|
86
|
+
} else if (typeof results === 'object') {
|
|
87
|
+
return createInteractionOutput<T>([{ type: 'json', value: results }]);
|
|
88
|
+
} else {
|
|
89
|
+
return createInteractionOutput<T>([{ type: 'text', value: String(results) }]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static isInteractionOutputArray(obj: any): boolean {
|
|
94
|
+
return obj && obj[IS_INTERACTION_OUTPUT] === true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get isEmpty() {
|
|
98
|
+
return this.results.length === 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
hasObject() {
|
|
102
|
+
return this.results.some(r => r.type === 'json');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
hasText() {
|
|
106
|
+
return this.results.some(r => r.type === 'text');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
hasImage() {
|
|
110
|
+
return this.results.some(r => r.type === 'image');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get the concatenated text from all text results.
|
|
115
|
+
* Returns an empty string if no text results exist.
|
|
116
|
+
*/
|
|
117
|
+
text(delimiter = '\n'): string {
|
|
118
|
+
return this.results
|
|
119
|
+
.filter(r => r.type === 'text')
|
|
120
|
+
.map(r => r.value)
|
|
121
|
+
.join(delimiter);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get an array of all text values from text results.
|
|
126
|
+
*/
|
|
127
|
+
texts(): string[] {
|
|
128
|
+
return this.results
|
|
129
|
+
.filter(r => r.type === 'text')
|
|
130
|
+
.map(r => r.value);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get the first JSON result as a parsed object.
|
|
135
|
+
* If no JSON result exists, attempts to parse the concatenated text as JSON.
|
|
136
|
+
* @returns The first JSON result typed as T (the class generic type)
|
|
137
|
+
* @throws Error if no JSON result found and text cannot be parsed as JSON
|
|
138
|
+
*/
|
|
139
|
+
object(): T {
|
|
140
|
+
const jsonResult = this.results.find(r => r.type === 'json');
|
|
141
|
+
if (jsonResult) {
|
|
142
|
+
return jsonResult.value as T;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Fallback: try to parse the other text parts as JSON
|
|
146
|
+
return parseCompletionResultAsJson(this.results);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get all JSON results as parsed objects.
|
|
151
|
+
* @returns An array of all JSON results typed as T[] (the class generic type)
|
|
152
|
+
*/
|
|
153
|
+
objects(): T[] {
|
|
154
|
+
return this.results
|
|
155
|
+
.filter(r => r.type === 'json')
|
|
156
|
+
.map(r => r.value as T);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get a specific JSON result by index as a parsed object.
|
|
161
|
+
* @template U - The type of the object at this index. Defaults to T (the class generic).
|
|
162
|
+
* @param index - The zero-based index of the JSON result
|
|
163
|
+
* @returns The JSON result at the specified index typed as U
|
|
164
|
+
* @throws Error if the index is out of bounds
|
|
165
|
+
*/
|
|
166
|
+
objectAt<U = T>(index: number): U {
|
|
167
|
+
let i = 0;
|
|
168
|
+
for (const result of this.results) {
|
|
169
|
+
if (result.type === 'json') {
|
|
170
|
+
if (i === index) {
|
|
171
|
+
return result.value as U;
|
|
172
|
+
}
|
|
173
|
+
i++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
throw new Error(`Object at index ${index} not found`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get the first image result (base64 data URL or URL).
|
|
181
|
+
* @throws Error if no image result exists
|
|
182
|
+
*/
|
|
183
|
+
image(): string {
|
|
184
|
+
const imageResult = this.results.find(r => r.type === 'image');
|
|
185
|
+
if (!imageResult) {
|
|
186
|
+
throw new Error('No image result found');
|
|
187
|
+
}
|
|
188
|
+
return imageResult.value;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get an array of all image values (base64 data URLs or URLs).
|
|
193
|
+
*/
|
|
194
|
+
images(): string[] {
|
|
195
|
+
return this.results
|
|
196
|
+
.filter(r => r.type === 'image')
|
|
197
|
+
.map(r => r.value);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Convert all results to a string representation.
|
|
202
|
+
* Text and image results are used as-is, JSON results are stringified with the specified indent.
|
|
203
|
+
* All parts are joined using the specified separator.
|
|
204
|
+
*
|
|
205
|
+
* @param separator - The separator to use between parts (default: '\n')
|
|
206
|
+
* @param indent - The indentation to use for JSON.stringify (default: 0 = no formatting)
|
|
207
|
+
* @returns A string representation of all results
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```typescript
|
|
211
|
+
* const output = InteractionOutput.from(results);
|
|
212
|
+
* output.stringify(); // Each part on a new line, compact JSON
|
|
213
|
+
* output.stringify('\n\n', 2); // Double newlines between parts, formatted JSON
|
|
214
|
+
* output.stringify(' '); // Space-separated, compact JSON
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
stringify(separator = '\n', indent = 2): string {
|
|
218
|
+
return this.results
|
|
219
|
+
.map(r => {
|
|
220
|
+
switch (r.type) {
|
|
221
|
+
case 'json':
|
|
222
|
+
return JSON.stringify(r.value, null, indent);
|
|
223
|
+
default:
|
|
224
|
+
return String(r.value);
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
.join(separator);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Convert to string representation (concatenated text).
|
|
232
|
+
* Useful for template literals or string coercion.
|
|
233
|
+
*/
|
|
234
|
+
toString(): string {
|
|
235
|
+
return this.text();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Convert to JSON representation.
|
|
240
|
+
* Attempts to return the first JSON object, falls back to concatenated text.
|
|
241
|
+
*/
|
|
242
|
+
toJSON(): CompletionResult[] {
|
|
243
|
+
return this.results
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Type representing a CompletionResult array enhanced with InteractionOutput methods.
|
|
249
|
+
* This is the return type of InteractionOutput.from() - it acts as both an array and has convenience methods.
|
|
250
|
+
*/
|
|
251
|
+
export type InteractionOutputArray<T = any> = CompletionResult[] & InteractionOutput<T>;
|
|
252
|
+
|
|
253
|
+
export interface EnhancedInteractionExecutionResult<ResultT = any, ParamsT = any> extends InteractionExecutionResult<ParamsT> {
|
|
254
|
+
result: InteractionOutputArray<ResultT>;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface EnhancedExecutionRun<ResultT = any, ParamsT = any> extends ExecutionRun<ParamsT> {
|
|
258
|
+
result: InteractionOutputArray<ResultT>;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Creates a proxied array that acts as both a CompletionResult[] and has InteractionOutput convenience methods.
|
|
263
|
+
* Note: It's recommended to use InteractionOutput.from() instead of calling this function directly.
|
|
264
|
+
*
|
|
265
|
+
* @template T - The expected type of JSON/object results. Defaults to any.
|
|
266
|
+
* @param results - The raw CompletionResult array from the interaction execution
|
|
267
|
+
* @returns A proxy that behaves as both an array and has convenience methods
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```typescript
|
|
271
|
+
* interface MyResponse { name: string; age: number; }
|
|
272
|
+
* const output = createInteractionOutput<MyResponse>(run.result);
|
|
273
|
+
*
|
|
274
|
+
* // Array access
|
|
275
|
+
* output[0]; // CompletionResult
|
|
276
|
+
* output.length; // number
|
|
277
|
+
* output.map(r => r.type) // string[]
|
|
278
|
+
*
|
|
279
|
+
* // Convenience methods
|
|
280
|
+
* const obj = output.object(); // MyResponse
|
|
281
|
+
* const text = output.text(); // string
|
|
282
|
+
* const img = output.image(); // string
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
export function createInteractionOutput<T = any>(results: CompletionResult[]): InteractionOutputArray<T> {
|
|
286
|
+
const wrapper = new InteractionOutput<T>(results);
|
|
287
|
+
|
|
288
|
+
return new Proxy(results, {
|
|
289
|
+
get(target, prop, receiver) {
|
|
290
|
+
// Return the marker symbol to identify wrapped arrays
|
|
291
|
+
if (prop === IS_INTERACTION_OUTPUT) {
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Check if the wrapper has this property/method
|
|
296
|
+
if (prop in wrapper) {
|
|
297
|
+
const value = (wrapper as any)[prop];
|
|
298
|
+
// If it's a function, bind it to the wrapper so 'this' works correctly
|
|
299
|
+
if (typeof value === 'function') {
|
|
300
|
+
return value.bind(wrapper);
|
|
301
|
+
}
|
|
302
|
+
// For getters and regular properties, just return the value
|
|
303
|
+
return value;
|
|
304
|
+
}
|
|
305
|
+
// Otherwise delegate to the array
|
|
306
|
+
return Reflect.get(target, prop, receiver);
|
|
307
|
+
}
|
|
308
|
+
}) as InteractionOutputArray<T>;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
function parseCompletionResultAsJson(data: CompletionResult[]) {
|
|
313
|
+
let lastError: Error | undefined;
|
|
314
|
+
for (const part of data) {
|
|
315
|
+
if (part.type === "text") {
|
|
316
|
+
const text = part.value.trim();
|
|
317
|
+
try {
|
|
318
|
+
return JSON.parse(text);
|
|
319
|
+
} catch (error: any) {
|
|
320
|
+
lastError = error;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (!lastError) {
|
|
325
|
+
lastError = new Error("No JSON result found and no text available to parse");
|
|
326
|
+
}
|
|
327
|
+
throw lastError;
|
|
328
|
+
}
|