@nordsym/apiclaw 1.3.13 → 1.4.0

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 (44) hide show
  1. package/PRD-API-CHAINING.md +483 -0
  2. package/PRD-HARDEN-SHELL.md +18 -12
  3. package/convex/_generated/api.d.ts +2 -0
  4. package/convex/chains.ts +1095 -0
  5. package/convex/schema.ts +101 -0
  6. package/dist/chain-types.d.ts +187 -0
  7. package/dist/chain-types.d.ts.map +1 -0
  8. package/dist/chain-types.js +33 -0
  9. package/dist/chain-types.js.map +1 -0
  10. package/dist/chainExecutor.d.ts +122 -0
  11. package/dist/chainExecutor.d.ts.map +1 -0
  12. package/dist/chainExecutor.js +454 -0
  13. package/dist/chainExecutor.js.map +1 -0
  14. package/dist/chainResolver.d.ts +100 -0
  15. package/dist/chainResolver.d.ts.map +1 -0
  16. package/dist/chainResolver.js +519 -0
  17. package/dist/chainResolver.js.map +1 -0
  18. package/dist/chainResolver.test.d.ts +5 -0
  19. package/dist/chainResolver.test.d.ts.map +1 -0
  20. package/dist/chainResolver.test.js +201 -0
  21. package/dist/chainResolver.test.js.map +1 -0
  22. package/dist/execute.d.ts +4 -1
  23. package/dist/execute.d.ts.map +1 -1
  24. package/dist/execute.js +3 -0
  25. package/dist/execute.js.map +1 -1
  26. package/dist/index.js +382 -2
  27. package/dist/index.js.map +1 -1
  28. package/landing/public/logos/chattgpt.svg +1 -0
  29. package/landing/public/logos/claude.svg +1 -0
  30. package/landing/public/logos/gemini.svg +1 -0
  31. package/landing/public/logos/grok.svg +1 -0
  32. package/landing/src/app/page.tsx +12 -21
  33. package/landing/src/app/workspace/chains/page.tsx +520 -0
  34. package/landing/src/components/AITestimonials.tsx +15 -9
  35. package/landing/src/components/ChainStepDetail.tsx +310 -0
  36. package/landing/src/components/ChainTrace.tsx +261 -0
  37. package/landing/src/lib/stats.json +1 -1
  38. package/package.json +1 -1
  39. package/src/chain-types.ts +270 -0
  40. package/src/chainExecutor.ts +730 -0
  41. package/src/chainResolver.test.ts +246 -0
  42. package/src/chainResolver.ts +658 -0
  43. package/src/execute.ts +23 -0
  44. package/src/index.ts +423 -2
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Quick tests for chainResolver
3
+ */
4
+
5
+ import {
6
+ extractReferences,
7
+ parseReference,
8
+ resolveReferences,
9
+ validateReferences,
10
+ evaluateCondition,
11
+ ChainContext,
12
+ ChainStep,
13
+ } from './chainResolver.js';
14
+
15
+ // Test data
16
+ const mockContext: ChainContext = {
17
+ results: {
18
+ step1: { url: 'https://example.com/image.png', id: '123' },
19
+ step2: { data: { items: [{ name: 'first' }, { name: 'second' }] } },
20
+ },
21
+ inputs: { email: 'test@example.com', count: 5 },
22
+ currentIndex: 2,
23
+ startedAt: '2024-01-01T00:00:00Z',
24
+ prevStepId: 'step2',
25
+ parallelResults: [
26
+ { url: 'https://a.com' },
27
+ { url: 'https://b.com' },
28
+ ],
29
+ forEachItem: { id: 42, name: 'test-item' },
30
+ forEachIndex: 3,
31
+ };
32
+
33
+ // ============================================================================
34
+ // TESTS
35
+ // ============================================================================
36
+
37
+ function test(name: string, fn: () => void) {
38
+ try {
39
+ fn();
40
+ console.log(`✅ ${name}`);
41
+ } catch (e: any) {
42
+ console.log(`❌ ${name}: ${e.message}`);
43
+ }
44
+ }
45
+
46
+ function assertEqual(actual: any, expected: any, msg?: string) {
47
+ const a = JSON.stringify(actual);
48
+ const e = JSON.stringify(expected);
49
+ if (a !== e) {
50
+ throw new Error(`${msg || 'Assertion failed'}: expected ${e}, got ${a}`);
51
+ }
52
+ }
53
+
54
+ console.log('\n=== Reference Extraction ===\n');
55
+
56
+ test('extracts simple step reference', () => {
57
+ const refs = extractReferences('$step1.url');
58
+ assertEqual(refs.length, 1);
59
+ assertEqual(refs[0].type, 'step');
60
+ assertEqual(refs[0].stepId, 'step1');
61
+ assertEqual(refs[0].path, ['url']);
62
+ });
63
+
64
+ test('extracts $prev reference', () => {
65
+ const refs = extractReferences('$prev.data');
66
+ assertEqual(refs.length, 1);
67
+ assertEqual(refs[0].type, 'prev');
68
+ assertEqual(refs[0].path, ['data']);
69
+ });
70
+
71
+ test('extracts nested path', () => {
72
+ const refs = extractReferences('$step2.data.items[0].name');
73
+ assertEqual(refs.length, 1);
74
+ assertEqual(refs[0].path, ['data', 'items', '0', 'name']);
75
+ });
76
+
77
+ test('extracts multiple references', () => {
78
+ const refs = extractReferences('Send $step1.url to $inputs.email');
79
+ assertEqual(refs.length, 2);
80
+ assertEqual(refs[0].raw, '$step1.url');
81
+ assertEqual(refs[1].raw, '$inputs.email');
82
+ });
83
+
84
+ test('extracts $parallel with index', () => {
85
+ const refs = extractReferences('$parallel[0].url');
86
+ assertEqual(refs.length, 1);
87
+ assertEqual(refs[0].type, 'parallel');
88
+ assertEqual(refs[0].arrayIndex, 0);
89
+ assertEqual(refs[0].path, ['url']);
90
+ });
91
+
92
+ test('extracts built-in $chain variables', () => {
93
+ const refs = extractReferences('$chain.startedAt');
94
+ assertEqual(refs.length, 1);
95
+ assertEqual(refs[0].type, 'chain');
96
+ assertEqual(refs[0].path, ['startedAt']);
97
+ });
98
+
99
+ console.log('\n=== Reference Resolution ===\n');
100
+
101
+ test('resolves step reference', () => {
102
+ const result = resolveReferences({ url: '$step1.url' }, mockContext);
103
+ assertEqual(result.url, 'https://example.com/image.png');
104
+ });
105
+
106
+ test('resolves $prev reference', () => {
107
+ const result = resolveReferences({ data: '$prev.data' }, mockContext);
108
+ assertEqual(result.data, mockContext.results.step2.data);
109
+ });
110
+
111
+ test('resolves nested path with array', () => {
112
+ const result = resolveReferences({ name: '$step2.data.items[1].name' }, mockContext);
113
+ assertEqual(result.name, 'second');
114
+ });
115
+
116
+ test('resolves $inputs reference', () => {
117
+ const result = resolveReferences({ to: '$inputs.email' }, mockContext);
118
+ assertEqual(result.to, 'test@example.com');
119
+ });
120
+
121
+ test('resolves $parallel reference', () => {
122
+ const result = resolveReferences({ url: '$parallel[1].url' }, mockContext);
123
+ assertEqual(result.url, 'https://b.com');
124
+ });
125
+
126
+ test('resolves $chain.startedAt', () => {
127
+ const result = resolveReferences({ time: '$chain.startedAt' }, mockContext);
128
+ assertEqual(result.time, '2024-01-01T00:00:00Z');
129
+ });
130
+
131
+ test('resolves $item in forEach', () => {
132
+ const result = resolveReferences({ id: '$item.id' }, mockContext);
133
+ assertEqual(result.id, 42);
134
+ });
135
+
136
+ test('resolves $index in forEach', () => {
137
+ const result = resolveReferences({ idx: '$index' }, mockContext);
138
+ assertEqual(result.idx, 3);
139
+ });
140
+
141
+ test('preserves non-reference strings', () => {
142
+ const result = resolveReferences({ msg: 'Hello world' }, mockContext);
143
+ assertEqual(result.msg, 'Hello world');
144
+ });
145
+
146
+ test('interpolates multiple references in string', () => {
147
+ const result = resolveReferences({ msg: 'File at $step1.url for $inputs.email' }, mockContext);
148
+ assertEqual(result.msg, 'File at https://example.com/image.png for test@example.com');
149
+ });
150
+
151
+ test('resolves nested objects', () => {
152
+ const result = resolveReferences({
153
+ outer: {
154
+ inner: '$step1.id',
155
+ deep: { value: '$inputs.count' }
156
+ }
157
+ }, mockContext);
158
+ assertEqual(result.outer.inner, '123');
159
+ assertEqual(result.outer.deep.value, 5);
160
+ });
161
+
162
+ test('resolves arrays', () => {
163
+ const result = resolveReferences({
164
+ urls: ['$parallel[0].url', '$parallel[1].url']
165
+ }, mockContext);
166
+ assertEqual(result.urls, ['https://a.com', 'https://b.com']);
167
+ });
168
+
169
+ console.log('\n=== Validation ===\n');
170
+
171
+ test('validates valid chain', () => {
172
+ const steps: ChainStep[] = [
173
+ { id: 'step1', provider: 'brave', action: 'search', params: { query: 'test' } },
174
+ { id: 'step2', provider: 'firecrawl', action: 'scrape', params: { url: '$step1.url' } },
175
+ ];
176
+ const result = validateReferences(steps);
177
+ assertEqual(result.valid, true);
178
+ assertEqual(result.errors.length, 0);
179
+ });
180
+
181
+ test('detects reference to undefined step', () => {
182
+ const steps: ChainStep[] = [
183
+ { id: 'step1', provider: 'brave', action: 'search', params: { url: '$nonexistent.url' } },
184
+ ];
185
+ const result = validateReferences(steps);
186
+ assertEqual(result.valid, false);
187
+ assertEqual(result.errors.length, 1);
188
+ });
189
+
190
+ test('detects forward reference', () => {
191
+ const steps: ChainStep[] = [
192
+ { id: 'step1', provider: 'brave', action: 'search', params: { url: '$step2.url' } },
193
+ { id: 'step2', provider: 'firecrawl', action: 'scrape', params: {} },
194
+ ];
195
+ const result = validateReferences(steps);
196
+ assertEqual(result.valid, false);
197
+ });
198
+
199
+ test('detects $prev in first step', () => {
200
+ const steps: ChainStep[] = [
201
+ { id: 'step1', provider: 'brave', action: 'search', params: { url: '$prev.url' } },
202
+ ];
203
+ const result = validateReferences(steps);
204
+ assertEqual(result.valid, false);
205
+ });
206
+
207
+ console.log('\n=== Condition Evaluation ===\n');
208
+
209
+ test('evaluates equality', () => {
210
+ assertEqual(evaluateCondition('"test" === "test"', mockContext), true);
211
+ assertEqual(evaluateCondition('"test" === "other"', mockContext), false);
212
+ });
213
+
214
+ test('evaluates inequality', () => {
215
+ assertEqual(evaluateCondition('5 !== 3', mockContext), true);
216
+ });
217
+
218
+ test('evaluates comparison', () => {
219
+ assertEqual(evaluateCondition('10 > 5', mockContext), true);
220
+ assertEqual(evaluateCondition('3 < 2', mockContext), false);
221
+ assertEqual(evaluateCondition('5 >= 5', mockContext), true);
222
+ });
223
+
224
+ test('evaluates logical AND', () => {
225
+ assertEqual(evaluateCondition('true && true', mockContext), true);
226
+ assertEqual(evaluateCondition('true && false', mockContext), false);
227
+ });
228
+
229
+ test('evaluates logical OR', () => {
230
+ assertEqual(evaluateCondition('false || true', mockContext), true);
231
+ assertEqual(evaluateCondition('false || false', mockContext), false);
232
+ });
233
+
234
+ test('evaluates negation', () => {
235
+ assertEqual(evaluateCondition('!false', mockContext), true);
236
+ assertEqual(evaluateCondition('!true', mockContext), false);
237
+ });
238
+
239
+ test('evaluates with resolved references', () => {
240
+ // $inputs.count === 5
241
+ const ctx = { ...mockContext, results: { check: { status: 'success' } } };
242
+ ctx.prevStepId = 'check';
243
+ assertEqual(evaluateCondition('$prev.status === "success"', ctx), true);
244
+ });
245
+
246
+ console.log('\n=== All Tests Complete ===\n');