ai-evaluate 2.1.8 → 2.2.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 (61) hide show
  1. package/dist/evaluate.d.ts.map +1 -1
  2. package/dist/evaluate.js.map +1 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/miniflare-pool.d.ts.map +1 -1
  6. package/dist/miniflare-pool.js.map +1 -1
  7. package/dist/node.d.ts.map +1 -1
  8. package/dist/node.js.map +1 -1
  9. package/dist/static/index.d.ts +111 -0
  10. package/dist/static/index.d.ts.map +1 -0
  11. package/dist/static/index.js +347 -0
  12. package/dist/static/index.js.map +1 -0
  13. package/dist/type-guards.d.ts.map +1 -1
  14. package/dist/type-guards.js.map +1 -1
  15. package/dist/worker-template/core.d.ts.map +1 -1
  16. package/dist/worker-template/core.js +1 -1
  17. package/dist/worker-template/core.js.map +1 -1
  18. package/package.json +17 -4
  19. package/public/capnweb.mjs +220 -0
  20. package/public/index.mjs +426 -0
  21. package/public/scaffold.mjs +198 -0
  22. package/.turbo/turbo-build.log +0 -4
  23. package/.turbo/turbo-test.log +0 -54
  24. package/.turbo/turbo-typecheck.log +0 -4
  25. package/CHANGELOG.md +0 -48
  26. package/example/package.json +0 -20
  27. package/example/src/index.ts +0 -221
  28. package/example/wrangler.jsonc +0 -25
  29. package/src/capnweb-bundle.ts +0 -2596
  30. package/src/evaluate.ts +0 -329
  31. package/src/index.ts +0 -23
  32. package/src/miniflare-pool.ts +0 -395
  33. package/src/node.ts +0 -245
  34. package/src/repl.ts +0 -228
  35. package/src/shared.ts +0 -186
  36. package/src/type-guards.ts +0 -323
  37. package/src/types.ts +0 -196
  38. package/src/validation.ts +0 -120
  39. package/src/worker-template/code-transforms.ts +0 -32
  40. package/src/worker-template/core.ts +0 -557
  41. package/src/worker-template/helpers.ts +0 -90
  42. package/src/worker-template/index.ts +0 -23
  43. package/src/worker-template/sdk-generator.ts +0 -2515
  44. package/src/worker-template/test-generator.ts +0 -358
  45. package/test/evaluate-extended.test.js +0 -429
  46. package/test/evaluate-extended.test.ts +0 -469
  47. package/test/evaluate.test.js +0 -235
  48. package/test/evaluate.test.ts +0 -253
  49. package/test/index.test.js +0 -77
  50. package/test/index.test.ts +0 -95
  51. package/test/miniflare-pool.test.ts +0 -246
  52. package/test/node.test.ts +0 -467
  53. package/test/security.test.ts +0 -1009
  54. package/test/shared.test.ts +0 -105
  55. package/test/type-guards.test.ts +0 -303
  56. package/test/validation.test.ts +0 -240
  57. package/test/worker-template.test.js +0 -365
  58. package/test/worker-template.test.ts +0 -432
  59. package/tsconfig.json +0 -22
  60. package/vitest.config.js +0 -21
  61. package/vitest.config.ts +0 -28
@@ -0,0 +1,198 @@
1
+ /**
2
+ * ai-evaluate v2.1.8
3
+ * Static worker template for evaluate.workers.do
4
+ * Generated: 2026-01-26T01:42:47.000Z
5
+ *
6
+ * @license MIT
7
+ */
8
+
9
+ // Base worker scaffold without capnweb dependency
10
+ // Use this for dev mode or when capnweb is not needed
11
+
12
+ const logs = [];
13
+
14
+ // Capture console output
15
+ const originalConsole = { ...console };
16
+ const captureConsole = (level) => (...args) => {
17
+ logs.push({
18
+ level,
19
+ message: args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '),
20
+ timestamp: Date.now()
21
+ });
22
+ originalConsole[level](...args);
23
+ };
24
+ console.log = captureConsole('log');
25
+ console.warn = captureConsole('warn');
26
+ console.error = captureConsole('error');
27
+ console.info = captureConsole('info');
28
+ console.debug = captureConsole('debug');
29
+
30
+
31
+ // Test framework (vitest-compatible API)
32
+ let currentDescribe = '';
33
+ let beforeEachFns = [];
34
+ let afterEachFns = [];
35
+
36
+ const describe = (name, fn) => {
37
+ const prev = currentDescribe;
38
+ const prevBeforeEach = [...beforeEachFns];
39
+ const prevAfterEach = [...afterEachFns];
40
+ currentDescribe = currentDescribe ? currentDescribe + ' > ' + name : name;
41
+ try { fn(); } finally {
42
+ currentDescribe = prev;
43
+ beforeEachFns = prevBeforeEach;
44
+ afterEachFns = prevAfterEach;
45
+ }
46
+ };
47
+
48
+ // Hooks
49
+ const beforeEach = (fn) => { beforeEachFns.push(fn); };
50
+ const afterEach = (fn) => { afterEachFns.push(fn); };
51
+
52
+ const it = (name, fn) => {
53
+ const fullName = currentDescribe ? currentDescribe + ' > ' + name : name;
54
+ const hooks = { before: [...beforeEachFns], after: [...afterEachFns] };
55
+ pendingTests.push({ name: fullName, fn, hooks });
56
+ };
57
+ const test = it;
58
+
59
+ it.skip = (name, fn) => {
60
+ const fullName = currentDescribe ? currentDescribe + ' > ' + name : name;
61
+ pendingTests.push({ name: fullName, fn: null, skip: true });
62
+ };
63
+ test.skip = it.skip;
64
+
65
+ it.only = (name, fn) => {
66
+ const fullName = currentDescribe ? currentDescribe + ' > ' + name : name;
67
+ const hooks = { before: [...beforeEachFns], after: [...afterEachFns] };
68
+ pendingTests.push({ name: fullName, fn, hooks, only: true });
69
+ };
70
+ test.only = it.only;
71
+
72
+ // Deep equality check
73
+ const deepEqual = (a, b) => {
74
+ if (a === b) return true;
75
+ if (a == null || b == null) return false;
76
+ if (typeof a !== typeof b) return false;
77
+ if (typeof a !== 'object') return false;
78
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
79
+ if (Array.isArray(a)) {
80
+ if (a.length !== b.length) return false;
81
+ return a.every((v, i) => deepEqual(v, b[i]));
82
+ }
83
+ const keysA = Object.keys(a);
84
+ const keysB = Object.keys(b);
85
+ if (keysA.length !== keysB.length) return false;
86
+ return keysA.every(k => deepEqual(a[k], b[k]));
87
+ };
88
+
89
+ // Expect implementation with vitest-compatible matchers
90
+ const expect = (actual) => {
91
+ const matchers = {
92
+ toBe: (expected) => {
93
+ if (actual !== expected) {
94
+ throw new Error(\
95
+
96
+ // Module exports object
97
+ const exports = {};
98
+ const pendingTests = [];
99
+ const testResults = { total: 0, passed: 0, failed: 0, skipped: 0, tests: [], duration: 0 };
100
+
101
+ // ============================================================
102
+ // USER CODE PLACEHOLDER
103
+ // ============================================================
104
+ // USER_MODULE_CODE
105
+ // USER_TEST_CODE
106
+ // USER_SCRIPT_CODE
107
+
108
+ // ============================================================
109
+ // WORKER ENTRY POINT
110
+ // ============================================================
111
+ export default {
112
+ async fetch(request, env) {
113
+ const url = new URL(request.url);
114
+
115
+ // Route: GET / - Return info
116
+ if (request.method === 'GET' && url.pathname === '/') {
117
+ return Response.json({
118
+ exports: Object.keys(exports),
119
+ execute: '/execute'
120
+ });
121
+ }
122
+
123
+ // Route: /execute - Run tests and scripts
124
+ let scriptResult = undefined;
125
+ let scriptError = null;
126
+
127
+
128
+ // Run all pending tests
129
+ const testStart = Date.now();
130
+ const hasOnly = pendingTests.some(t => t.only);
131
+ const testsToRun = hasOnly ? pendingTests.filter(t => t.only || t.skip) : pendingTests;
132
+
133
+ for (const { name, fn, hooks, skip } of testsToRun) {
134
+ testResults.total++;
135
+
136
+ if (skip) {
137
+ testResults.skipped++;
138
+ testResults.tests.push({ name, passed: true, skipped: true, duration: 0 });
139
+ continue;
140
+ }
141
+
142
+ const start = Date.now();
143
+ try {
144
+ // Run beforeEach hooks
145
+ if (hooks?.before) {
146
+ for (const hook of hooks.before) {
147
+ const hookResult = hook();
148
+ if (hookResult && typeof hookResult.then === 'function') {
149
+ await hookResult;
150
+ }
151
+ }
152
+ }
153
+
154
+ // Run the test
155
+ const result = fn();
156
+ if (result && typeof result.then === 'function') {
157
+ await result;
158
+ }
159
+
160
+ // Run afterEach hooks
161
+ if (hooks?.after) {
162
+ for (const hook of hooks.after) {
163
+ const hookResult = hook();
164
+ if (hookResult && typeof hookResult.then === 'function') {
165
+ await hookResult;
166
+ }
167
+ }
168
+ }
169
+
170
+ testResults.passed++;
171
+ testResults.tests.push({ name, passed: true, duration: Date.now() - start });
172
+ } catch (e) {
173
+ testResults.failed++;
174
+ testResults.tests.push({
175
+ name,
176
+ passed: false,
177
+ error: e.message || String(e),
178
+ duration: Date.now() - start
179
+ });
180
+ }
181
+ }
182
+
183
+ testResults.duration = Date.now() - testStart;
184
+
185
+
186
+ const hasTests = pendingTests.length > 0;
187
+ const success = scriptError === null && (!hasTests || testResults.failed === 0);
188
+
189
+ return Response.json({
190
+ success,
191
+ value: scriptResult,
192
+ logs,
193
+ testResults: hasTests ? testResults : undefined,
194
+ error: scriptError || undefined,
195
+ duration: 0
196
+ });
197
+ }
198
+ };
@@ -1,4 +0,0 @@
1
-
2
- > ai-evaluate@2.1.4 build /Users/nathanclevenger/projects/primitives.org.ai/packages/ai-evaluate
3
- > tsc -p tsconfig.json
4
-
@@ -1,54 +0,0 @@
1
-
2
- > ai-evaluate@2.1.3 test /Users/nathanclevenger/projects/primitives.org.ai/packages/ai-evaluate
3
- > vitest
4
-
5
-
6
- DEV v2.1.9 /Users/nathanclevenger/projects/primitives.org.ai/packages/ai-evaluate
7
-
8
- ✓ test/worker-template.test.ts (65 tests) 7ms
9
- stdout | test/evaluate.test.ts > evaluate > script execution > captures console output
10
- hello
11
-
12
- stderr | test/evaluate.test.ts > evaluate > script execution > captures console output
13
- warning
14
- error
15
-
16
- stdout | test/index.test.ts > types > LogEntry has correct shape
17
- test
18
-
19
- stderr | test/evaluate.test.ts > evaluate > script execution > handles script errors
20
- Script error: test error
21
-
22
- ✓ test/index.test.ts (8 tests) 1959ms
23
- ✓ types > EvaluateOptions interface is usable 1765ms
24
- stderr | test/evaluate-extended.test.ts > evaluate - extended tests > module errors > captures module runtime errors
25
- Module error: module error
26
-
27
- stdout | test/evaluate-extended.test.ts > evaluate - extended tests > console methods > captures console.info
28
- info message
29
-
30
- stdout | test/evaluate-extended.test.ts > evaluate - extended tests > console methods > captures console.debug
31
- debug message
32
-
33
- stdout | test/evaluate-extended.test.ts > evaluate - extended tests > console methods > stringifies objects in console output
34
- { a: 1 }
35
-
36
- stdout | test/evaluate-extended.test.ts > evaluate - extended tests > console methods > joins multiple console arguments
37
- a b c
38
-
39
- stderr | test/evaluate-extended.test.ts > evaluate - extended tests > async scripts > handles Promise rejection in script
40
- Script error: async error
41
-
42
- ✓ test/evaluate.test.ts (18 tests) 2319ms
43
- ✓ evaluate > script execution > executes simple expressions 1762ms
44
- ✓ test/evaluate-extended.test.ts (40 tests) 2896ms
45
- ✓ evaluate - extended tests > edge cases > handles empty options 1762ms
46
-
47
- Test Files 4 passed (4)
48
- Tests 131 passed (131)
49
- Start at 16:04:47
50
- Duration 3.30s (transform 119ms, setup 0ms, collect 216ms, tests 7.18s, environment 0ms, prepare 174ms)
51
-
52
- PASS Waiting for file changes...
53
- press h to show help, press q to quit
54
-  ELIFECYCLE  Test failed. See above for more details.
@@ -1,4 +0,0 @@
1
-
2
- > ai-evaluate@2.1.3 typecheck /Users/nathanclevenger/projects/primitives.org.ai/packages/ai-evaluate
3
- > tsc --noEmit
4
-
package/CHANGELOG.md DELETED
@@ -1,48 +0,0 @@
1
- # ai-evaluate
2
-
3
- ## 2.1.3
4
-
5
- ### Patch Changes
6
-
7
- - Documentation and testing improvements
8
- - Add deterministic AI testing suite with self-validating patterns
9
- - Apply StoryBrand narrative to all package READMEs
10
- - Update TESTING.md with four principles of deterministic AI testing
11
- - Fix duplicate examples package name conflict
12
-
13
- - Updated dependencies
14
- - ai-functions@2.1.3
15
- - ai-tests@2.1.3
16
-
17
- ## 2.1.1
18
-
19
- ### Patch Changes
20
-
21
- - Updated dependencies [6beb531]
22
- - ai-functions@2.1.1
23
- - ai-tests@2.1.1
24
-
25
- ## 2.0.3
26
-
27
- ### Patch Changes
28
-
29
- - Updated dependencies
30
- - rpc.do@0.2.0
31
- - ai-functions@2.0.3
32
- - ai-tests@2.0.3
33
-
34
- ## 2.0.2
35
-
36
- ### Patch Changes
37
-
38
- - Updated dependencies
39
- - ai-functions@2.0.2
40
- - ai-tests@2.0.2
41
-
42
- ## 2.0.1
43
-
44
- ### Patch Changes
45
-
46
- - Updated dependencies
47
- - ai-functions@2.0.1
48
- - ai-tests@2.0.1
@@ -1,20 +0,0 @@
1
- {
2
- "name": "eval-workers-do",
3
- "version": "0.0.1",
4
- "private": true,
5
- "description": "ai-evaluate REST API Worker for eval.workers.do",
6
- "type": "module",
7
- "scripts": {
8
- "dev": "wrangler dev",
9
- "deploy": "wrangler deploy",
10
- "typecheck": "tsc --noEmit"
11
- },
12
- "dependencies": {
13
- "ai-evaluate": "workspace:*"
14
- },
15
- "devDependencies": {
16
- "@cloudflare/workers-types": "^4.20251011.0",
17
- "typescript": "^5.7.0",
18
- "wrangler": "^4.60.0"
19
- }
20
- }
@@ -1,221 +0,0 @@
1
- /**
2
- * ai-evaluate REST API Worker
3
- *
4
- * Deploy: wrangler deploy
5
- * Domain: eval.workers.do
6
- *
7
- * Endpoints:
8
- * - POST / - Execute code (accepts { script?, module?, tests?, imports? })
9
- * - GET /health - Health check
10
- */
11
-
12
- import { evaluate, type SandboxEnv, type EvaluateOptions, type EvaluateResult } from 'ai-evaluate'
13
-
14
- interface Env extends SandboxEnv {
15
- loader: unknown
16
- }
17
-
18
- // CORS headers for cross-origin requests
19
- const corsHeaders = {
20
- 'Access-Control-Allow-Origin': '*',
21
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
22
- 'Access-Control-Allow-Headers': 'Content-Type',
23
- 'Content-Type': 'application/json',
24
- }
25
-
26
- export default {
27
- async fetch(request: Request, env: Env): Promise<Response> {
28
- const url = new URL(request.url)
29
-
30
- // Handle CORS preflight
31
- if (request.method === 'OPTIONS') {
32
- return new Response(null, { headers: corsHeaders })
33
- }
34
-
35
- // Health check endpoint
36
- if (url.pathname === '/health' || url.pathname === '/healthz') {
37
- return Response.json(
38
- {
39
- status: 'ok',
40
- service: 'ai-evaluate',
41
- version: '2.1.6',
42
- timestamp: new Date().toISOString(),
43
- },
44
- { headers: corsHeaders }
45
- )
46
- }
47
-
48
- // GET with query params - execute code
49
- // e.g., GET /?script=return+1+%2B+1
50
- // e.g., GET /?script=return+_.chunk([1,2,3],2)&imports=https://esm.sh/lodash
51
- if (request.method === 'GET' && url.pathname === '/') {
52
- const script = url.searchParams.get('script') || url.searchParams.get('code')
53
- const module = url.searchParams.get('module')
54
- const importsParam = url.searchParams.get('imports')
55
-
56
- // If script or module provided, execute code
57
- if (script || module) {
58
- try {
59
- // Parse imports - can be comma-separated or multiple params
60
- let imports: string[] | undefined
61
- if (importsParam) {
62
- imports = importsParam.includes(',')
63
- ? importsParam.split(',').map((s) => s.trim())
64
- : [importsParam]
65
- }
66
-
67
- const options: EvaluateOptions = {
68
- script: script || undefined,
69
- module: module || undefined,
70
- imports,
71
- }
72
-
73
- const result = await evaluate(options, env)
74
- return Response.json(
75
- {
76
- $id: request.url,
77
- $context: url.origin,
78
- input: {
79
- script: script || undefined,
80
- module: module || undefined,
81
- imports: imports || undefined,
82
- },
83
- ...result,
84
- },
85
- {
86
- status: result.success ? 200 : 400,
87
- headers: corsHeaders,
88
- }
89
- )
90
- } catch (error) {
91
- return Response.json(
92
- {
93
- $id: request.url,
94
- $context: url.origin,
95
- success: false,
96
- error: error instanceof Error ? error.message : 'Invalid request',
97
- logs: [],
98
- duration: 0,
99
- },
100
- { status: 400, headers: corsHeaders }
101
- )
102
- }
103
- }
104
-
105
- // No script - return API info with clickable examples
106
- const baseUrl = url.origin
107
- return Response.json(
108
- {
109
- name: 'ai-evaluate',
110
- version: '2.1.6',
111
- description: 'Secure code execution in sandboxed Cloudflare Workers',
112
- endpoints: {
113
- 'GET /?script=...': 'Execute code via query params',
114
- 'POST /': 'Execute code via JSON body',
115
- 'GET /health': 'Health check',
116
- },
117
- tryIt: {
118
- // Basic JavaScript
119
- math: `${baseUrl}/?script=return+1+%2B+1`,
120
- variables: `${baseUrl}/?script=const+x+%3D+10%3B+const+y+%3D+20%3B+return+x+*+y`,
121
- arrays: `${baseUrl}/?script=return+[1,2,3,4,5].map(n+%3D%3E+n+*+2)`,
122
- objects: `${baseUrl}/?script=return+%7B+name%3A+'eval'%2C+version%3A+'2.1.6'+%7D`,
123
- functions: `${baseUrl}/?script=const+add+%3D+(a%2Cb)+%3D%3E+a%2Bb%3B+return+add(5%2C3)`,
124
- async: `${baseUrl}/?script=return+await+Promise.resolve(42)`,
125
- console: `${baseUrl}/?script=console.log('Hello')%3B+return+'check+logs'`,
126
- json: `${baseUrl}/?script=return+JSON.parse('%7B%22a%22%3A1%7D')`,
127
- date: `${baseUrl}/?script=return+new+Date().toISOString()`,
128
- // npm packages (bare names auto-resolve via esm.sh)
129
- lodash: `${baseUrl}/?script=return+_.chunk([1,2,3,4,5,6],2)&imports=lodash`,
130
- lodashMap: `${baseUrl}/?script=return+_.map([1,2,3],n%3D%3En*10)&imports=lodash`,
131
- dayjs: `${baseUrl}/?script=return+dayjs().format('YYYY-MM-DD')&imports=dayjs`,
132
- uuid: `${baseUrl}/?script=return+uuid.v4()&imports=uuid`,
133
- // Versioned packages
134
- lodashVersioned: `${baseUrl}/?script=return+_.VERSION&imports=lodash@4.17.21`,
135
- chalk: `${baseUrl}/?script=return+chalk.blue('Hello')&imports=chalk@5`,
136
- zod: `${baseUrl}/?script=const+schema+%3D+z.string()%3B+return+schema.parse('hello')&imports=zod`,
137
- },
138
- curl: {
139
- get: `curl '${baseUrl}/?script=return+1+%2B+1'`,
140
- post: `curl -X POST ${baseUrl} -H 'Content-Type: application/json' -d '{"script":"return 1 + 1"}'`,
141
- withImports: `curl -X POST ${baseUrl} -H 'Content-Type: application/json' -d '{"script":"return _.chunk([1,2,3,4,5,6],2)","imports":["lodash"]}'`,
142
- multipleImports: `curl -X POST ${baseUrl} -H 'Content-Type: application/json' -d '{"script":"return { chunks: _.chunk([1,2,3,4],2), date: dayjs().format() }","imports":["lodash","dayjs"]}'`,
143
- },
144
- },
145
- { headers: corsHeaders }
146
- )
147
- }
148
-
149
- // Execute code endpoint (POST)
150
- if (request.method === 'POST') {
151
- try {
152
- const body = (await request.json()) as Partial<EvaluateOptions> & { code?: string }
153
-
154
- // Support both simple { code } and full { module, tests, script }
155
- const options: EvaluateOptions = {
156
- module: body.module,
157
- tests: body.tests,
158
- script: body.script || body.code,
159
- timeout: body.timeout,
160
- imports: body.imports,
161
- sdk: body.sdk,
162
- fetch: body.fetch,
163
- }
164
-
165
- // Validate that at least one of script/module/tests is provided
166
- if (!options.script && !options.module && !options.tests) {
167
- return Response.json(
168
- {
169
- $id: request.url,
170
- $context: url.origin,
171
- success: false,
172
- error: 'At least one of script, module, or tests is required',
173
- logs: [],
174
- duration: 0,
175
- },
176
- { status: 400, headers: corsHeaders }
177
- )
178
- }
179
-
180
- const result = await evaluate(options, env)
181
- return Response.json(
182
- {
183
- $id: request.url,
184
- $context: url.origin,
185
- input: {
186
- script: options.script || undefined,
187
- module: options.module || undefined,
188
- tests: options.tests || undefined,
189
- imports: options.imports || undefined,
190
- timeout: options.timeout || undefined,
191
- sdk: options.sdk || undefined,
192
- },
193
- ...result,
194
- },
195
- {
196
- status: result.success ? 200 : 400,
197
- headers: corsHeaders,
198
- }
199
- )
200
- } catch (error) {
201
- return Response.json(
202
- {
203
- $id: request.url,
204
- $context: url.origin,
205
- success: false,
206
- error: error instanceof Error ? error.message : 'Invalid request',
207
- logs: [],
208
- duration: 0,
209
- },
210
- { status: 400, headers: corsHeaders }
211
- )
212
- }
213
- }
214
-
215
- // 404 for unknown routes
216
- return Response.json(
217
- { error: 'Not found', path: url.pathname },
218
- { status: 404, headers: corsHeaders }
219
- )
220
- },
221
- }
@@ -1,25 +0,0 @@
1
- {
2
- "$schema": "node_modules/wrangler/config-schema.json",
3
- "name": "eval-workers-do",
4
- "main": "src/index.ts",
5
- "compatibility_date": "2026-01-01",
6
- "account_id": "b6641681fe423910342b9ffa1364c76d",
7
-
8
- // Routes - zone route for workers.do domain
9
- "routes": [
10
- { "pattern": "eval.workers.do/*", "zone_name": "workers.do" }
11
- ],
12
-
13
- // Keep workers.dev enabled for testing
14
- "workers_dev": true,
15
-
16
- // Worker loader for dynamic sandbox execution
17
- "worker_loaders": [
18
- { "binding": "loader" }
19
- ],
20
-
21
- // Bundle configuration to resolve workspace package
22
- "alias": {
23
- "ai-evaluate": "../dist/index.js"
24
- }
25
- }