@lesliechan721/agent-skill-eval 0.1.0-beta.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 (50) hide show
  1. package/dist/bin.d.ts +2 -0
  2. package/dist/bin.js +80 -0
  3. package/dist/bin.js.map +1 -0
  4. package/dist/bundle.d.ts +93 -0
  5. package/dist/bundle.js +216 -0
  6. package/dist/bundle.js.map +1 -0
  7. package/dist/capabilities.d.ts +2 -0
  8. package/dist/capabilities.js +38 -0
  9. package/dist/capabilities.js.map +1 -0
  10. package/dist/common.d.ts +8 -0
  11. package/dist/common.js +22 -0
  12. package/dist/common.js.map +1 -0
  13. package/dist/corpus-digest.d.ts +3 -0
  14. package/dist/corpus-digest.js +19 -0
  15. package/dist/corpus-digest.js.map +1 -0
  16. package/dist/index.d.ts +16 -0
  17. package/dist/index.js +11 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/json-schema.d.ts +3 -0
  20. package/dist/json-schema.js +70 -0
  21. package/dist/json-schema.js.map +1 -0
  22. package/dist/output-path.d.ts +1 -0
  23. package/dist/output-path.js +34 -0
  24. package/dist/output-path.js.map +1 -0
  25. package/dist/plan.d.ts +79 -0
  26. package/dist/plan.js +285 -0
  27. package/dist/plan.js.map +1 -0
  28. package/dist/process-tree.d.ts +25 -0
  29. package/dist/process-tree.js +385 -0
  30. package/dist/process-tree.js.map +1 -0
  31. package/dist/process.d.ts +14 -0
  32. package/dist/process.js +301 -0
  33. package/dist/process.js.map +1 -0
  34. package/dist/run.d.ts +2 -0
  35. package/dist/run.js +592 -0
  36. package/dist/run.js.map +1 -0
  37. package/dist/runtime-skill.d.ts +18 -0
  38. package/dist/runtime-skill.js +135 -0
  39. package/dist/runtime-skill.js.map +1 -0
  40. package/dist/schemas/aw-skill-eval-execution-plan.schema.json +220 -0
  41. package/dist/schemas/digests.json +5 -0
  42. package/dist/schemas/eval-run-bundle.schema.json +408 -0
  43. package/dist/schemas/eval-runtime-capabilities.schema.json +85 -0
  44. package/dist/schemas.d.ts +5 -0
  45. package/dist/schemas.js +27 -0
  46. package/dist/schemas.js.map +1 -0
  47. package/dist/types.d.ts +86 -0
  48. package/dist/types.js +2 -0
  49. package/dist/types.js.map +1 -0
  50. package/package.json +34 -0
@@ -0,0 +1,408 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://agent-skill-toolkit.invalid/schemas/eval-run-bundle.schema.json",
4
+ "title": "EvalRunBundle",
5
+ "type": "object",
6
+ "required": [
7
+ "schemaVersion",
8
+ "runner",
9
+ "independentContext",
10
+ "provider",
11
+ "planDigest",
12
+ "targetSkillDigest",
13
+ "caseCorpusDigest",
14
+ "comparisonPlanned",
15
+ "evidenceLevel",
16
+ "runs",
17
+ "aggregate",
18
+ "limitations"
19
+ ],
20
+ "properties": {
21
+ "schemaVersion": {
22
+ "const": 3
23
+ },
24
+ "runner": {
25
+ "type": "object",
26
+ "required": [
27
+ "id",
28
+ "version"
29
+ ],
30
+ "properties": {
31
+ "id": {
32
+ "type": "string",
33
+ "minLength": 1
34
+ },
35
+ "version": {
36
+ "type": "string",
37
+ "minLength": 1
38
+ }
39
+ },
40
+ "additionalProperties": false
41
+ },
42
+ "independentContext": {
43
+ "type": "boolean"
44
+ },
45
+ "provider": {
46
+ "type": "object",
47
+ "required": [
48
+ "id",
49
+ "version"
50
+ ],
51
+ "properties": {
52
+ "id": {
53
+ "type": "string",
54
+ "minLength": 1
55
+ },
56
+ "version": {
57
+ "type": "string",
58
+ "minLength": 1
59
+ }
60
+ },
61
+ "additionalProperties": false
62
+ },
63
+ "planDigest": {
64
+ "type": "string",
65
+ "pattern": "^sha256:[0-9a-f]{64}$"
66
+ },
67
+ "targetSkillDigest": {
68
+ "type": "string",
69
+ "pattern": "^sha256:[0-9a-f]{64}$"
70
+ },
71
+ "caseCorpusDigest": {
72
+ "type": "string",
73
+ "pattern": "^sha256:[0-9a-f]{64}$"
74
+ },
75
+ "comparisonPlanned": {
76
+ "type": "boolean"
77
+ },
78
+ "evidenceLevel": {
79
+ "enum": [
80
+ "executed",
81
+ "comparative"
82
+ ]
83
+ },
84
+ "evidenceRequirement": {
85
+ "type": "object",
86
+ "required": [
87
+ "minimumEvidenceLevel",
88
+ "plannedComparisons",
89
+ "comparableComparisons",
90
+ "satisfied"
91
+ ],
92
+ "properties": {
93
+ "minimumEvidenceLevel": {
94
+ "enum": [
95
+ "executed",
96
+ "comparative"
97
+ ]
98
+ },
99
+ "plannedComparisons": {
100
+ "type": "integer",
101
+ "minimum": 0
102
+ },
103
+ "comparableComparisons": {
104
+ "type": "integer",
105
+ "minimum": 0
106
+ },
107
+ "satisfied": {
108
+ "type": "boolean"
109
+ }
110
+ },
111
+ "additionalProperties": false
112
+ },
113
+ "processManagement": {
114
+ "type": "object",
115
+ "required": [
116
+ "mode",
117
+ "descendantCleanupGuaranteed"
118
+ ],
119
+ "properties": {
120
+ "mode": {
121
+ "enum": [
122
+ "managed",
123
+ "verified-observed",
124
+ "observed"
125
+ ]
126
+ },
127
+ "descendantCleanupGuaranteed": {
128
+ "type": "boolean"
129
+ },
130
+ "limitation": {
131
+ "type": "string",
132
+ "minLength": 1
133
+ }
134
+ },
135
+ "allOf": [
136
+ {
137
+ "if": {
138
+ "properties": {
139
+ "mode": {
140
+ "enum": [
141
+ "verified-observed",
142
+ "observed"
143
+ ]
144
+ }
145
+ },
146
+ "required": [
147
+ "mode"
148
+ ]
149
+ },
150
+ "then": {
151
+ "required": [
152
+ "limitation"
153
+ ]
154
+ }
155
+ }
156
+ ],
157
+ "additionalProperties": false
158
+ },
159
+ "runs": {
160
+ "type": "array",
161
+ "minItems": 1,
162
+ "items": {
163
+ "type": "object",
164
+ "required": [
165
+ "caseId",
166
+ "configuration",
167
+ "comparisonId",
168
+ "skillMode",
169
+ "skillDigest",
170
+ "runIndex",
171
+ "status",
172
+ "exitCode",
173
+ "durationMs",
174
+ "stdoutPath",
175
+ "stdoutDigest",
176
+ "stderrPath",
177
+ "stderrDigest",
178
+ "usage",
179
+ "grading",
180
+ "artifacts"
181
+ ],
182
+ "properties": {
183
+ "caseId": {
184
+ "type": "string",
185
+ "minLength": 1
186
+ },
187
+ "configuration": {
188
+ "type": "string",
189
+ "minLength": 1
190
+ },
191
+ "comparisonId": {
192
+ "type": [
193
+ "string",
194
+ "null"
195
+ ],
196
+ "minLength": 1
197
+ },
198
+ "skillMode": {
199
+ "enum": [
200
+ "with-skill",
201
+ "without-skill",
202
+ "previous-version"
203
+ ]
204
+ },
205
+ "skillDigest": {
206
+ "type": [
207
+ "string",
208
+ "null"
209
+ ],
210
+ "pattern": "^sha256:[0-9a-f]{64}$"
211
+ },
212
+ "runIndex": {
213
+ "type": "integer",
214
+ "minimum": 1
215
+ },
216
+ "status": {
217
+ "enum": [
218
+ "completed",
219
+ "process-failed",
220
+ "timeout",
221
+ "error"
222
+ ]
223
+ },
224
+ "exitCode": {
225
+ "type": [
226
+ "integer",
227
+ "null"
228
+ ]
229
+ },
230
+ "durationMs": {
231
+ "type": "integer",
232
+ "minimum": 0
233
+ },
234
+ "stdoutPath": {
235
+ "type": "string",
236
+ "minLength": 1
237
+ },
238
+ "stdoutDigest": {
239
+ "type": "string",
240
+ "pattern": "^sha256:[0-9a-f]{64}$"
241
+ },
242
+ "stderrPath": {
243
+ "type": "string",
244
+ "minLength": 1
245
+ },
246
+ "stderrDigest": {
247
+ "type": "string",
248
+ "pattern": "^sha256:[0-9a-f]{64}$"
249
+ },
250
+ "usage": {
251
+ "type": [
252
+ "object",
253
+ "null"
254
+ ]
255
+ },
256
+ "grading": {
257
+ "type": [
258
+ "object",
259
+ "null"
260
+ ],
261
+ "required": [
262
+ "grader",
263
+ "result"
264
+ ],
265
+ "properties": {
266
+ "grader": {
267
+ "type": "object",
268
+ "required": [
269
+ "id",
270
+ "version"
271
+ ],
272
+ "properties": {
273
+ "id": {
274
+ "type": "string",
275
+ "minLength": 1
276
+ },
277
+ "version": {
278
+ "type": "string",
279
+ "minLength": 1
280
+ }
281
+ },
282
+ "additionalProperties": true
283
+ },
284
+ "result": {
285
+ "type": "object"
286
+ }
287
+ },
288
+ "additionalProperties": true
289
+ },
290
+ "artifacts": {
291
+ "type": "array",
292
+ "items": {
293
+ "type": "object",
294
+ "required": [
295
+ "role",
296
+ "path",
297
+ "digest"
298
+ ],
299
+ "properties": {
300
+ "role": {
301
+ "enum": [
302
+ "trace",
303
+ "report",
304
+ "output",
305
+ "grading",
306
+ "other"
307
+ ]
308
+ },
309
+ "path": {
310
+ "type": "string",
311
+ "minLength": 1
312
+ },
313
+ "digest": {
314
+ "type": "string",
315
+ "pattern": "^sha256:[0-9a-f]{64}$"
316
+ }
317
+ },
318
+ "additionalProperties": false
319
+ }
320
+ }
321
+ },
322
+ "additionalProperties": false
323
+ }
324
+ },
325
+ "aggregate": {
326
+ "type": "object",
327
+ "required": [
328
+ "cases",
329
+ "configurations",
330
+ "runs",
331
+ "completed",
332
+ "processFailed",
333
+ "timedOut",
334
+ "comparisons"
335
+ ],
336
+ "properties": {
337
+ "cases": {
338
+ "type": "integer",
339
+ "minimum": 1
340
+ },
341
+ "configurations": {
342
+ "type": "integer",
343
+ "minimum": 1
344
+ },
345
+ "runs": {
346
+ "type": "integer",
347
+ "minimum": 1
348
+ },
349
+ "completed": {
350
+ "type": "integer",
351
+ "minimum": 0
352
+ },
353
+ "processFailed": {
354
+ "type": "integer",
355
+ "minimum": 0
356
+ },
357
+ "timedOut": {
358
+ "type": "integer",
359
+ "minimum": 0
360
+ },
361
+ "comparisons": {
362
+ "type": "array",
363
+ "items": {
364
+ "type": "object",
365
+ "required": [
366
+ "comparisonId",
367
+ "caseId",
368
+ "runIndex",
369
+ "paired",
370
+ "comparable"
371
+ ],
372
+ "properties": {
373
+ "comparisonId": {
374
+ "type": "string",
375
+ "minLength": 1
376
+ },
377
+ "caseId": {
378
+ "type": "string",
379
+ "minLength": 1
380
+ },
381
+ "runIndex": {
382
+ "type": "integer",
383
+ "minimum": 1
384
+ },
385
+ "paired": {
386
+ "type": "boolean"
387
+ },
388
+ "comparable": {
389
+ "type": "boolean"
390
+ }
391
+ },
392
+ "additionalProperties": false
393
+ }
394
+ }
395
+ },
396
+ "additionalProperties": false
397
+ },
398
+ "limitations": {
399
+ "type": "array",
400
+ "items": {
401
+ "type": "string",
402
+ "minLength": 1
403
+ },
404
+ "uniqueItems": true
405
+ }
406
+ },
407
+ "additionalProperties": false
408
+ }
@@ -0,0 +1,85 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://agent-skill-toolkit.invalid/schemas/eval-runtime-capabilities.schema.json",
4
+ "title": "EvalRuntimeCapabilities",
5
+ "type": "object",
6
+ "required": [
7
+ "schemaVersion",
8
+ "provider",
9
+ "providerVersion",
10
+ "capabilities",
11
+ "limitations"
12
+ ],
13
+ "properties": {
14
+ "schemaVersion": {
15
+ "const": 1
16
+ },
17
+ "provider": {
18
+ "type": "string",
19
+ "minLength": 1
20
+ },
21
+ "providerVersion": {
22
+ "type": "string",
23
+ "minLength": 1
24
+ },
25
+ "capabilities": {
26
+ "type": "object",
27
+ "required": [
28
+ "triggerDiscovery",
29
+ "skillPathExecution",
30
+ "previousVersionBaseline",
31
+ "withoutSkillBaseline",
32
+ "cleanContext",
33
+ "parallelRuns",
34
+ "toolTrace",
35
+ "tokenUsage",
36
+ "duration",
37
+ "sandbox",
38
+ "materialization"
39
+ ],
40
+ "properties": {
41
+ "triggerDiscovery": {
42
+ "type": "boolean"
43
+ },
44
+ "skillPathExecution": {
45
+ "type": "boolean"
46
+ },
47
+ "previousVersionBaseline": {
48
+ "type": "boolean"
49
+ },
50
+ "withoutSkillBaseline": {
51
+ "type": "boolean"
52
+ },
53
+ "cleanContext": {
54
+ "type": "boolean"
55
+ },
56
+ "parallelRuns": {
57
+ "type": "boolean"
58
+ },
59
+ "toolTrace": {
60
+ "type": "boolean"
61
+ },
62
+ "tokenUsage": {
63
+ "type": "boolean"
64
+ },
65
+ "duration": {
66
+ "type": "boolean"
67
+ },
68
+ "sandbox": {
69
+ "type": "boolean"
70
+ },
71
+ "materialization": {
72
+ "type": "boolean"
73
+ }
74
+ },
75
+ "additionalProperties": false
76
+ },
77
+ "limitations": {
78
+ "type": "array",
79
+ "items": {
80
+ "type": "string"
81
+ }
82
+ }
83
+ },
84
+ "additionalProperties": false
85
+ }
@@ -0,0 +1,5 @@
1
+ import type { Digest, RuntimeCapabilities, SchemaName, ValidationResult } from './types.js';
2
+ export declare function isSchemaName(value: string): value is SchemaName;
3
+ export declare function getSchema(name: SchemaName): Promise<Record<string, unknown>>;
4
+ export declare function getSchemaDigest(name: SchemaName): Promise<Digest>;
5
+ export declare function validateRuntimeCapabilities(value: unknown): Promise<ValidationResult<RuntimeCapabilities>>;
@@ -0,0 +1,27 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { validateJsonSchema } from './json-schema.js';
3
+ const schemaNames = new Set([
4
+ 'aw-skill-eval-execution-plan',
5
+ 'eval-run-bundle',
6
+ 'eval-runtime-capabilities',
7
+ ]);
8
+ export function isSchemaName(value) {
9
+ return schemaNames.has(value);
10
+ }
11
+ export async function getSchema(name) {
12
+ const source = new URL(`./schemas/${name}.schema.json`, import.meta.url);
13
+ return JSON.parse(await readFile(source, 'utf8'));
14
+ }
15
+ export async function getSchemaDigest(name) {
16
+ const digests = JSON.parse(await readFile(new URL('./schemas/digests.json', import.meta.url), 'utf8'));
17
+ return digests[name];
18
+ }
19
+ export async function validateRuntimeCapabilities(value) {
20
+ const errors = validateJsonSchema(await getSchema('eval-runtime-capabilities'), value);
21
+ return {
22
+ valid: errors.length === 0,
23
+ errors,
24
+ calculated: errors.length === 0 ? value : null,
25
+ };
26
+ }
27
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAQrD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAa;IACtC,8BAA8B;IAC9B,iBAAiB;IACjB,2BAA2B;CAC5B,CAAC,CAAA;AAEF,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,WAAW,CAAC,GAAG,CAAC,KAAmB,CAAC,CAAA;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAgB;IAC9C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,aAAa,IAAI,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACxE,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAA4B,CAAA;AAC9E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAgB;IACpD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CACxB,MAAM,QAAQ,CAAC,IAAI,GAAG,CAAC,wBAAwB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAC7C,CAAA;IAC/B,OAAO,OAAO,CAAC,IAAI,CAAC,CAAA;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,KAAc;IAEd,MAAM,MAAM,GAAG,kBAAkB,CAAC,MAAM,SAAS,CAAC,2BAA2B,CAAC,EAAE,KAAK,CAAC,CAAA;IACtF,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC;QAC1B,MAAM;QACN,UAAU,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAA4B,CAAC,CAAC,CAAC,IAAI;KACtE,CAAA;AACH,CAAC"}
@@ -0,0 +1,86 @@
1
+ export type Digest = `sha256:${string}`;
2
+ export interface ValidationResult<TCalculated = unknown> {
3
+ valid: boolean;
4
+ errors: string[];
5
+ calculated: TCalculated | null;
6
+ }
7
+ export interface RuntimeCapabilities {
8
+ schemaVersion: 1;
9
+ provider: string;
10
+ providerVersion: string;
11
+ capabilities: {
12
+ triggerDiscovery: boolean;
13
+ skillPathExecution: boolean;
14
+ previousVersionBaseline: boolean;
15
+ withoutSkillBaseline: boolean;
16
+ cleanContext: boolean;
17
+ parallelRuns: boolean;
18
+ toolTrace: boolean;
19
+ tokenUsage: boolean;
20
+ duration: boolean;
21
+ sandbox: boolean;
22
+ materialization: boolean;
23
+ };
24
+ limitations: string[];
25
+ }
26
+ export interface RuntimeSkillSnapshot {
27
+ root: string;
28
+ digest: Digest;
29
+ }
30
+ export interface ProcessRequest {
31
+ executable: string;
32
+ args: string[];
33
+ cwd: string;
34
+ env: NodeJS.ProcessEnv;
35
+ timeoutMs: number;
36
+ maxBufferBytes: number;
37
+ signal?: AbortSignal;
38
+ }
39
+ export interface ProviderProcessHandle {
40
+ pid: number;
41
+ }
42
+ export type ProcessTerminationReason = 'complete' | 'timeout' | 'abort' | 'max-buffer';
43
+ export interface ProcessTreeCleanupConfirmation {
44
+ confirmed: true;
45
+ }
46
+ export interface ExternalContainmentConfirmation {
47
+ confirmed: true;
48
+ descendantCleanupGuaranteed: true;
49
+ readOnlySkillSnapshotGuaranteed: true;
50
+ }
51
+ export interface ProcessLifecycleHooks {
52
+ externalContainment?: ExternalContainmentConfirmation;
53
+ onSpawn?(handle: ProviderProcessHandle): void | Promise<void>;
54
+ terminateProcessTree?(handle: ProviderProcessHandle, reason: ProcessTerminationReason): Promise<ProcessTreeCleanupConfirmation>;
55
+ }
56
+ export interface ProcessResult {
57
+ stdout: string;
58
+ stderr: string;
59
+ exitCode: number | null;
60
+ signal: NodeJS.Signals | null;
61
+ timedOut: boolean;
62
+ cancelled: boolean;
63
+ terminationReason: ProcessTerminationReason | null;
64
+ maxBufferExceeded: boolean;
65
+ stdoutTruncated: boolean;
66
+ stderrTruncated: boolean;
67
+ descendantCleanupGuaranteed: boolean;
68
+ gradingEvidenceAccepted: boolean;
69
+ containmentMode: 'managed' | 'verified-observed' | 'observed' | 'unmanaged-confirmed';
70
+ containmentLimitation?: string;
71
+ }
72
+ export interface RunExecutionPlanOptions {
73
+ processHooks?: ProcessLifecycleHooks;
74
+ beforeBundleCommit?: () => void | Promise<void>;
75
+ sourceEnv?: NodeJS.ProcessEnv;
76
+ platform?: NodeJS.Platform;
77
+ processTree?: import('./process-tree.js').ProcessTreeDependencies;
78
+ }
79
+ export interface RunExecutionResult {
80
+ valid: true;
81
+ bundlePath: string;
82
+ bundleDigest: Digest;
83
+ evidenceLevel: 'executed' | 'comparative';
84
+ requirementsMet: boolean;
85
+ }
86
+ export type SchemaName = 'aw-skill-eval-execution-plan' | 'eval-run-bundle' | 'eval-runtime-capabilities';
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@lesliechan721/agent-skill-eval",
3
+ "version": "0.1.0-beta.1",
4
+ "type": "module",
5
+ "engines": {
6
+ "node": ">=22"
7
+ },
8
+ "publishConfig": {
9
+ "access": "public",
10
+ "registry": "https://registry.npmjs.org/"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "bin": {
19
+ "aw-skill-eval": "./dist/bin.js"
20
+ },
21
+ "files": [
22
+ "dist/"
23
+ ],
24
+ "devDependencies": {
25
+ "@types/node": "^24.0.0",
26
+ "typescript": "^5.9.3",
27
+ "vitest": "^3.2.4"
28
+ },
29
+ "scripts": {
30
+ "build": "node scripts/clean-dist.mjs && tsc -p tsconfig.json && node scripts/embed-schemas.mjs",
31
+ "test": "pnpm run build && vitest run",
32
+ "test:unit": "pnpm run build && vitest run tests/unit"
33
+ }
34
+ }