maka-agent 0.2.0-dev.4.20260830 → 0.2.0-dev.5.20260830

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/dist/eval-bundle-path.js +1 -1
  2. package/dist/pi-tui-runner.js +3 -1
  3. package/dist/runtime-host-onboarding.js +10 -1
  4. package/dist/runtime-host-tui-context.js +2 -6
  5. package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
  6. package/node_modules/@maka/eval/dist/external-subject.js +1 -1
  7. package/node_modules/@maka/eval/dist/harbor-external-subject.js +2 -2
  8. package/package.json +1 -2
  9. package/packages/eval/dist/attempt-store.js +0 -138
  10. package/packages/eval/dist/cli.js +0 -119
  11. package/packages/eval/dist/experiment-directory.js +0 -59
  12. package/packages/eval/dist/experiment.js +0 -49
  13. package/packages/eval/dist/external-subject.js +0 -488
  14. package/packages/eval/dist/harbor-external-subject.js +0 -929
  15. package/packages/eval/dist/harbor-maka-subject.js +0 -83
  16. package/packages/eval/dist/harness-environment.js +0 -80
  17. package/packages/eval/dist/harness-executor.js +0 -1013
  18. package/packages/eval/dist/index.js +0 -28
  19. package/packages/eval/dist/install-preflight.js +0 -201
  20. package/packages/eval/dist/maka-artifacts.js +0 -126
  21. package/packages/eval/dist/maka-runtime-policy.js +0 -42
  22. package/packages/eval/dist/maka-subject.js +0 -268
  23. package/packages/eval/dist/metering-checkpoint.js +0 -80
  24. package/packages/eval/dist/provider-admission.js +0 -39
  25. package/packages/eval/dist/provider-metering.js +0 -67
  26. package/packages/eval/dist/provider-web-tool-surface.js +0 -74
  27. package/packages/eval/dist/relay-result-frame.js +0 -38
  28. package/packages/eval/dist/result.js +0 -94
  29. package/packages/eval/dist/runner.js +0 -290
  30. package/packages/eval/dist/spec.js +0 -135
  31. package/packages/eval/dist/toolchain-verification.js +0 -130
  32. package/packages/eval/harbor/deepseek-codex-models.json +0 -69
  33. package/packages/eval/harbor/deepseek-harness-profile/cordis.patch.yml +0 -140
  34. package/packages/eval/harbor/deepseek-harness-profile/cordis.yml +0 -18
  35. package/packages/eval/harbor/deepseek-harness-profile/package.json +0 -10
  36. package/packages/eval/harbor/docker-compose-egress-proxy.yaml +0 -57
  37. package/packages/eval/harbor/egress-proxy/Dockerfile +0 -25
  38. package/packages/eval/harbor/egress-proxy/entrypoint.sh +0 -62
  39. package/packages/eval/harbor/egress-proxy/network-policy +0 -148
  40. package/packages/eval/harbor/egress_filter.py +0 -435
  41. package/packages/eval/harbor/eval_framework.py +0 -40
  42. package/packages/eval/harbor/relay_agent.py +0 -686
  43. package/packages/eval/harbor/run_trial.py +0 -223
  44. package/packages/eval/package.json +0 -20
@@ -1,80 +0,0 @@
1
- /*
2
- * Licensed to the Apache Software Foundation (ASF) under one
3
- * or more contributor license agreements. See the NOTICE file
4
- * distributed with this work for additional information
5
- * regarding copyright ownership. The ASF licenses this file
6
- * to you under the Apache License, Version 2.0 (the
7
- * "License"); you may not use this file except in compliance
8
- * with the License. You may obtain a copy of the License at
9
- *
10
- * http://www.apache.org/licenses/LICENSE-2.0
11
- *
12
- * Unless required by applicable law or agreed to in writing,
13
- * software distributed under the License is distributed on an
14
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
- * KIND, either express or implied. See the License for the
16
- * specific language governing permissions and limitations
17
- * under the License.
18
- */
19
- import { createHmac, timingSafeEqual } from 'node:crypto';
20
- import { constants } from 'node:fs';
21
- import { chmod, open, rename, writeFile } from 'node:fs/promises';
22
- // One JSON snapshot of proxy counts. Anything larger is not a checkpoint the
23
- // wrapper writes; it is treated as absent rather than parsed.
24
- export const METERING_CHECKPOINT_LIMIT_BYTES = 64 * 1024;
25
- const MAC_PATTERN = /^[0-9a-f]{64}$/u;
26
- let atomicWriteSequence = 0;
27
- // The checkpoint is read by a different process after this one may have died
28
- // mid-write, so it is renamed into place rather than written in place: a reader
29
- // sees either the previous snapshot or the next one, never a truncated file.
30
- // /logs/agent is created under umask 077, which silently downgrades the
31
- // writeFile mode to 0600. chmod the temporary file before rename so the
32
- // published inode is already 0644; a host that races the rename must not see
33
- // a 0600 file it cannot read.
34
- export async function writeJsonAtomic(path, value) {
35
- const temporary = `${path}.tmp-${process.pid}-${atomicWriteSequence++}`;
36
- await writeFile(temporary, `${JSON.stringify(value)}\n`, { mode: 0o644 });
37
- await chmod(temporary, 0o644);
38
- await rename(temporary, path);
39
- }
40
- // The subject can replace the checkpoint path. Refuse anything that is not a
41
- // bounded regular file so a symlink or a huge write cannot be used as the
42
- // host's usage evidence.
43
- export async function readBoundedRegularFile(path, limitBytes = METERING_CHECKPOINT_LIMIT_BYTES) {
44
- let handle;
45
- try {
46
- handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
47
- }
48
- catch {
49
- return undefined;
50
- }
51
- try {
52
- const info = await handle.stat();
53
- if (!info.isFile() || info.size <= 0 || info.size > limitBytes)
54
- return undefined;
55
- const bytes = Buffer.alloc(info.size);
56
- const { bytesRead } = await handle.read(bytes, 0, info.size, 0);
57
- if (bytesRead !== info.size)
58
- return undefined;
59
- return bytes;
60
- }
61
- finally {
62
- await handle.close();
63
- }
64
- }
65
- // The wrapper signs with the relay result token, which is host-issued and
66
- // stripped from the wrapper environment before the subject starts. A subject
67
- // that writes a schema-valid file cannot produce this mac.
68
- export function signMeteringCheckpoint(body, secret) {
69
- return { ...body, mac: createHmac('sha256', secret).update(JSON.stringify(body)).digest('hex') };
70
- }
71
- export function meteringCheckpointMacMatches(record, secret) {
72
- const mac = record.mac;
73
- if (typeof mac !== 'string' || !MAC_PATTERN.test(mac))
74
- return false;
75
- const body = { ...record };
76
- delete body.mac;
77
- const expected = Buffer.from(createHmac('sha256', secret).update(JSON.stringify(body)).digest('hex'), 'hex');
78
- const actual = Buffer.from(mac, 'hex');
79
- return expected.length === actual.length && timingSafeEqual(expected, actual);
80
- }
@@ -1,39 +0,0 @@
1
- /*
2
- * Licensed to the Apache Software Foundation (ASF) under one
3
- * or more contributor license agreements. See the NOTICE file
4
- * distributed with this work for additional information
5
- * regarding copyright ownership. The ASF licenses this file
6
- * to you under the Apache License, Version 2.0 (the
7
- * "License"); you may not use this file except in compliance
8
- * with the License. You may obtain a copy of the License at
9
- *
10
- * http://www.apache.org/licenses/LICENSE-2.0
11
- *
12
- * Unless required by applicable law or agreed to in writing,
13
- * software distributed under the License is distributed on an
14
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
- * KIND, either express or implied. See the License for the
16
- * specific language governing permissions and limitations
17
- * under the License.
18
- */
19
- export function isInferenceAdmissionEvent(event, anthropic) {
20
- if (anthropic && event.type === 'message_start' && isRecord(event.message))
21
- return true;
22
- if (typeof event.type === 'string' &&
23
- (event.type === 'response.created' ||
24
- event.type === 'response.in_progress' ||
25
- event.type === 'response.output_item.added' ||
26
- event.type === 'response.completed')) {
27
- return true;
28
- }
29
- if (Array.isArray(event.choices))
30
- return true;
31
- for (const candidate of [event, event.response, event.message]) {
32
- if (isRecord(candidate) && isRecord(candidate.usage))
33
- return true;
34
- }
35
- return false;
36
- }
37
- function isRecord(value) {
38
- return value !== null && typeof value === 'object' && !Array.isArray(value);
39
- }
@@ -1,67 +0,0 @@
1
- /*
2
- * Licensed to the Apache Software Foundation (ASF) under one
3
- * or more contributor license agreements. See the NOTICE file
4
- * distributed with this work for additional information
5
- * regarding copyright ownership. The ASF licenses this file
6
- * to you under the Apache License, Version 2.0 (the
7
- * "License"); you may not use this file except in compliance
8
- * with the License. You may obtain a copy of the License at
9
- *
10
- * http://www.apache.org/licenses/LICENSE-2.0
11
- *
12
- * Unless required by applicable law or agreed to in writing,
13
- * software distributed under the License is distributed on an
14
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
- * KIND, either express or implied. See the License for the
16
- * specific language governing permissions and limitations
17
- * under the License.
18
- */
19
- // Cost is reported only for a complete figure. A partial token count is still
20
- // worth recording as a lower bound, but a cost derived from it would enter the
21
- // result kernel as a number indistinguishable from a settled one.
22
- export function deriveMetering(counts) {
23
- const usageComplete = counts.settled &&
24
- counts.admittedRequests > 0 &&
25
- counts.usageRequests === counts.admittedRequests;
26
- return {
27
- settledRequests: counts.requests - counts.inFlightRequests,
28
- missingUsageRequests: counts.admittedRequests - counts.usageRequests,
29
- usageComplete,
30
- costUsd: counts.usage && usageComplete ? deepSeekCostUsd(counts.usage) : null,
31
- tokenBasis: usageComplete ? 'complete' : 'lower-bound',
32
- };
33
- }
34
- // Published DeepSeek V4 Flash prices, in USD per million tokens. Eval bills
35
- // every arm from this table rather than from each arm's own transcription of
36
- // it, so a difference in reported cost can only come from a difference in what
37
- // the agent spent. Pi is additionally handed the table for its own display; the
38
- // other frameworks are given no cost block at all, which is why their figures
39
- // are not consulted here.
40
- //
41
- // Rates as published 2026-08-17, normalized at the off-peak band: DeepSeek
42
- // bills peak hours (01:00–04:00 and 06:00–10:00 UTC) at exactly double these,
43
- // and the checkpoint records no request timestamps to split by.
44
- //
45
- // DeepSeek bills input in exactly two kinds, cache hit and cache miss, and
46
- // charges nothing for writing the cache — a written token is a miss token. So
47
- // `cacheWrite` is the miss rate rather than a rate of its own: it exists to
48
- // keep a provider that reports cache writes separately from being billed at
49
- // zero, and for DeepSeek itself it never applies, because the API reports only
50
- // `prompt_cache_hit_tokens` and `prompt_cache_miss_tokens`.
51
- export const DEEPSEEK_V4_FLASH_COST = {
52
- input: 0.22,
53
- output: 0.66,
54
- cacheRead: 0.007,
55
- cacheWrite: 0.22,
56
- };
57
- // `inputTokens` is normalized to include both cached kinds, so each kind is
58
- // subtracted out and charged at its own rate. Leaving cache writes subtracted
59
- // but unpriced billed them at zero.
60
- export function deepSeekCostUsd(usage) {
61
- const uncached = Math.max(0, usage.inputTokens - usage.cacheReadTokens - usage.cacheWriteTokens);
62
- return ((uncached * DEEPSEEK_V4_FLASH_COST.input +
63
- usage.cacheReadTokens * DEEPSEEK_V4_FLASH_COST.cacheRead +
64
- usage.cacheWriteTokens * DEEPSEEK_V4_FLASH_COST.cacheWrite +
65
- usage.outputTokens * DEEPSEEK_V4_FLASH_COST.output) /
66
- 1_000_000);
67
- }
@@ -1,74 +0,0 @@
1
- /*
2
- * Licensed to the Apache Software Foundation (ASF) under one
3
- * or more contributor license agreements. See the NOTICE file
4
- * distributed with this work for additional information
5
- * regarding copyright ownership. The ASF licenses this file
6
- * to you under the Apache License, Version 2.0 (the
7
- * "License"); you may not use this file except in compliance
8
- * with the License. You may obtain a copy of the License at
9
- *
10
- * http://www.apache.org/licenses/LICENSE-2.0
11
- *
12
- * Unless required by applicable law or agreed to in writing,
13
- * software distributed under the License is distributed on an
14
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
- * KIND, either express or implied. See the License for the
16
- * specific language governing permissions and limitations
17
- * under the License.
18
- */
19
- const DISALLOWED_WEB_TOOL_NAMES = new Set(['websearch', 'webfetch', 'fetchurl']);
20
- export function removeEvalWebTools(body) {
21
- if (body.length === 0)
22
- return { body, removed: 0, toolNames: [] };
23
- let value;
24
- try {
25
- value = JSON.parse(body.toString('utf8'));
26
- }
27
- catch {
28
- return { body, removed: 0, toolNames: [] };
29
- }
30
- if (!isRecord(value))
31
- return { body, removed: 0, toolNames: [] };
32
- const model = typeof value.model === 'string' ? value.model : undefined;
33
- if (!Array.isArray(value.tools))
34
- return { body, removed: 0, toolNames: [], ...(model ? { model } : {}) };
35
- const tools = value.tools.filter((tool) => !isDisallowedWebTool(tool));
36
- const removed = value.tools.length - tools.length;
37
- const toolNames = tools.flatMap(toolName);
38
- if (removed === 0)
39
- return { body, removed: 0, toolNames, ...(model ? { model } : {}) };
40
- return {
41
- body: Buffer.from(JSON.stringify({ ...value, tools }), 'utf8'),
42
- removed,
43
- toolNames,
44
- ...(model ? { model } : {}),
45
- };
46
- }
47
- function isDisallowedWebTool(tool) {
48
- if (!isRecord(tool))
49
- return false;
50
- const names = [tool.name, isRecord(tool.function) ? tool.function.name : undefined];
51
- if (names.some((name) => typeof name === 'string' && DISALLOWED_WEB_TOOL_NAMES.has(normalize(name)))) {
52
- return true;
53
- }
54
- if (typeof tool.type !== 'string')
55
- return false;
56
- const type = normalize(tool.type);
57
- return [...DISALLOWED_WEB_TOOL_NAMES].some((name) => type === name || type.startsWith(name));
58
- }
59
- function normalize(value) {
60
- return value.toLowerCase().replace(/[^a-z0-9]/gu, '');
61
- }
62
- function toolName(tool) {
63
- if (!isRecord(tool))
64
- return [];
65
- if (typeof tool.name === 'string')
66
- return [tool.name];
67
- if (isRecord(tool.function) && typeof tool.function.name === 'string') {
68
- return [tool.function.name];
69
- }
70
- return typeof tool.type === 'string' ? [tool.type] : [];
71
- }
72
- function isRecord(value) {
73
- return value !== null && typeof value === 'object' && !Array.isArray(value);
74
- }
@@ -1,38 +0,0 @@
1
- /*
2
- * Licensed to the Apache Software Foundation (ASF) under one
3
- * or more contributor license agreements. See the NOTICE file
4
- * distributed with this work for additional information
5
- * regarding copyright ownership. The ASF licenses this file
6
- * to you under the Apache License, Version 2.0 (the
7
- * "License"); you may not use this file except in compliance
8
- * with the License. You may obtain a copy of the License at
9
- *
10
- * http://www.apache.org/licenses/LICENSE-2.0
11
- *
12
- * Unless required by applicable law or agreed to in writing,
13
- * software distributed under the License is distributed on an
14
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
- * KIND, either express or implied. See the License for the
16
- * specific language governing permissions and limitations
17
- * under the License.
18
- */
19
- import { createHash } from 'node:crypto';
20
- // Keep the complete record below Linux PIPE_BUF so merged Docker stdout/stderr
21
- // cannot interleave inside an otherwise valid result frame.
22
- const RESULT_PAYLOAD_LIMIT_BYTES = 2 * 1024;
23
- const TOKEN_PATTERN = /^[0-9a-f]{32}$/u;
24
- export function takeRelayResultToken() {
25
- const token = process.env.MAKA_EVAL_RESULT_TOKEN;
26
- delete process.env.MAKA_EVAL_RESULT_TOKEN;
27
- if (!token || !TOKEN_PATTERN.test(token))
28
- throw new Error('relay result token is invalid');
29
- return token;
30
- }
31
- export function writeRelayResult(token, value) {
32
- const payload = Buffer.from(JSON.stringify(value));
33
- if (payload.byteLength > RESULT_PAYLOAD_LIMIT_BYTES) {
34
- throw new Error('relay result exceeds the payload limit');
35
- }
36
- const digest = createHash('sha256').update(payload).digest('hex');
37
- process.stdout.write(`MAKA-EVAL-RESULT-V1 ${token} ${payload.byteLength} ${digest} ${payload.toString('base64url')}\n`);
38
- }
@@ -1,94 +0,0 @@
1
- /*
2
- * Licensed to the Apache Software Foundation (ASF) under one
3
- * or more contributor license agreements. See the NOTICE file
4
- * distributed with this work for additional information
5
- * regarding copyright ownership. The ASF licenses this file
6
- * to you under the Apache License, Version 2.0 (the
7
- * "License"); you may not use this file except in compliance
8
- * with the License. You may obtain a copy of the License at
9
- *
10
- * http://www.apache.org/licenses/LICENSE-2.0
11
- *
12
- * Unless required by applicable law or agreed to in writing,
13
- * software distributed under the License is distributed on an
14
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
- * KIND, either express or implied. See the License for the
16
- * specific language governing permissions and limitations
17
- * under the License.
18
- */
19
- import { decodeJsonObject } from './experiment.js';
20
- export function decodeEvalResult(value, where = 'result') {
21
- const result = exact(value, where, [
22
- 'score',
23
- 'usage',
24
- 'costUsd',
25
- 'durationMs',
26
- 'status',
27
- 'failureReason',
28
- 'artifacts',
29
- ]);
30
- if (!['completed', 'subject_failed', 'infra_failed', 'indeterminate'].includes(String(result.status))) {
31
- throw new Error(`${where}.status is invalid`);
32
- }
33
- if (result.failureReason !== null && typeof result.failureReason !== 'string') {
34
- throw new Error(`${where}.failureReason is invalid`);
35
- }
36
- if (!Array.isArray(result.artifacts)) {
37
- throw new Error(`${where}.artifacts is invalid`);
38
- }
39
- return {
40
- score: nullableNumber(result.score, `${where}.score`),
41
- usage: result.usage === null ? null : decodeUsage(result.usage, `${where}.usage`),
42
- costUsd: nullableNonnegative(result.costUsd, `${where}.costUsd`),
43
- durationMs: nonnegative(result.durationMs, `${where}.durationMs`),
44
- status: result.status,
45
- failureReason: result.failureReason,
46
- artifacts: result.artifacts.map((artifact, index) => decodeJsonObject(artifact, `${where}.artifacts[${index}]`)),
47
- };
48
- }
49
- export function isReplaceableAttempt(attempt) {
50
- return attempt.result.status === 'infra_failed' || attempt.result.status === 'indeterminate';
51
- }
52
- export function selectCellResult(attempts) {
53
- return [...attempts]
54
- .sort((left, right) => left.sequence - right.sequence)
55
- .find((attempt) => !isReplaceableAttempt(attempt));
56
- }
57
- function decodeUsage(value, where) {
58
- const usage = exact(value, where, [
59
- 'inputTokens',
60
- 'outputTokens',
61
- 'cacheReadTokens',
62
- 'cacheWriteTokens',
63
- 'reasoningTokens',
64
- 'totalTokens',
65
- ]);
66
- return Object.fromEntries(Object.entries(usage).map(([key, item]) => [key, nonnegative(item, `${where}.${key}`)]));
67
- }
68
- function exact(value, where, fields) {
69
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
70
- throw new Error(`${where} must be an object`);
71
- }
72
- const record = value;
73
- if (Object.keys(record).length !== fields.length ||
74
- fields.some((field) => !Object.hasOwn(record, field))) {
75
- throw new Error(`${where} fields are invalid`);
76
- }
77
- return record;
78
- }
79
- function nonnegative(value, where) {
80
- if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
81
- throw new Error(`${where} is invalid`);
82
- }
83
- return value;
84
- }
85
- function nullableNumber(value, where) {
86
- if (value === null)
87
- return null;
88
- if (typeof value !== 'number' || !Number.isFinite(value))
89
- throw new Error(`${where} is invalid`);
90
- return value;
91
- }
92
- function nullableNonnegative(value, where) {
93
- return value === null ? null : nonnegative(value, where);
94
- }
@@ -1,290 +0,0 @@
1
- /*
2
- * Licensed to the Apache Software Foundation (ASF) under one
3
- * or more contributor license agreements. See the NOTICE file
4
- * distributed with this work for additional information
5
- * regarding copyright ownership. The ASF licenses this file
6
- * to you under the Apache License, Version 2.0 (the
7
- * "License"); you may not use this file except in compliance
8
- * with the License. You may obtain a copy of the License at
9
- *
10
- * http://www.apache.org/licenses/LICENSE-2.0
11
- *
12
- * Unless required by applicable law or agreed to in writing,
13
- * software distributed under the License is distributed on an
14
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
- * KIND, either express or implied. See the License for the
16
- * specific language governing permissions and limitations
17
- * under the License.
18
- */
19
- import { expandExperiment, } from './experiment.js';
20
- import { decodeEvalResult, isReplaceableAttempt, } from './result.js';
21
- export async function runExperiment(input) {
22
- return input.store.runExclusive(async () => {
23
- const cells = expandExperiment(input.spec);
24
- const selected = selectCells(cells, input.cellIds);
25
- const subjects = new Map(input.subjects.map((subject) => [subject.kind, subject]));
26
- const subjectCredentialNames = [
27
- ...new Set(input.spec.subjects.flatMap((subject) => subject.credentials)),
28
- ];
29
- if (input.executor.kind !== input.spec.executor.kind)
30
- throw new Error('executor kind mismatch');
31
- for (const cell of selected) {
32
- input.executor.validate?.(cell);
33
- const subject = subjects.get(cell.subject.kind);
34
- if (!subject)
35
- throw new Error(`missing subject adapter: ${cell.subject.kind}`);
36
- subject.validate?.(cell);
37
- }
38
- for (const subject of subjects.values()) {
39
- const cellsForSubject = selected.filter((cell) => cell.subject.kind === subject.kind);
40
- if (cellsForSubject.length > 0) {
41
- await subject.prepare?.({ spec: input.spec, cells: cellsForSubject });
42
- }
43
- }
44
- await runTaskGroups(groupTaskCells(selected), input.spec.execution.maxConcurrentTaskGroups, input.signal, async (group, fail) => {
45
- if (input.signal?.aborted)
46
- return;
47
- const operations = group.map(async (cell) => {
48
- if (input.signal?.aborted)
49
- return;
50
- const attempts = await input.store.list(cell.id);
51
- if (input.signal?.aborted)
52
- return;
53
- const subject = subjects.get(cell.subject.kind);
54
- if (selectSubjectResult(attempts, subject, cell))
55
- return;
56
- const startedAt = (input.now ?? Date.now)();
57
- const result = await executeCell(input.executor, subject, cell, subjectCredentialNames, input.signal);
58
- await input.store.append({
59
- cellId: cell.id,
60
- sequence: (attempts.at(-1)?.sequence ?? 0) + 1,
61
- startedAt,
62
- completedAt: (input.now ?? Date.now)(),
63
- result,
64
- });
65
- });
66
- await Promise.allSettled(operations.map((operation) => operation.catch((error) => {
67
- fail(error);
68
- throw error;
69
- })));
70
- });
71
- return new Map((await Promise.all(cells.map(async (cell) => [
72
- cell.id,
73
- selectSubjectResult(await input.store.list(cell.id), subjects.get(cell.subject.kind), cell),
74
- ]))).flatMap(([cellId, result]) => (result ? [[cellId, result]] : [])));
75
- });
76
- }
77
- function selectSubjectResult(attempts, subject, cell) {
78
- return [...attempts]
79
- .sort((left, right) => left.sequence - right.sequence)
80
- .find((attempt) => !isReplaceableAttempt(attempt) && (subject.canReuse?.({ cell, attempt }) ?? true));
81
- }
82
- function groupTaskCells(cells) {
83
- const groups = new Map();
84
- for (const cell of cells) {
85
- const key = `${cell.task.id}\u0000${cell.repetition}`;
86
- const group = groups.get(key);
87
- if (group)
88
- group.push(cell);
89
- else
90
- groups.set(key, [cell]);
91
- }
92
- return [...groups.values()];
93
- }
94
- async function runTaskGroups(groups, maximum, signal, run) {
95
- let next = 0;
96
- let failed = false;
97
- let failure;
98
- const fail = (error) => {
99
- if (failed)
100
- return;
101
- failed = true;
102
- failure = error;
103
- };
104
- const worker = async () => {
105
- for (;;) {
106
- if (failed || signal?.aborted)
107
- return;
108
- const group = groups[next];
109
- next += 1;
110
- if (group === undefined)
111
- return;
112
- try {
113
- await run(group, fail);
114
- }
115
- catch (error) {
116
- fail(error);
117
- }
118
- }
119
- };
120
- await Promise.all(Array.from({ length: Math.min(maximum, groups.length) }, async () => await worker()));
121
- if (failed)
122
- throw failure;
123
- }
124
- async function executeCell(executor, subject, cell, subjectCredentialNames, signal) {
125
- try {
126
- const attempt = await executor.runAttempt({ cell, subjectCredentialNames, ...(signal ? { signal } : {}) }, async ({ context, verify }) => {
127
- let execution;
128
- try {
129
- execution = decodeSubjectExecution(await subject.execute({
130
- cell,
131
- context: { ...context, ...(signal ? { signal } : {}) },
132
- }));
133
- }
134
- catch {
135
- return failure('infra_failed', 'subject execution failed');
136
- }
137
- if (signal?.aborted ||
138
- execution.status === 'infra_failed' ||
139
- execution.status === 'indeterminate') {
140
- return fromUncertainSubject(execution, signal?.aborted === true);
141
- }
142
- try {
143
- const verified = decodeVerification(await verify());
144
- return {
145
- score: verified.score,
146
- usage: execution.usage,
147
- costUsd: execution.costUsd,
148
- durationMs: execution.durationMs,
149
- status: settledStatus(execution.status, verified.status),
150
- failureReason: verified.status === 'infra_failed'
151
- ? verified.failureReason
152
- : (execution.failureReason ?? verified.failureReason),
153
- artifacts: [...execution.artifacts, ...verified.artifacts],
154
- };
155
- }
156
- catch {
157
- return failure('infra_failed', 'verification failed', execution);
158
- }
159
- });
160
- if (attempt.kind === 'not_started') {
161
- const cancelled = attempt.code === 'cancelled';
162
- return failure(cancelled ? 'indeterminate' : 'infra_failed', cancelled ? 'executor preparation cancelled' : 'executor preparation failed', undefined, attempt.artifacts);
163
- }
164
- if (attempt.kind === 'settled')
165
- return decodeEvalResult(attempt.value);
166
- const failureReason = attempt.cause === 'host-cancelled'
167
- ? 'executor cancelled before verification completed'
168
- : 'executor cleanup did not settle';
169
- if (!attempt.value)
170
- return failure('indeterminate', failureReason);
171
- const partial = decodeEvalResult(attempt.value);
172
- return {
173
- ...partial,
174
- score: null,
175
- status: 'indeterminate',
176
- failureReason,
177
- };
178
- }
179
- catch {
180
- return failure('infra_failed', 'executor preparation failed');
181
- }
182
- }
183
- function decodeSubjectExecution(value) {
184
- const subject = exactRecord(value, ['usage', 'costUsd', 'durationMs', 'status', 'failureReason', 'artifacts'], ['output']);
185
- if (subject.status !== 'completed' &&
186
- subject.status !== 'failed' &&
187
- subject.status !== 'infra_failed' &&
188
- subject.status !== 'indeterminate') {
189
- throw new Error('subject status is invalid');
190
- }
191
- if (subject.output !== undefined && typeof subject.output !== 'string') {
192
- throw new Error('subject output is invalid');
193
- }
194
- const decoded = decodeEvalResult({
195
- score: null,
196
- usage: subject.usage,
197
- costUsd: subject.costUsd,
198
- durationMs: subject.durationMs,
199
- status: subject.status === 'failed' ? 'subject_failed' : subject.status,
200
- failureReason: subject.failureReason,
201
- artifacts: subject.artifacts,
202
- });
203
- return {
204
- ...(subject.output === undefined ? {} : { output: subject.output }),
205
- usage: decoded.usage,
206
- costUsd: decoded.costUsd,
207
- durationMs: decoded.durationMs,
208
- status: subject.status,
209
- failureReason: decoded.failureReason,
210
- artifacts: decoded.artifacts,
211
- };
212
- }
213
- function decodeVerification(value) {
214
- const verification = exactRecord(value, ['status', 'score', 'failureReason', 'artifacts']);
215
- if (verification.status !== 'completed' &&
216
- verification.status !== 'subject_failed' &&
217
- verification.status !== 'infra_failed') {
218
- throw new Error('verification status is invalid');
219
- }
220
- const decoded = decodeEvalResult({
221
- score: verification.score,
222
- usage: null,
223
- costUsd: null,
224
- durationMs: 0,
225
- status: verification.status,
226
- failureReason: verification.failureReason,
227
- artifacts: verification.artifacts,
228
- });
229
- return {
230
- status: verification.status,
231
- score: decoded.score,
232
- failureReason: decoded.failureReason,
233
- artifacts: decoded.artifacts,
234
- };
235
- }
236
- function settledStatus(subject, verification) {
237
- if (verification === 'infra_failed')
238
- return 'infra_failed';
239
- if (subject === 'failed' || verification === 'subject_failed')
240
- return 'subject_failed';
241
- return 'completed';
242
- }
243
- function exactRecord(value, required, optional = []) {
244
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
245
- throw new Error('result envelope must be an object');
246
- }
247
- const record = value;
248
- const allowed = new Set([...required, ...optional]);
249
- if (required.some((field) => !Object.hasOwn(record, field)) ||
250
- Object.keys(record).some((field) => !allowed.has(field))) {
251
- throw new Error('result envelope fields are invalid');
252
- }
253
- return record;
254
- }
255
- function fromUncertainSubject(subject, cancelled) {
256
- return {
257
- score: null,
258
- usage: subject.usage,
259
- costUsd: subject.costUsd,
260
- durationMs: subject.durationMs,
261
- status: cancelled
262
- ? 'indeterminate'
263
- : subject.status === 'infra_failed'
264
- ? 'infra_failed'
265
- : 'indeterminate',
266
- failureReason: subject.failureReason,
267
- artifacts: subject.artifacts,
268
- };
269
- }
270
- function failure(status, failureReason, subject, artifacts = subject?.artifacts ?? []) {
271
- return {
272
- score: null,
273
- usage: subject?.usage ?? null,
274
- costUsd: subject?.costUsd ?? null,
275
- durationMs: subject?.durationMs ?? 0,
276
- status,
277
- failureReason,
278
- artifacts,
279
- };
280
- }
281
- function selectCells(cells, ids) {
282
- if (!ids)
283
- return [...cells];
284
- const selected = new Set(ids);
285
- const known = new Set(cells.map(({ id }) => id));
286
- for (const id of selected)
287
- if (!known.has(id))
288
- throw new Error(`unknown experiment cell: ${id}`);
289
- return cells.filter(({ id }) => selected.has(id));
290
- }