@tracecode/harness 0.9.2 → 0.9.3

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.
@@ -6481,9 +6481,6 @@ async function executeCode(payload) {
6481
6481
  const consoleOutput = [];
6482
6482
  const consoleProxy = createConsoleProxy(consoleOutput);
6483
6483
  const runtimeGlobal = createJavaScriptRuntimeGlobal(consoleProxy);
6484
- const normalizedInputs = normalizeInputs(inputs);
6485
- const materializers = await resolveInputMaterializers(code, functionName, executionStyle, language);
6486
- const materializedInputs = applyInputMaterializers(normalizedInputs, materializers);
6487
6484
 
6488
6485
  try {
6489
6486
  if (typeof code !== 'string') {
@@ -6494,6 +6491,9 @@ async function executeCode(payload) {
6494
6491
  }
6495
6492
 
6496
6493
  const executableCode = await prepareExecutableCode(code, language);
6494
+ const materializers = await resolveInputMaterializers(code, functionName, executionStyle, language);
6495
+ const normalizedInputs = normalizeInputs(inputs);
6496
+ const materializedInputs = applyInputMaterializers(normalizedInputs, materializers);
6497
6497
  const hasNamedFunction = typeof functionName === 'string' && functionName.length > 0;
6498
6498
  let output;
6499
6499
 
@@ -6539,6 +6539,19 @@ async function executeCode(payload) {
6539
6539
  }
6540
6540
  }
6541
6541
 
6542
+ function batchSignatureSampleInput(inputBatch) {
6543
+ const sample = {};
6544
+ for (const inputs of inputBatch) {
6545
+ if (!inputs || typeof inputs !== 'object' || Array.isArray(inputs)) continue;
6546
+ for (const key of Object.keys(inputs)) {
6547
+ if (!Object.prototype.hasOwnProperty.call(sample, key)) {
6548
+ sample[key] = inputs[key];
6549
+ }
6550
+ }
6551
+ }
6552
+ return sample;
6553
+ }
6554
+
6542
6555
  async function executeCodeBatch(payload) {
6543
6556
  const startedAt = performanceNow();
6544
6557
  const inputBatch = Array.isArray(payload?.inputBatch)
@@ -6554,10 +6567,97 @@ async function executeCodeBatch(payload) {
6554
6567
  };
6555
6568
  }
6556
6569
 
6570
+ const {
6571
+ code,
6572
+ functionName,
6573
+ executionStyle = 'function',
6574
+ language = 'javascript',
6575
+ } = payload ?? {};
6576
+
6557
6577
  const results = [];
6558
- for (const inputs of inputBatch) {
6559
- results.push(await executeCode({ ...payload, inputs }));
6578
+ try {
6579
+ if (typeof code !== 'string') {
6580
+ throw new Error('`code` must be a string');
6581
+ }
6582
+ if (language !== 'javascript' && language !== 'typescript') {
6583
+ throw new Error(`Unsupported language for JavaScript worker: ${String(language)}`);
6584
+ }
6585
+
6586
+ const executableCode = await prepareExecutableCode(code, language);
6587
+ const materializers = await resolveInputMaterializers(code, functionName, executionStyle, language);
6588
+ const hasNamedFunction = typeof functionName === 'string' && functionName.length > 0;
6589
+ let runner;
6590
+ let inputArguments = [];
6591
+ let argNames = [];
6592
+ let argumentMaterializers = [];
6593
+
6594
+ if (hasNamedFunction) {
6595
+ if (executionStyle === 'ops-class') {
6596
+ runner = buildFunctionExecutionRunner(executableCode, executionStyle, [], [], code);
6597
+ } else {
6598
+ const sampleInputs = applyInputMaterializers(normalizeInputs(batchSignatureSampleInput(inputBatch)), materializers);
6599
+ inputArguments = await resolveOrderedInputArguments(code, functionName, sampleInputs, executionStyle, language);
6600
+ argNames = inputArguments.map((argument, index) => `__arg${index}${argument.rest ? '__tracecodeRest' : ''}`);
6601
+ argumentMaterializers = inputArguments.map((argument) => materializers[argument.key] ?? null);
6602
+ runner = buildFunctionExecutionRunner(executableCode, executionStyle, argNames, argumentMaterializers, code);
6603
+ }
6604
+ } else {
6605
+ if (executionStyle !== 'function') {
6606
+ throw new Error('Script-mode execution only supports executionStyle="function".');
6607
+ }
6608
+ runner = buildScriptExecutionRunner(executableCode, code);
6609
+ }
6610
+
6611
+ for (const inputs of inputBatch) {
6612
+ const consoleOutput = [];
6613
+ const consoleProxy = createConsoleProxy(consoleOutput);
6614
+ const runtimeGlobal = createJavaScriptRuntimeGlobal(consoleProxy);
6615
+ try {
6616
+ const materializedInputs = applyInputMaterializers(normalizeInputs(inputs), materializers);
6617
+ let output;
6618
+ if (hasNamedFunction) {
6619
+ if (executionStyle === 'ops-class') {
6620
+ const { operations, argumentsList } = getOpsClassInputs(materializedInputs);
6621
+ output = await Promise.resolve(runner(consoleProxy, functionName, operations, argumentsList, runtimeGlobal));
6622
+ } else {
6623
+ const argValues = inputArguments.map((argument) => materializedInputs[argument.key]);
6624
+ output = await Promise.resolve(runner(consoleProxy, functionName, ...argValues, runtimeGlobal));
6625
+ }
6626
+ } else {
6627
+ const scriptStdin = typeof materializedInputs.stdin === 'string' ? materializedInputs.stdin : undefined;
6628
+ output = await Promise.resolve(runner(consoleProxy, scriptStdin, runtimeGlobal));
6629
+ if (scriptStdin !== undefined && output === null) {
6630
+ output = consoleOutput.length > 0 ? `${consoleOutput.join('\n')}\n` : '';
6631
+ }
6632
+ }
6633
+ results.push({
6634
+ success: true,
6635
+ output: serializeOutputValue(output),
6636
+ consoleOutput,
6637
+ });
6638
+ } catch (error) {
6639
+ results.push({
6640
+ success: false,
6641
+ output: null,
6642
+ error: formatRuntimeErrorMessage(error),
6643
+ errorLine: extractUserErrorLine(error),
6644
+ consoleOutput,
6645
+ });
6646
+ }
6647
+ }
6648
+ } catch (error) {
6649
+ const message = formatRuntimeErrorMessage(error);
6650
+ for (let index = 0; index < inputBatch.length; index += 1) {
6651
+ results.push({
6652
+ success: false,
6653
+ output: null,
6654
+ error: message,
6655
+ errorLine: extractUserErrorLine(error),
6656
+ consoleOutput: [],
6657
+ });
6658
+ }
6560
6659
  }
6660
+
6561
6661
  return {
6562
6662
  success: results.every((result) => result.success === true),
6563
6663
  results,
@@ -4350,30 +4350,13 @@ json.dumps({
4350
4350
  }
4351
4351
 
4352
4352
  async function executeCodeBatch(code, functionName, inputBatch, executionStyle = 'function') {
4353
- const startedAt = performance.now();
4354
- const cases = Array.isArray(inputBatch)
4355
- ? inputBatch.map((inputs) => (inputs && typeof inputs === 'object' ? inputs : {}))
4356
- : [];
4357
- if (cases.length === 0) {
4358
- return {
4359
- success: false,
4360
- results: [],
4361
- error: 'Python batch execution requires a non-empty inputBatch array.',
4362
- consoleOutput: [],
4363
- timings: { totalMs: performance.now() - startedAt },
4364
- };
4365
- }
4366
-
4367
- const results = [];
4368
- for (const inputs of cases) {
4369
- results.push(await executeCode(code, functionName, inputs, executionStyle));
4370
- }
4371
- return {
4372
- success: results.every((result) => result.success === true),
4373
- results,
4374
- consoleOutput: results.flatMap((result) => result.consoleOutput ?? []),
4375
- timings: { totalMs: performance.now() - startedAt },
4376
- };
4353
+ return loadPyodideRuntimeCore().executeCodeBatch(
4354
+ buildRuntimeDeps(),
4355
+ code,
4356
+ functionName,
4357
+ inputBatch,
4358
+ executionStyle
4359
+ );
4377
4360
  }
4378
4361
 
4379
4362
  async function processMessage(data) {
@@ -4339,10 +4339,341 @@ json.dumps({
4339
4339
  }
4340
4340
  }
4341
4341
 
4342
+ async function executeCodeBatch(deps, code, functionName, inputBatch, executionStyle = 'function') {
4343
+ const startedAt = deps.performanceNow();
4344
+ const userCodeLineCount = code.split('\n').length;
4345
+ const cases = Array.isArray(inputBatch)
4346
+ ? inputBatch.map((inputs) => (inputs && typeof inputs === 'object' && !Array.isArray(inputs) ? inputs : {}))
4347
+ : [];
4348
+
4349
+ if (cases.length === 0) {
4350
+ return {
4351
+ success: false,
4352
+ results: [],
4353
+ error: 'Python batch execution requires a non-empty inputBatch array.',
4354
+ consoleOutput: [],
4355
+ timings: { totalMs: deps.performanceNow() - startedAt },
4356
+ };
4357
+ }
4358
+
4359
+ try {
4360
+ await deps.loadPyodideInstance();
4361
+
4362
+ const functionNameLiteral = deps.toPythonLiteral(functionName);
4363
+ const executionStyleLiteral = deps.toPythonLiteral(executionStyle);
4364
+ const batchCode = `
4365
+ import copy as _copy
4366
+ import json
4367
+ import math
4368
+ import sys
4369
+ import builtins as _builtins
4370
+ ${deps.PYTHON_CLASS_DEFINITIONS_SNIPPET}
4371
+ ${PYTHON_DEFAULT_IMPORT_PRELUDE}
4372
+ pow = _builtins.pow
4373
+
4374
+ ${deps.PYTHON_EXECUTE_SERIALIZE_FUNCTION_SNIPPET}
4375
+ ${deps.PYTHON_CONVERSION_HELPERS_SNIPPET}
4376
+
4377
+ _USER_CODE = ${JSON.stringify(code)}
4378
+ _FUNCTION_NAME = ${functionNameLiteral}
4379
+ _EXECUTION_STYLE = ${executionStyleLiteral}
4380
+ _INPUT_BATCH = ${deps.toPythonLiteral(cases)}
4381
+ _USER_CODE_OBJECT = compile(_USER_CODE, "solution.py", "exec")
4382
+ _INPLACE_RESULT_NAMES = ('nums1', 'nums', 'arr', 'array', 'matrix', 'board', 'grid')
4383
+
4384
+ def _tracecode_materialize_custom_input(obj, _env):
4385
+ if isinstance(obj, _builtins.list):
4386
+ return [_tracecode_materialize_custom_input(item, _env) for item in obj]
4387
+ if isinstance(obj, _builtins.tuple):
4388
+ return tuple(_tracecode_materialize_custom_input(item, _env) for item in obj)
4389
+ if isinstance(obj, _builtins.dict):
4390
+ if obj.get('__type__') == 'TreeNode' or 'left' in obj or 'right' in obj:
4391
+ return _dict_to_tree(obj)
4392
+ if obj.get('__type__') == 'ListNode' or 'next' in obj:
4393
+ return _dict_to_list(obj)
4394
+ _type_name = obj.get('__type__') if isinstance(obj.get('__type__'), _builtins.str) else obj.get('__class__')
4395
+ _fields = {key: _tracecode_materialize_custom_input(value, _env) for key, value in obj.items() if key not in ('__type__', '__class__', '__id__')}
4396
+ if isinstance(_type_name, _builtins.str):
4397
+ _fields = {'__type__': _type_name, **_fields}
4398
+ _constructor_fields = {key: value for key, value in _fields.items() if key not in ('__type__', '__class__')}
4399
+ _cls = _env.get(_type_name) if isinstance(_type_name, _builtins.str) else None
4400
+ if isinstance(_cls, _builtins.type):
4401
+ try:
4402
+ return _cls(**_constructor_fields)
4403
+ except Exception:
4404
+ pass
4405
+ try:
4406
+ return _cls(*_builtins.list(_constructor_fields.values()))
4407
+ except Exception:
4408
+ pass
4409
+ try:
4410
+ _instance = _cls.__new__(_cls)
4411
+ for _key, _value in _constructor_fields.items():
4412
+ setattr(_instance, _key, _value)
4413
+ return _instance
4414
+ except Exception:
4415
+ pass
4416
+ return _fields
4417
+ return obj
4418
+
4419
+ def _tracecode_hydrate_for_annotation(_obj, _annotation, _env):
4420
+ try:
4421
+ import typing as _tracecode_typing
4422
+ import collections.abc as _tracecode_collections_abc
4423
+ except Exception:
4424
+ return _obj
4425
+ if _annotation is None:
4426
+ return _obj
4427
+ if isinstance(_annotation, _builtins.str):
4428
+ _annotation = _env.get(_annotation, _annotation)
4429
+ if _annotation in (_builtins.object, getattr(_tracecode_typing, 'Any', None)):
4430
+ return _obj
4431
+ _origin = _tracecode_typing.get_origin(_annotation)
4432
+ _args = _tracecode_typing.get_args(_annotation)
4433
+ if _origin is _tracecode_typing.Union:
4434
+ _non_none = [_arg for _arg in _args if _arg is not type(None)]
4435
+ return _tracecode_hydrate_for_annotation(_obj, _non_none[0], _env) if len(_non_none) == 1 else _obj
4436
+ if _origin in (_builtins.list, _builtins.tuple, _builtins.set, _builtins.frozenset):
4437
+ _item_annotation = _args[0] if _args else None
4438
+ if isinstance(_obj, _builtins.list):
4439
+ _items = [_tracecode_hydrate_for_annotation(_item, _item_annotation, _env) for _item in _obj]
4440
+ if _origin is _builtins.tuple:
4441
+ return tuple(_items)
4442
+ if _origin is _builtins.set:
4443
+ return _builtins.set(_items)
4444
+ if _origin is _builtins.frozenset:
4445
+ return _builtins.frozenset(_items)
4446
+ return _items
4447
+ return _obj
4448
+ if _origin in (_builtins.dict, _tracecode_collections_abc.Mapping, _tracecode_collections_abc.MutableMapping) and isinstance(_obj, _builtins.dict):
4449
+ _key_annotation = _args[0] if len(_args) > 0 else None
4450
+ _value_annotation = _args[1] if len(_args) > 1 else None
4451
+ return {
4452
+ _tracecode_hydrate_for_annotation(_key, _key_annotation, _env): _tracecode_hydrate_for_annotation(_value, _value_annotation, _env)
4453
+ for _key, _value in _obj.items()
4454
+ }
4455
+ if isinstance(_annotation, _builtins.type) and isinstance(_obj, _builtins.dict):
4456
+ if _annotation.__name__ in ('TreeNode', 'ListNode'):
4457
+ return _obj
4458
+ _fields = {key: value for key, value in _obj.items() if key not in ('__type__', '__class__', '__id__')}
4459
+ try:
4460
+ _ctor_hints = _tracecode_typing.get_type_hints(getattr(_annotation, '__init__'), _env, _env)
4461
+ except Exception:
4462
+ _ctor_hints = {}
4463
+ _hydrated_fields = {
4464
+ key: _tracecode_hydrate_for_annotation(value, _ctor_hints.get(key), _env)
4465
+ for key, value in _fields.items()
4466
+ }
4467
+ try:
4468
+ return _annotation(**_hydrated_fields)
4469
+ except Exception:
4470
+ pass
4471
+ try:
4472
+ return _annotation(*_builtins.list(_hydrated_fields.values()))
4473
+ except Exception:
4474
+ pass
4475
+ try:
4476
+ _instance = _annotation.__new__(_annotation)
4477
+ for _key, _value in _hydrated_fields.items():
4478
+ setattr(_instance, _key, _value)
4479
+ return _instance
4480
+ except Exception:
4481
+ return _obj
4482
+ return _obj
4483
+
4484
+ def _tracecode_resolve_target_callable(_env):
4485
+ if _EXECUTION_STYLE == 'solution-method' and 'Solution' in _env and hasattr(_env['Solution'], _FUNCTION_NAME):
4486
+ return getattr(_env['Solution'], _FUNCTION_NAME)
4487
+ if _FUNCTION_NAME in _env and callable(_env[_FUNCTION_NAME]):
4488
+ return _env[_FUNCTION_NAME]
4489
+ if 'Solution' in _env and hasattr(_env['Solution'], _FUNCTION_NAME):
4490
+ return getattr(_env['Solution'], _FUNCTION_NAME)
4491
+ return None
4492
+
4493
+ def _tracecode_hydrate_annotated_inputs(_env, _input_names):
4494
+ try:
4495
+ import typing as _tracecode_typing
4496
+ _callable = _tracecode_resolve_target_callable(_env)
4497
+ if _callable is None:
4498
+ return
4499
+ try:
4500
+ _annotations = _tracecode_typing.get_type_hints(_callable, _env, _env)
4501
+ except Exception:
4502
+ _annotations = getattr(_callable, '__annotations__', {}) or {}
4503
+ for _name in _input_names:
4504
+ if _name in _env and _name in _annotations:
4505
+ _env[_name] = _tracecode_hydrate_for_annotation(_env[_name], _annotations[_name], _env)
4506
+ except Exception:
4507
+ return
4508
+
4509
+ def _tracecode_resolve_entry_callable(_env):
4510
+ if _EXECUTION_STYLE == 'solution-method' and 'Solution' in _env and hasattr(_env['Solution'], _FUNCTION_NAME):
4511
+ return getattr(_env['Solution'](), _FUNCTION_NAME)
4512
+ if _FUNCTION_NAME in _env and callable(_env[_FUNCTION_NAME]):
4513
+ return _env[_FUNCTION_NAME]
4514
+ if 'Solution' in _env and hasattr(_env['Solution'], _FUNCTION_NAME):
4515
+ return getattr(_env['Solution'](), _FUNCTION_NAME)
4516
+ return None
4517
+
4518
+ def _tracecode_invoke_entry(_env, _input_names):
4519
+ import inspect as _tracecode_inspect
4520
+ _callable = _tracecode_resolve_entry_callable(_env)
4521
+ if _callable is None:
4522
+ raise NameError(f"Implement {_FUNCTION_NAME}(...) or Solution.{_FUNCTION_NAME}(...)")
4523
+ _values = {_name: _env[_name] for _name in _input_names if _name in _env}
4524
+ try:
4525
+ _signature = _tracecode_inspect.signature(_callable)
4526
+ except Exception:
4527
+ return _callable(**_values)
4528
+ _args = []
4529
+ _kwargs = {}
4530
+ _has_varargs = any(
4531
+ _parameter.kind is _tracecode_inspect.Parameter.VAR_POSITIONAL
4532
+ for _parameter in _signature.parameters.values()
4533
+ )
4534
+ for _parameter in _signature.parameters.values():
4535
+ if _parameter.name in ('self', 'cls'):
4536
+ continue
4537
+ _kind = _parameter.kind
4538
+ if _kind is _tracecode_inspect.Parameter.VAR_POSITIONAL:
4539
+ if _parameter.name in _values:
4540
+ _raw = _values[_parameter.name]
4541
+ if isinstance(_raw, (_builtins.list, _builtins.tuple)):
4542
+ _args.extend(_raw)
4543
+ else:
4544
+ _args.append(_raw)
4545
+ continue
4546
+ if _kind is _tracecode_inspect.Parameter.VAR_KEYWORD:
4547
+ if _parameter.name in _values and isinstance(_values[_parameter.name], _builtins.dict):
4548
+ _kwargs.update(_values[_parameter.name])
4549
+ continue
4550
+ if _parameter.name not in _values:
4551
+ continue
4552
+ if _kind is _tracecode_inspect.Parameter.POSITIONAL_ONLY:
4553
+ _args.append(_values[_parameter.name])
4554
+ elif _kind is _tracecode_inspect.Parameter.POSITIONAL_OR_KEYWORD and _has_varargs:
4555
+ _args.append(_values[_parameter.name])
4556
+ else:
4557
+ _kwargs[_parameter.name] = _values[_parameter.name]
4558
+ return _callable(*_args, **_kwargs)
4559
+
4560
+ def _tracecode_run_ops_class(_env):
4561
+ _ops = _env.get('operations', _env.get('ops'))
4562
+ _args = _env.get('arguments', _env.get('args'))
4563
+ if _ops is None or _args is None:
4564
+ raise ValueError("ops-class execution requires inputs.operations and inputs.arguments (or ops/args)")
4565
+ if len(_ops) != len(_args):
4566
+ raise ValueError("operations and arguments must have the same length")
4567
+ _cls = _env[_FUNCTION_NAME]
4568
+ _instance = None
4569
+ _out = []
4570
+ for _i, _op in enumerate(_ops):
4571
+ _call_args = _args[_i] if _i < len(_args) else []
4572
+ if _call_args is None:
4573
+ _call_args = []
4574
+ if not isinstance(_call_args, (_builtins.list, _builtins.tuple)):
4575
+ _call_args = [_call_args]
4576
+ if _i == 0:
4577
+ _instance = _cls(*_call_args)
4578
+ _out.append(None)
4579
+ else:
4580
+ if not hasattr(_instance, _op):
4581
+ raise AttributeError(f"Required method '{_op}' is not implemented on {_cls.__name__}")
4582
+ _method = getattr(_instance, _op)
4583
+ _out.append(_method(*_call_args))
4584
+ return _out
4585
+
4586
+ _TRACE_CODE_CASE_BASE_NAMES = tuple(
4587
+ name for name in globals()
4588
+ if name not in ('_USER_CODE', '_FUNCTION_NAME', '_EXECUTION_STYLE', '_INPUT_BATCH', '_USER_CODE_OBJECT')
4589
+ )
4590
+
4591
+ def _tracecode_base_env():
4592
+ _env = {name: globals()[name] for name in _TRACE_CODE_CASE_BASE_NAMES if name in globals()}
4593
+ _env['__builtins__'] = _builtins
4594
+ _env['__name__'] = '__main__'
4595
+ _env['print'] = None
4596
+ return _env
4597
+
4598
+ def _tracecode_run_case(_raw_inputs):
4599
+ _console_output = []
4600
+ def _custom_print(*args, **kwargs):
4601
+ _console_output.append(" ".join(str(arg) for arg in args))
4602
+ _env = _tracecode_base_env()
4603
+ _env['print'] = _custom_print
4604
+ try:
4605
+ _raw_input_items = _raw_inputs.items() if isinstance(_raw_inputs, _builtins.dict) else []
4606
+ if not isinstance(_FUNCTION_NAME, _builtins.str) or not _FUNCTION_NAME:
4607
+ _inputs = {
4608
+ str(_key): _tracecode_materialize_custom_input(_copy.deepcopy(_value), _env)
4609
+ for _key, _value in _raw_input_items
4610
+ }
4611
+ _env.update(_inputs)
4612
+ exec(_USER_CODE_OBJECT, _env)
4613
+ else:
4614
+ exec(_USER_CODE_OBJECT, _env)
4615
+ _inputs = {
4616
+ str(_key): _tracecode_materialize_custom_input(_copy.deepcopy(_value), _env)
4617
+ for _key, _value in _raw_input_items
4618
+ }
4619
+ _env.update(_inputs)
4620
+ _input_names = list(_inputs.keys())
4621
+ _tracecode_hydrate_annotated_inputs(_env, _input_names)
4622
+ if not isinstance(_FUNCTION_NAME, _builtins.str) or not _FUNCTION_NAME:
4623
+ _result = _env.get('result')
4624
+ elif _EXECUTION_STYLE == 'ops-class':
4625
+ _result = _tracecode_run_ops_class(_env)
4626
+ else:
4627
+ _result = _tracecode_invoke_entry(_env, _input_names)
4628
+ if _result is None:
4629
+ for _name in _INPLACE_RESULT_NAMES:
4630
+ if _name in _env:
4631
+ _result = _env[_name]
4632
+ break
4633
+ return {'success': True, 'output': _serialize(_result), 'consoleOutput': _console_output}
4634
+ except Exception as _error:
4635
+ return {'success': False, 'output': None, 'error': str(_error), 'consoleOutput': _console_output}
4636
+
4637
+ _results = [_tracecode_run_case(_case) for _case in _INPUT_BATCH]
4638
+ json.dumps({
4639
+ 'success': all(_result.get('success') is True for _result in _results),
4640
+ 'results': _results,
4641
+ 'consoleOutput': [line for _result in _results for line in _result.get('consoleOutput', [])],
4642
+ })
4643
+ `;
4644
+
4645
+ const resultJson = await deps.getPyodide().runPythonAsync(batchCode);
4646
+ const parsed = JSON.parse(resultJson);
4647
+ return {
4648
+ success: parsed.success === true,
4649
+ results: Array.isArray(parsed.results) ? parsed.results : [],
4650
+ consoleOutput: Array.isArray(parsed.consoleOutput) ? parsed.consoleOutput : [],
4651
+ timings: { totalMs: deps.performanceNow() - startedAt },
4652
+ };
4653
+ } catch (error) {
4654
+ const rawError = error instanceof Error ? error.message : String(error);
4655
+ const { message, line } = parsePythonError(rawError, 1, userCodeLineCount);
4656
+ return {
4657
+ success: false,
4658
+ results: cases.map(() => ({
4659
+ success: false,
4660
+ output: null,
4661
+ error: message,
4662
+ errorLine: line,
4663
+ consoleOutput: [],
4664
+ })),
4665
+ error: message,
4666
+ consoleOutput: [],
4667
+ timings: { totalMs: deps.performanceNow() - startedAt },
4668
+ };
4669
+ }
4670
+ }
4671
+
4342
4672
  globalScope.__TRACECODE_PYODIDE_RUNTIME__ = {
4343
4673
  generateTracingCode,
4344
4674
  parsePythonError,
4345
4675
  executeWithTracing,
4346
4676
  executeCode,
4677
+ executeCodeBatch,
4347
4678
  };
4348
4679
  })(typeof self !== 'undefined' ? self : globalThis);