@citeark/agent 0.3.20
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.
- package/LICENSE +202 -0
- package/README.md +128 -0
- package/data/dataset-source-registry.v1.json +300 -0
- package/dist/arkgraph/boot.js +6 -0
- package/dist/arkgraph/index.html +1 -0
- package/dist/arkgraph/viewer.css +1 -0
- package/dist/arkgraph/viewer.en.css +1 -0
- package/dist/arkgraph/viewer.en.js +49 -0
- package/dist/arkgraph/viewer.en.js.LEGAL.txt +56 -0
- package/dist/arkgraph/viewer.js +49 -0
- package/dist/arkgraph/viewer.js.LEGAL.txt +56 -0
- package/docker/claude-code/Dockerfile +97 -0
- package/docker/claude-code/codex-pro-relay.mjs +466 -0
- package/docker/claude-code/runtime-contract-check.mjs +79 -0
- package/docs/arkgraph-reading.md +79 -0
- package/docs/configuration.md +100 -0
- package/docs/integration.md +92 -0
- package/docs/maturity-plan.md +27 -0
- package/docs/npm-release.md +44 -0
- package/docs/paper-reading.md +40 -0
- package/docs/research-plan-granularity.md +27 -0
- package/docs/terminal.md +49 -0
- package/examples/toy-evaluation/compile-task.json +27 -0
- package/examples/toy-evaluation/paper.md +5 -0
- package/examples/toy-evaluation/repository/README.md +9 -0
- package/examples/toy-evaluation/repository/checkpoint.json +4 -0
- package/examples/toy-evaluation/repository/evaluate.py +17 -0
- package/examples/toy-evaluation/task.json +81 -0
- package/package.json +59 -0
- package/prompts/compile-research.md +58 -0
- package/prompts/execute-contract.md +72 -0
- package/prompts/execute-workspace-simple.md +51 -0
- package/prompts/execute-workspace.md +34 -0
- package/prompts/prepare-reproduction.md +82 -0
- package/prompts/repair-research.md +45 -0
- package/protocol/CAP.md +129 -0
- package/protocol/LICENSE +12 -0
- package/protocol/MAPPINGS.md +72 -0
- package/protocol/README.md +38 -0
- package/protocol/conformance-v2.0-alpha.1.json +36 -0
- package/protocol/examples/arkgraph/checkpoint-evaluation.json +309 -0
- package/protocol/examples/arkgraph/fixtures.mjs +49 -0
- package/protocol/examples/arkgraph/paper-free.json +291 -0
- package/protocol/examples/arkgraph/partial-failure.json +344 -0
- package/protocol/examples/arkgraph/training-evaluation.json +443 -0
- package/protocol/profiles/agent-trace.md +16 -0
- package/protocol/profiles/computational-run.md +16 -0
- package/protocol/profiles/core.md +15 -0
- package/protocol/profiles/public-bundle.md +18 -0
- package/protocol/profiles/reproduction.md +29 -0
- package/protocol/profiles/research-compilation.md +44 -0
- package/protocol/profiles/research-plan.md +39 -0
- package/protocol/profiles/restricted-evidence.md +15 -0
- package/runtime/bootstrap-autodl-runtime.sh +314 -0
- package/runtime/create-runtime-venv.sh +41 -0
- package/runtime/install-local-cpu-runtime.sh +23 -0
- package/runtime/install-scientific-runtime.sh +153 -0
- package/runtime/mineru/parse.py +62 -0
- package/runtime/mineru/requirements.txt +4 -0
- package/runtime/requirements-baseline.txt +38 -0
- package/schemas/cap/v2/activity.schema.json +47 -0
- package/schemas/cap/v2/agent.schema.json +32 -0
- package/schemas/cap/v2/assertion.schema.json +110 -0
- package/schemas/cap/v2/descriptor.schema.json +243 -0
- package/schemas/cap/v2/entity.schema.json +64 -0
- package/schemas/cap/v2/manifest.schema.json +67 -0
- package/schemas/cap/v2/relation.schema.json +82 -0
- package/schemas/compute-catalog.schema.json +63 -0
- package/schemas/compute-decision.schema.json +27 -0
- package/schemas/execution-contract.schema.json +1024 -0
- package/schemas/research-card.schema.json +30 -0
- package/schemas/research-inventory-draft.schema.json +366 -0
- package/schemas/research.schema.json +1044 -0
- package/schemas/result.schema.json +173 -0
- package/schemas/verification-policy.schema.json +47 -0
- package/schemas/verified-conclusion.schema.json +58 -0
- package/schemas/workspace-summary.schema.json +24 -0
- package/scripts/build-arkgraph-view.mjs +12 -0
- package/scripts/check-execution-feasibility.mjs +24 -0
- package/scripts/check-syntax.mjs +15 -0
- package/scripts/deterministic-asset-preparation.py +438 -0
- package/scripts/package-cap.mjs +23 -0
- package/scripts/package-local-agent.mjs +23 -0
- package/scripts/preview-arkgraph.mjs +25 -0
- package/scripts/replay-research-compiler-candidate.mjs +134 -0
- package/scripts/review-compiler-sources.mjs +44 -0
- package/scripts/run-asset-preparation.sh +17 -0
- package/scripts/run-research-plan.mjs +98 -0
- package/scripts/validate-asset-preparation.py +290 -0
- package/scripts/verify-local-runtime.mjs +57 -0
- package/scripts/verify-npm-package.mjs +57 -0
- package/src/adapters/paper2agent.mjs +107 -0
- package/src/assets/cache.mjs +159 -0
- package/src/assets/compute.mjs +98 -0
- package/src/assets/executor.mjs +145 -0
- package/src/assets/lifecycle.mjs +213 -0
- package/src/assets/manifest.mjs +242 -0
- package/src/assets/opportunistic-preparation.mjs +81 -0
- package/src/assets/plan.mjs +411 -0
- package/src/assets/prompts.mjs +29 -0
- package/src/assets/public-asset-probe.mjs +525 -0
- package/src/assets/qualification.mjs +119 -0
- package/src/assets/readiness.mjs +130 -0
- package/src/assets/reproduction-admission.mjs +355 -0
- package/src/assets/requirements.mjs +152 -0
- package/src/assets/source-grounding.mjs +341 -0
- package/src/assets/source-policy.mjs +118 -0
- package/src/autodl/client.mjs +260 -0
- package/src/autodl/ssh.mjs +380 -0
- package/src/autodl/tools.mjs +129 -0
- package/src/cap/redaction.mjs +38 -0
- package/src/cap/v2/archive.mjs +152 -0
- package/src/cap/v2/attestation.mjs +204 -0
- package/src/cap/v2/canonical-json.mjs +114 -0
- package/src/cap/v2/compilation-artifact.mjs +240 -0
- package/src/cap/v2/core.mjs +282 -0
- package/src/cap/v2/measurement-assessment-records.mjs +23 -0
- package/src/cap/v2/pipeline-artifact.mjs +922 -0
- package/src/cap/v2/read.mjs +41 -0
- package/src/cap/v2/reassessment-artifact.mjs +383 -0
- package/src/cap/v2/research-artifact.mjs +231 -0
- package/src/cap/v2/research-map-records.mjs +46 -0
- package/src/cap/v2/research-object-records.mjs +163 -0
- package/src/cap/v2/research-records.mjs +187 -0
- package/src/cap/v2/verify.mjs +642 -0
- package/src/cli.mjs +1146 -0
- package/src/compute/autodl-pro-compiler.mjs +347 -0
- package/src/compute/autodl-pro-executor.mjs +459 -0
- package/src/compute/autodl-pro-job.mjs +843 -0
- package/src/compute/autodl-pro-network.mjs +295 -0
- package/src/compute/autodl-pro-remote.mjs +810 -0
- package/src/compute/autodl-pro-staging.mjs +117 -0
- package/src/compute/campaign.mjs +110 -0
- package/src/compute/catalog.mjs +123 -0
- package/src/compute/checkpoint-protocol.mjs +154 -0
- package/src/compute/codex-account-lock.mjs +111 -0
- package/src/compute/codex-account-session.mjs +107 -0
- package/src/compute/compiler-profile.mjs +38 -0
- package/src/compute/compiler-router.mjs +23 -0
- package/src/compute/coordinator-recovery.mjs +210 -0
- package/src/compute/executor-router.mjs +29 -0
- package/src/compute/gcp-batch-compiler.mjs +685 -0
- package/src/compute/gcp-batch-executor.mjs +1215 -0
- package/src/compute/gcp-batch-failure.mjs +92 -0
- package/src/compute/gcp-batch-job.mjs +527 -0
- package/src/compute/gcp-batch-lifecycle.mjs +81 -0
- package/src/compute/gcp-checkpoint-worker.mjs +1633 -0
- package/src/compute/local-codex-compiler.mjs +52 -0
- package/src/compute/measurement-hardware.mjs +128 -0
- package/src/compute/remote-attempt.mjs +226 -0
- package/src/compute/requirements.mjs +124 -0
- package/src/compute/research-phases.mjs +48 -0
- package/src/compute/scheduler.mjs +452 -0
- package/src/compute/shared-workloads.mjs +26 -0
- package/src/compute/stage-archive.mjs +79 -0
- package/src/contracts/campaign-contract.mjs +52 -0
- package/src/contracts/execution-contract.mjs +819 -0
- package/src/contracts/execution-mode.mjs +19 -0
- package/src/contracts/execution-timeouts.mjs +45 -0
- package/src/contracts/execution-workload.mjs +68 -0
- package/src/contracts/preflight-schema.mjs +25 -0
- package/src/contracts/public-contract.mjs +63 -0
- package/src/contracts/subject-tags.mjs +31 -0
- package/src/dashboard/data.mjs +898 -0
- package/src/dashboard/server.mjs +79 -0
- package/src/dashboard/static/dashboard.css +366 -0
- package/src/dashboard/static/dashboard.js +560 -0
- package/src/dashboard/static/index.html +85 -0
- package/src/deployment/community-policy.mjs +9 -0
- package/src/deployment/environment.mjs +112 -0
- package/src/deployment/guided.mjs +98 -0
- package/src/deployment/handoff.mjs +102 -0
- package/src/deployment/local-contract.mjs +31 -0
- package/src/deployment/local.mjs +100 -0
- package/src/deployment/prepare.mjs +46 -0
- package/src/deployment/recipe.mjs +108 -0
- package/src/deployment/supplement.mjs +51 -0
- package/src/deployment/terminal.mjs +43 -0
- package/src/diagnosis/renderer.mjs +75 -0
- package/src/diagnosis/target-failure.mjs +46 -0
- package/src/evidence/parser-registry.mjs +54 -0
- package/src/evidence/parsers/fasttext-classification.mjs +82 -0
- package/src/evidence/parsers/json-scalar.mjs +96 -0
- package/src/evidence/parsers/simcse-senteval.mjs +104 -0
- package/src/evidence/parsers/starspace-classification.mjs +78 -0
- package/src/evidence/registry.mjs +147 -0
- package/src/execution/runner-audit.mjs +473 -0
- package/src/gcp/auth.mjs +106 -0
- package/src/gcp/batch-client.mjs +120 -0
- package/src/gcp/resource-discovery.mjs +177 -0
- package/src/gcp/rest.mjs +82 -0
- package/src/gcp/secret-manager.mjs +34 -0
- package/src/gcp/signed-url.mjs +133 -0
- package/src/gcp/storage.mjs +220 -0
- package/src/graph/command.mjs +41 -0
- package/src/graph/execution.mjs +97 -0
- package/src/graph/model.mjs +37 -0
- package/src/graph/presentation.mjs +110 -0
- package/src/graph/query.mjs +159 -0
- package/src/graph/research-relations.mjs +69 -0
- package/src/graph/source-page.mjs +12 -0
- package/src/graph/source-preview.mjs +34 -0
- package/src/graph/validate.mjs +76 -0
- package/src/job.mjs +496 -0
- package/src/network/autodl-routing-proxy.mjs +462 -0
- package/src/network/egress-proxy.mjs +158 -0
- package/src/observability/event-contract.mjs +230 -0
- package/src/observability/pipeline-monitor.mjs +166 -0
- package/src/pipeline/orchestrator.mjs +1281 -0
- package/src/pipeline/recovery-error.mjs +11 -0
- package/src/pipeline/replay.mjs +304 -0
- package/src/pipeline/shared-execution.mjs +115 -0
- package/src/pipeline/stage-checkpoint.mjs +86 -0
- package/src/pipeline/stage-recovery.mjs +101 -0
- package/src/pipeline/targets.mjs +110 -0
- package/src/process.mjs +143 -0
- package/src/protocol.mjs +312 -0
- package/src/provider/codex-account.mjs +44 -0
- package/src/provider/codex-completion.mjs +49 -0
- package/src/provider/completion.mjs +292 -0
- package/src/provider/model-client.mjs +44 -0
- package/src/provider/model-route.mjs +29 -0
- package/src/provider/openrouter-readiness.mjs +189 -0
- package/src/provider/reader-bridge.mjs +35 -0
- package/src/provider/relay.mjs +263 -0
- package/src/provider/runtime-auth.mjs +40 -0
- package/src/public/cap.d.mts +90 -0
- package/src/public/cap.mjs +12 -0
- package/src/public/contracts.d.mts +2 -0
- package/src/public/host.mjs +171 -0
- package/src/public/operations.d.mts +11 -0
- package/src/public/presentation.d.mts +4 -0
- package/src/records/views.mjs +26 -0
- package/src/remote/command.mjs +178 -0
- package/src/remote/ssh.mjs +59 -0
- package/src/repository-origin.mjs +81 -0
- package/src/reproduction/evidence-feedback.mjs +96 -0
- package/src/reproduction/incomplete-initialization.mjs +25 -0
- package/src/reproduction/lifecycle.mjs +253 -0
- package/src/reproduction/plan.mjs +132 -0
- package/src/reproduction/prompts.mjs +70 -0
- package/src/reproduction/runner.mjs +188 -0
- package/src/reproduction/summary.mjs +130 -0
- package/src/reproduction/workspace-mode.mjs +7 -0
- package/src/research/automatic-admission.mjs +156 -0
- package/src/research/compiler-coverage.mjs +85 -0
- package/src/research/compiler-failure.mjs +24 -0
- package/src/research/compiler-normalization-guards.mjs +112 -0
- package/src/research/compiler-repair.mjs +3 -0
- package/src/research/compiler.mjs +853 -0
- package/src/research/continuation-selection.mjs +26 -0
- package/src/research/execution-graph-context.mjs +43 -0
- package/src/research/experiment-importance.mjs +15 -0
- package/src/research/inventory-handoff.mjs +104 -0
- package/src/research/inventory-revisions.mjs +32 -0
- package/src/research/mineru-local.mjs +73 -0
- package/src/research/paper-command.mjs +19 -0
- package/src/research/paper-markdown.mjs +180 -0
- package/src/research/paper-source-map.mjs +69 -0
- package/src/research/planning-policy.mjs +88 -0
- package/src/research/reference-materials.mjs +11 -0
- package/src/research/reproduction-scope.mjs +30 -0
- package/src/research/research-map.mjs +94 -0
- package/src/research/research-objects.mjs +88 -0
- package/src/research/source-discovery.mjs +646 -0
- package/src/research/source-observations.mjs +75 -0
- package/src/research/source-review-cli-mcp.mjs +26 -0
- package/src/research/source-review-input.mjs +209 -0
- package/src/research/source-review-local-codex.mjs +36 -0
- package/src/research/source-review-model.mjs +70 -0
- package/src/research/source-review.mjs +173 -0
- package/src/research/structure.mjs +3163 -0
- package/src/research-card/renderer.mjs +277 -0
- package/src/research-card/verified-conclusion.mjs +143 -0
- package/src/results/output-registry.mjs +183 -0
- package/src/runtime/claude-code.mjs +52 -0
- package/src/runtime/codex-capacity-retry.mjs +87 -0
- package/src/runtime/codex.mjs +64 -0
- package/src/runtime/config.mjs +157 -0
- package/src/runtime/final-output.mjs +40 -0
- package/src/runtime/index.mjs +21 -0
- package/src/runtime/local-codex.mjs +74 -0
- package/src/runtime/opencode.mjs +95 -0
- package/src/runtime/prompt.mjs +13 -0
- package/src/sandbox/docker.mjs +363 -0
- package/src/settings/command.mjs +297 -0
- package/src/settings/store.mjs +119 -0
- package/src/telemetry/pricing.mjs +68 -0
- package/src/telemetry/usage.mjs +265 -0
- package/src/terminal/events.mjs +97 -0
- package/src/terminal/input.mjs +40 -0
- package/src/terminal/plain.mjs +40 -0
- package/src/terminal/remote-stream.mjs +22 -0
- package/src/terminal/screen.mjs +214 -0
- package/src/terminal/transcript.mjs +69 -0
- package/src/util.mjs +107 -0
- package/src/verification/ai-assessor.mjs +534 -0
- package/src/verification/claim-evaluator.mjs +242 -0
- package/src/verification/evidence-context.mjs +165 -0
- package/src/verification/evidence-reader.mjs +95 -0
- package/src/verification/integrity.mjs +570 -0
- package/src/verification/tolerance.mjs +32 -0
- package/src/workloads/cpu-research-preparation.mjs +56 -0
- package/src/workloads/definition.mjs +74 -0
- package/src/workloads/phase-aware-reproduction.mjs +46 -0
- package/src/workloads/reproduction.mjs +85 -0
- package/src/workspace/command.mjs +242 -0
- package/src/workspace/control.mjs +49 -0
- package/src/workspace/entry.mjs +28 -0
- package/src/workspace/input.mjs +93 -0
- package/src/workspace/interactive.mjs +94 -0
- package/src/workspace/jobs.mjs +418 -0
- package/src/workspace/session.mjs +97 -0
- package/src/workspace/worker.mjs +137 -0
- package/ui/arkgraph/ambient-motion.mjs +10 -0
- package/ui/arkgraph/app.jsx +153 -0
- package/ui/arkgraph/boot.js +6 -0
- package/ui/arkgraph/camera-motion.mjs +20 -0
- package/ui/arkgraph/context-reveal.mjs +39 -0
- package/ui/arkgraph/details.css +3 -0
- package/ui/arkgraph/entry.jsx +28 -0
- package/ui/arkgraph/experiment-curves.mjs +17 -0
- package/ui/arkgraph/experiment-selection.mjs +15 -0
- package/ui/arkgraph/experiment-style.css +26 -0
- package/ui/arkgraph/experiment-ui.jsx +32 -0
- package/ui/arkgraph/frame.html +1 -0
- package/ui/arkgraph/graph-gestures.mjs +62 -0
- package/ui/arkgraph/label-layout.mjs +57 -0
- package/ui/arkgraph/locales/en.json +229 -0
- package/ui/arkgraph/locales/source-types.json +15 -0
- package/ui/arkgraph/localization-build.mjs +27 -0
- package/ui/arkgraph/material-build.mjs +23 -0
- package/ui/arkgraph/material-colors.mjs +39 -0
- package/ui/arkgraph/material-style.css +15 -0
- package/ui/arkgraph/open-graph.jsx +326 -0
- package/ui/arkgraph/outline.jsx +49 -0
- package/ui/arkgraph/package-lock.json +888 -0
- package/ui/arkgraph/package.json +17 -0
- package/ui/arkgraph/reading-layout.mjs +130 -0
- package/ui/arkgraph/reading-presentation.mjs +73 -0
- package/ui/arkgraph/record-detail.css +51 -0
- package/ui/arkgraph/record-details.jsx +29 -0
- package/ui/arkgraph/research-types.mjs +31 -0
- package/ui/arkgraph/selection-mark.jsx +6 -0
- package/ui/arkgraph/soft-spine.mjs +26 -0
- package/ui/arkgraph/steering-style.css +187 -0
- package/ui/arkgraph/style.css +272 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { DEFAULT_COMPUTE_POLICY, validateComputeCatalog, validateComputePolicy } from "./catalog.mjs";
|
|
2
|
+
import { resolveComputeRequirement } from "./requirements.mjs";
|
|
3
|
+
import { CiteArkError, readJson, sha256Value, writeJson } from "../util.mjs";
|
|
4
|
+
import { LEGACY_EXECUTION_TIMEOUT_FALLBACK_MINUTES } from "../contracts/execution-timeouts.mjs";
|
|
5
|
+
import { measurementHardwareConstraint, measurementHardwareRejections } from "./measurement-hardware.mjs";
|
|
6
|
+
|
|
7
|
+
export async function scheduleCompute({
|
|
8
|
+
experiment,
|
|
9
|
+
contract,
|
|
10
|
+
catalog,
|
|
11
|
+
policy = DEFAULT_COMPUTE_POLICY,
|
|
12
|
+
resourceLimits,
|
|
13
|
+
profileOverride,
|
|
14
|
+
selectionConstraints,
|
|
15
|
+
decidedAt = new Date().toISOString(),
|
|
16
|
+
outputPath,
|
|
17
|
+
}) {
|
|
18
|
+
policy = { ...DEFAULT_COMPUTE_POLICY, ...policy };
|
|
19
|
+
// This mode comes from the host's per-job authorization, never research JSON.
|
|
20
|
+
if (resourceLimits?.maximumCost === null) policy.maxEstimatedComputeUsd = null;
|
|
21
|
+
validateComputeCatalog(catalog);
|
|
22
|
+
validateComputePolicy(policy);
|
|
23
|
+
const selection = normalizeSelectionConstraints(selectionConstraints, {
|
|
24
|
+
allowFallbackProfiles: Boolean(profileOverride),
|
|
25
|
+
});
|
|
26
|
+
selection.measurementHardware = measurementHardwareConstraint(contract ?? experiment);
|
|
27
|
+
if (contract?.research?.experimentVersionId && experiment?.versionId !== contract.research.experimentVersionId) {
|
|
28
|
+
throw new CiteArkError("计算调度的 experiment 与 execution contract 未绑定");
|
|
29
|
+
}
|
|
30
|
+
const requirement = contract?.computeRequirement ?? resolveComputeRequirement(experiment);
|
|
31
|
+
const contractEnvironment = contract?.environment ?? experiment?.environment ?? {};
|
|
32
|
+
const evaluatedProfiles = catalog.profiles
|
|
33
|
+
.map((profile) => evaluateProfile(
|
|
34
|
+
profile,
|
|
35
|
+
requirement,
|
|
36
|
+
policy,
|
|
37
|
+
contractEnvironment,
|
|
38
|
+
selection,
|
|
39
|
+
));
|
|
40
|
+
const candidates = evaluatedProfiles.filter((candidate) => candidate.compatible);
|
|
41
|
+
const selected = profileOverride
|
|
42
|
+
? candidates.find((candidate) => candidate.profile.id === profileOverride)
|
|
43
|
+
: [...candidates].sort(compareCandidates)[0];
|
|
44
|
+
if (!selected) {
|
|
45
|
+
const overrideMessage = profileOverride ? `指定的 profile ${profileOverride} 不可用或不兼容。` : "没有兼容的计算资源。";
|
|
46
|
+
const rejections = evaluatedProfiles.map((result) =>
|
|
47
|
+
`${result.profile.id}: ${result.reasons.join(";") || "未知原因"}`);
|
|
48
|
+
const error = new CiteArkError(`${overrideMessage}\n- ${rejections.join("\n- ")}`);
|
|
49
|
+
error.computeAvailability = publicComputeAvailability({
|
|
50
|
+
requirement,
|
|
51
|
+
policy,
|
|
52
|
+
evaluatedProfiles,
|
|
53
|
+
});
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const alternatives = evaluatedProfiles.map((evaluated) => {
|
|
58
|
+
const profile = evaluated.profile;
|
|
59
|
+
return {
|
|
60
|
+
profileId: profile.id,
|
|
61
|
+
profile: publicProfile(profile),
|
|
62
|
+
compatible: evaluated.compatible,
|
|
63
|
+
estimatedDurationMinutes: evaluated.estimatedDurationMinutes,
|
|
64
|
+
estimatedCostUsd: evaluated.estimatedCostUsd,
|
|
65
|
+
estimatedCostCny: evaluated.estimatedCostCny,
|
|
66
|
+
reasons: evaluated.reasons,
|
|
67
|
+
warnings: evaluated.warnings,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
const body = {
|
|
71
|
+
schemaVersion: "0.1",
|
|
72
|
+
kind: "citeark.compute-decision",
|
|
73
|
+
experimentId: experiment?.id ?? contract?.research?.experimentId ?? null,
|
|
74
|
+
experimentVersionId: experiment?.versionId ?? contract?.research?.experimentVersionId ?? null,
|
|
75
|
+
contractDigest: contract?.contractDigest ?? null,
|
|
76
|
+
requirement,
|
|
77
|
+
measurementHardware: selection.measurementHardware,
|
|
78
|
+
selectedProfile: publicProfile(selected.profile),
|
|
79
|
+
environment: executionEnvironment(
|
|
80
|
+
selected.profile,
|
|
81
|
+
contractEnvironment,
|
|
82
|
+
),
|
|
83
|
+
estimate: {
|
|
84
|
+
durationMinutes: selected.estimatedDurationMinutes,
|
|
85
|
+
costUsd: selected.estimatedCostUsd,
|
|
86
|
+
costCny: selected.estimatedCostCny,
|
|
87
|
+
pricingKnown: selected.estimatedCostUsd !== null || selected.estimatedCostCny !== null,
|
|
88
|
+
durationBasis: selected.estimatedDurationMinutes === null ? "unknown" : "research_estimate",
|
|
89
|
+
},
|
|
90
|
+
allocation: runtimeAllocation(selected.profile, contractEnvironment),
|
|
91
|
+
policy: {
|
|
92
|
+
allowCpuFallback: policy.allowCpuFallback ?? true,
|
|
93
|
+
maxEstimatedComputeUsd: policy.maxEstimatedComputeUsd ?? null,
|
|
94
|
+
maxNonResumableSpotDurationMinutes: policy.maxNonResumableSpotDurationMinutes ?? null,
|
|
95
|
+
manualProfileOverride: profileOverride ?? null,
|
|
96
|
+
requiredExecutor: selection.requiredExecutor,
|
|
97
|
+
requiredProvisioningModel: selection.requiredProvisioningModel,
|
|
98
|
+
allowFallbackProfiles: selection.allowFallbackProfiles,
|
|
99
|
+
selectionReason: selection.reason,
|
|
100
|
+
},
|
|
101
|
+
catalogDigest: `sha256:${sha256Value(catalog)}`,
|
|
102
|
+
policyDigest: `sha256:${sha256Value(policy)}`,
|
|
103
|
+
reasonCodes: selectionReasonCodes(selected, requirement, profileOverride, selection),
|
|
104
|
+
rationale: selectionRationale(selected, requirement, profileOverride, selection),
|
|
105
|
+
alternatives,
|
|
106
|
+
decidedAt,
|
|
107
|
+
scheduler: { name: "citeark-compute-scheduler", version: "0.2.0", strategy: "compatible-preemption-safety-then-estimated-total-cost" },
|
|
108
|
+
};
|
|
109
|
+
const decision = { ...body, decisionDigest: `sha256:${sha256Value(body)}` };
|
|
110
|
+
if (outputPath) await writeJson(outputPath, decision);
|
|
111
|
+
return decision;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function publicComputeAvailability({ requirement, policy, evaluatedProfiles }) {
|
|
115
|
+
const nearest = [...evaluatedProfiles].sort(compareRejectedProfiles)[0] ?? null;
|
|
116
|
+
return {
|
|
117
|
+
requirements: {
|
|
118
|
+
accelerator: requirement.accelerator,
|
|
119
|
+
cpuCores: requirement.cpuCores,
|
|
120
|
+
memoryGb: requirement.memoryGb,
|
|
121
|
+
gpuCount: requirement.gpuCount,
|
|
122
|
+
minVramGb: requirement.minVramGb,
|
|
123
|
+
preferredGpuTypes: uniquePublicStrings(requirement.preferredGpuTypes),
|
|
124
|
+
},
|
|
125
|
+
policy: {
|
|
126
|
+
maxEstimatedComputeUsd: finitePublicNumber(policy.maxEstimatedComputeUsd),
|
|
127
|
+
allowedGpuTypes: uniquePublicStrings(policy.allowedGpuTypes),
|
|
128
|
+
maximumGpuCount: finitePublicNumber(policy.maximumGpuCount),
|
|
129
|
+
},
|
|
130
|
+
nearestProfile: nearest ? {
|
|
131
|
+
label: nearest.profile.label ?? nearest.profile.id,
|
|
132
|
+
accelerator: nearest.profile.accelerator,
|
|
133
|
+
cpuCores: nearest.profile.cpuCores,
|
|
134
|
+
memoryGb: nearest.profile.memoryGb,
|
|
135
|
+
gpuType: nearest.profile.gpu?.type ?? null,
|
|
136
|
+
gpuCount: nearest.profile.gpu?.count ?? null,
|
|
137
|
+
vramGb: nearest.profile.gpu?.vramGb ?? null,
|
|
138
|
+
estimatedDurationMinutes: nearest.estimatedDurationMinutes,
|
|
139
|
+
estimatedCostUsd: nearest.estimatedCostUsd,
|
|
140
|
+
estimatedCostCny: nearest.estimatedCostCny,
|
|
141
|
+
blockerCodes: uniquePublicStrings(nearest.reasons.map(publicBlockerCode)),
|
|
142
|
+
} : null,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function compareRejectedProfiles(left, right) {
|
|
147
|
+
const score = (candidate) => candidate.reasons.reduce(
|
|
148
|
+
(total, reason) => total + blockerWeight(publicBlockerCode(reason)),
|
|
149
|
+
0,
|
|
150
|
+
);
|
|
151
|
+
const scoreDifference = score(left) - score(right);
|
|
152
|
+
return scoreDifference || compareCandidates(left, right);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function publicBlockerCode(reason) {
|
|
156
|
+
if (/超过预算/.test(reason)) return "budget_exceeded";
|
|
157
|
+
if (/资源当前不可用/.test(reason)) return "capacity_unavailable";
|
|
158
|
+
if (/资源规格不在策略白名单|显式供应商回退/.test(reason)) return "profile_not_allowed";
|
|
159
|
+
if (/执行器/.test(reason)) return "executor_not_allowed";
|
|
160
|
+
if (/供应模式/.test(reason)) return "provisioning_mismatch";
|
|
161
|
+
if (/CPU 不足/.test(reason)) return "cpu_insufficient";
|
|
162
|
+
if (/内存不足/.test(reason)) return "memory_insufficient";
|
|
163
|
+
if (/必须使用 GPU/.test(reason)) return "gpu_required";
|
|
164
|
+
if (/仅 CPU/.test(reason)) return "cpu_only";
|
|
165
|
+
if (/禁止 CPU 回退/.test(reason)) return "cpu_fallback_disabled";
|
|
166
|
+
if (/GPU 型号/.test(reason)) return "gpu_type_incompatible";
|
|
167
|
+
if (/GPU 数量/.test(reason)) return "gpu_count_incompatible";
|
|
168
|
+
if (/显存不足/.test(reason)) return "vram_insufficient";
|
|
169
|
+
if (/超过执行上限/.test(reason)) return "timeout_exceeded";
|
|
170
|
+
if (/科研测量要求|科研测量禁止|硬件/.test(reason)) return "hardware_incompatible";
|
|
171
|
+
return "other";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function blockerWeight(code) {
|
|
175
|
+
if (code === "budget_exceeded") return 1;
|
|
176
|
+
if (code === "capacity_unavailable") return 3;
|
|
177
|
+
if (code === "profile_not_allowed" || code === "executor_not_allowed"
|
|
178
|
+
|| code === "provisioning_mismatch") return 5;
|
|
179
|
+
return 10;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function finitePublicNumber(value) {
|
|
183
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function uniquePublicStrings(value) {
|
|
187
|
+
return [...new Set((Array.isArray(value) ? value : []).filter(
|
|
188
|
+
(item) => typeof item === "string" && item,
|
|
189
|
+
))];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function runtimeAllocation(profile, environment) {
|
|
193
|
+
const minutes = executionEnvironment(profile, environment).timeoutMinutes;
|
|
194
|
+
const cost = (rate) => Number.isFinite(rate) && rate >= 0
|
|
195
|
+
? Math.round(rate * minutes / 60 * 1e6) / 1e6 : null;
|
|
196
|
+
return {
|
|
197
|
+
basis: "configured_execution_timeout",
|
|
198
|
+
durationMinutes: minutes,
|
|
199
|
+
computeCostUsd: cost(profile.hourlyUsd),
|
|
200
|
+
computeCostCny: cost(profile.hourlyCny),
|
|
201
|
+
scope: "One execution window at catalog compute rates; excludes startup, retries, models, storage and coordinator costs. Not a completion-time estimate or an invoice ceiling.",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function loadComputeDecision(target, contract) {
|
|
206
|
+
const decision = await readJson(target);
|
|
207
|
+
validateComputeDecision(decision, contract);
|
|
208
|
+
return decision;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function validateComputeDecision(decision, contract) {
|
|
212
|
+
const issues = [];
|
|
213
|
+
if (decision?.schemaVersion !== "0.1") issues.push("decision.schemaVersion 必须是 0.1");
|
|
214
|
+
if (decision?.kind !== "citeark.compute-decision") issues.push("decision.kind 无效");
|
|
215
|
+
if (typeof decision?.selectedProfile?.id !== "string" || !decision.selectedProfile.id) issues.push("selectedProfile.id 必须是非空字符串");
|
|
216
|
+
if (typeof decision?.selectedProfile?.executor !== "string" || !decision.selectedProfile.executor) issues.push("selectedProfile.executor 必须是非空字符串");
|
|
217
|
+
if (!decision?.environment || typeof decision.environment !== "object") issues.push("environment 必须是对象");
|
|
218
|
+
else {
|
|
219
|
+
if (typeof decision.environment.image !== "string" || !decision.environment.image) issues.push("environment.image 必须是非空字符串");
|
|
220
|
+
for (const field of ["timeoutMinutes", "cpus", "memoryGb", "shmGb"]) {
|
|
221
|
+
if (typeof decision.environment[field] !== "number" || !Number.isFinite(decision.environment[field]) || decision.environment[field] <= 0) issues.push(`environment.${field} 必须大于 0`);
|
|
222
|
+
}
|
|
223
|
+
if (!new Set(["none", "all"]).has(decision.environment.gpu)) issues.push("environment.gpu 必须是 none 或 all");
|
|
224
|
+
if (decision.environment.networkAccess !== true) issues.push("当前运行器要求 environment.networkAccess=true");
|
|
225
|
+
}
|
|
226
|
+
if (decision?.selectedProfile?.accelerator === "gpu" && decision?.environment?.gpu !== "all") issues.push("GPU profile 与执行环境不一致");
|
|
227
|
+
if (decision?.selectedProfile?.accelerator === "cpu" && decision?.environment?.gpu !== "none") issues.push("CPU profile 与执行环境不一致");
|
|
228
|
+
if (contract?.contractDigest && decision?.contractDigest && decision.contractDigest !== contract.contractDigest) issues.push("compute decision 与 execution contract 未绑定");
|
|
229
|
+
if (contract?.research?.experimentVersionId && decision?.experimentVersionId && decision.experimentVersionId !== contract.research.experimentVersionId) issues.push("compute decision 与 experiment version 未绑定");
|
|
230
|
+
if (contract?.computeRequirement) {
|
|
231
|
+
if (decision?.environment?.cpus < contract.computeRequirement.cpuCores) issues.push("compute decision 的 CPU 低于契约需求");
|
|
232
|
+
if (decision?.environment?.memoryGb < contract.computeRequirement.memoryGb) issues.push("compute decision 的内存低于契约需求");
|
|
233
|
+
if (contract.computeRequirement.accelerator === "required" && decision?.environment?.gpu !== "all") issues.push("compute decision 没有满足契约的 GPU 必需条件");
|
|
234
|
+
}
|
|
235
|
+
if (
|
|
236
|
+
typeof contract?.environment?.timeoutMinutes === "number"
|
|
237
|
+
&& decision?.environment?.timeoutMinutes > contract.environment.timeoutMinutes
|
|
238
|
+
) {
|
|
239
|
+
issues.push("compute decision 的超时上限不能超过执行契约");
|
|
240
|
+
}
|
|
241
|
+
const body = { ...decision };
|
|
242
|
+
delete body.decisionDigest;
|
|
243
|
+
if (decision?.decisionDigest !== `sha256:${sha256Value(body)}`) issues.push("decisionDigest 与内容不一致");
|
|
244
|
+
if (issues.length) throw new CiteArkError(`计算决策校验失败:\n- ${issues.join("\n- ")}`);
|
|
245
|
+
return decision;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function evaluateProfile(profile, requirement, policy, contractEnvironment, selection) {
|
|
249
|
+
const reasons = measurementHardwareRejections(profile, selection.measurementHardware);
|
|
250
|
+
const warnings = [];
|
|
251
|
+
if (profile.available === false) reasons.push("资源当前不可用");
|
|
252
|
+
if (profile.executorConfig?.fallbackOnly === true && selection.allowFallbackProfiles !== true) {
|
|
253
|
+
reasons.push("资源仅用于显式供应商回退");
|
|
254
|
+
}
|
|
255
|
+
if (selection.requiredExecutor && profile.executor !== selection.requiredExecutor) {
|
|
256
|
+
reasons.push(`执行器不符合本次约束(需要 ${selection.requiredExecutor})`);
|
|
257
|
+
}
|
|
258
|
+
if (
|
|
259
|
+
selection.requiredProvisioningModel
|
|
260
|
+
&& profile.executorConfig?.provisioningModel !== selection.requiredProvisioningModel
|
|
261
|
+
) {
|
|
262
|
+
reasons.push(`供应模式不符合本次约束(需要 ${selection.requiredProvisioningModel})`);
|
|
263
|
+
}
|
|
264
|
+
if (policy.allowedExecutors && !policy.allowedExecutors.includes(profile.executor)) reasons.push("执行器不在策略白名单");
|
|
265
|
+
if (policy.allowedProfileIds && !policy.allowedProfileIds.includes(profile.id)) reasons.push("资源规格不在策略白名单");
|
|
266
|
+
if (profile.cpuCores < requirement.cpuCores) reasons.push(`CPU 不足(需要 ${requirement.cpuCores})`);
|
|
267
|
+
if (profile.memoryGb < requirement.memoryGb) reasons.push(`内存不足(需要 ${requirement.memoryGb} GB)`);
|
|
268
|
+
if (
|
|
269
|
+
requirement.accelerator === "none"
|
|
270
|
+
&& profile.accelerator !== "cpu"
|
|
271
|
+
&& profile.executorConfig?.allowCpuWorkloads !== true
|
|
272
|
+
) reasons.push("任务分类为仅 CPU,不占用 GPU 资源");
|
|
273
|
+
if (requirement.accelerator === "required" && profile.accelerator !== "gpu") reasons.push("实验必须使用 GPU");
|
|
274
|
+
if (profile.accelerator === "cpu" && requirement.accelerator === "optional" && (!requirement.cpuFallbackAllowed || policy.allowCpuFallback === false)) {
|
|
275
|
+
reasons.push("策略禁止 CPU 回退");
|
|
276
|
+
}
|
|
277
|
+
if (profile.accelerator === "gpu") {
|
|
278
|
+
if (policy.allowedGpuTypes && !policy.allowedGpuTypes.includes(profile.gpu?.type)) reasons.push("GPU 型号超出论文算力范围");
|
|
279
|
+
if (policy.maximumGpuCount && profile.gpu?.count > policy.maximumGpuCount) reasons.push("GPU 数量超出论文算力范围");
|
|
280
|
+
if ((profile.gpu?.count ?? 0) < requirement.gpuCount) reasons.push(`GPU 数量不足(需要 ${requirement.gpuCount})`);
|
|
281
|
+
if (requirement.minVramGb && (profile.gpu?.vramGb ?? 0) < requirement.minVramGb) reasons.push(`显存不足(需要 ${requirement.minVramGb} GB)`);
|
|
282
|
+
}
|
|
283
|
+
const duration = profile.accelerator === "gpu"
|
|
284
|
+
? requirement.estimatedDurationMinutes.gpu
|
|
285
|
+
: requirement.estimatedDurationMinutes.cpu;
|
|
286
|
+
const timeoutMinutes = executionTimeoutMinutes(profile, contractEnvironment);
|
|
287
|
+
if (duration !== null && duration > timeoutMinutes) {
|
|
288
|
+
reasons.push(`预计运行 ${duration} 分钟超过执行上限 ${timeoutMinutes} 分钟`);
|
|
289
|
+
}
|
|
290
|
+
const estimatedCostUsd = duration !== null && profile.hourlyUsd !== null && profile.hourlyUsd !== undefined
|
|
291
|
+
? roundMoney((duration / 60) * profile.hourlyUsd)
|
|
292
|
+
: profile.hourlyUsd === 0 ? 0 : null;
|
|
293
|
+
const hourlyCny = profile.hourlyCny
|
|
294
|
+
?? profile.executorConfig?.maxHourlyCny
|
|
295
|
+
?? null;
|
|
296
|
+
const estimatedCostCny = duration !== null && typeof hourlyCny === "number"
|
|
297
|
+
? roundMoney((duration / 60) * hourlyCny)
|
|
298
|
+
: hourlyCny === 0 ? 0 : null;
|
|
299
|
+
if (policy.maxEstimatedComputeUsd !== null && policy.maxEstimatedComputeUsd !== undefined && estimatedCostUsd !== null && estimatedCostUsd > policy.maxEstimatedComputeUsd) {
|
|
300
|
+
reasons.push(`预计费用 ${estimatedCostUsd} USD 超过预算`);
|
|
301
|
+
}
|
|
302
|
+
const unsafeSpot = profile.executorConfig?.provisioningModel === "SPOT"
|
|
303
|
+
&& requirement.preemptionSafe !== true
|
|
304
|
+
&& duration !== null
|
|
305
|
+
&& policy.maxNonResumableSpotDurationMinutes !== null
|
|
306
|
+
&& policy.maxNonResumableSpotDurationMinutes !== undefined
|
|
307
|
+
&& duration > policy.maxNonResumableSpotDurationMinutes;
|
|
308
|
+
if (unsafeSpot) {
|
|
309
|
+
warnings.push(`预计运行 ${duration} 分钟,且科研计算未声明可从持久化检查点续算;优先使用非 Spot 资源`);
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
profile,
|
|
313
|
+
compatible: reasons.length === 0,
|
|
314
|
+
reasons,
|
|
315
|
+
warnings,
|
|
316
|
+
unsafeSpot,
|
|
317
|
+
estimatedDurationMinutes: duration,
|
|
318
|
+
estimatedCostUsd,
|
|
319
|
+
estimatedCostCny,
|
|
320
|
+
preferredGpuType: profile.accelerator === "gpu" && requirement.preferredGpuTypes.includes(profile.gpu?.type),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function compareCandidates(left, right) {
|
|
325
|
+
if (left.unsafeSpot !== right.unsafeSpot) return left.unsafeSpot ? 1 : -1;
|
|
326
|
+
const leftCost = left.estimatedCostUsd;
|
|
327
|
+
const rightCost = right.estimatedCostUsd;
|
|
328
|
+
if (leftCost !== null && rightCost !== null && leftCost !== rightCost) return leftCost - rightCost;
|
|
329
|
+
if (leftCost !== null && rightCost === null) return -1;
|
|
330
|
+
if (leftCost === null && rightCost !== null) return 1;
|
|
331
|
+
const leftCny = left.estimatedCostCny;
|
|
332
|
+
const rightCny = right.estimatedCostCny;
|
|
333
|
+
if (leftCny !== null && rightCny !== null && leftCny !== rightCny) return leftCny - rightCny;
|
|
334
|
+
if (leftCny !== null && rightCny === null) return -1;
|
|
335
|
+
if (leftCny === null && rightCny !== null) return 1;
|
|
336
|
+
if (left.preferredGpuType !== right.preferredGpuType) return left.preferredGpuType ? -1 : 1;
|
|
337
|
+
if (left.profile.accelerator !== right.profile.accelerator) return left.profile.accelerator === "cpu" ? -1 : 1;
|
|
338
|
+
if (
|
|
339
|
+
left.profile.accelerator === "gpu"
|
|
340
|
+
&& right.profile.accelerator === "gpu"
|
|
341
|
+
&& left.profile.gpu?.vramGb !== right.profile.gpu?.vramGb
|
|
342
|
+
) {
|
|
343
|
+
return (left.profile.gpu?.vramGb ?? Number.POSITIVE_INFINITY)
|
|
344
|
+
- (right.profile.gpu?.vramGb ?? Number.POSITIVE_INFINITY);
|
|
345
|
+
}
|
|
346
|
+
const leftHourly = left.profile.hourlyUsd ?? Number.POSITIVE_INFINITY;
|
|
347
|
+
const rightHourly = right.profile.hourlyUsd ?? Number.POSITIVE_INFINITY;
|
|
348
|
+
if (leftHourly !== rightHourly) return leftHourly - rightHourly;
|
|
349
|
+
return left.profile.id.localeCompare(right.profile.id);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function executionEnvironment(profile, fallback = {}) {
|
|
353
|
+
return {
|
|
354
|
+
image: profile.image ?? fallback.image ?? "citeark-agent/runtime:0.2.0",
|
|
355
|
+
timeoutMinutes: executionTimeoutMinutes(profile, fallback),
|
|
356
|
+
cpus: profile.cpuCores,
|
|
357
|
+
memoryGb: profile.memoryGb,
|
|
358
|
+
shmGb: profile.shmGb ?? fallback.shmGb ?? Math.min(8, profile.memoryGb),
|
|
359
|
+
gpu: profile.accelerator === "gpu" ? "all" : "none",
|
|
360
|
+
networkAccess: fallback.networkAccess ?? true,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function executionTimeoutMinutes(profile, fallback = {}) {
|
|
365
|
+
const profileTimeout = profile.timeoutMinutes
|
|
366
|
+
?? fallback.timeoutMinutes
|
|
367
|
+
?? LEGACY_EXECUTION_TIMEOUT_FALLBACK_MINUTES;
|
|
368
|
+
const contractTimeout = fallback.timeoutMinutes ?? profileTimeout;
|
|
369
|
+
return Math.min(profileTimeout, contractTimeout);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function publicProfile(profile) {
|
|
373
|
+
return {
|
|
374
|
+
id: profile.id,
|
|
375
|
+
label: profile.label ?? profile.id,
|
|
376
|
+
executor: profile.executor,
|
|
377
|
+
accelerator: profile.accelerator,
|
|
378
|
+
cpuCores: profile.cpuCores,
|
|
379
|
+
memoryGb: profile.memoryGb,
|
|
380
|
+
gpu: profile.gpu ?? null,
|
|
381
|
+
hourlyUsd: profile.hourlyUsd ?? null,
|
|
382
|
+
hourlyCny: profile.hourlyCny ?? null,
|
|
383
|
+
executorConfig: profile.executorConfig ? structuredClone(profile.executorConfig) : null,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function selectionReasonCodes(selected, requirement, profileOverride, selection) {
|
|
388
|
+
const codes = [profileOverride ? "manual-profile-override" : "compatible-preemption-safety-then-cost"];
|
|
389
|
+
if (selection.reason) codes.push(selection.reason);
|
|
390
|
+
if (selection.requiredExecutor) codes.push(`executor-${selection.requiredExecutor}`);
|
|
391
|
+
if (selection.requiredProvisioningModel) {
|
|
392
|
+
codes.push(`provisioning-${selection.requiredProvisioningModel.toLowerCase()}`);
|
|
393
|
+
}
|
|
394
|
+
codes.push(requirement.classification);
|
|
395
|
+
codes.push(selected.profile.accelerator === "gpu" ? "gpu-selected" : "cpu-selected");
|
|
396
|
+
if (selected.estimatedCostUsd === null && selected.estimatedCostCny === null) {
|
|
397
|
+
codes.push("pricing-incomplete");
|
|
398
|
+
}
|
|
399
|
+
if (selected.unsafeSpot) codes.push("preemption-risk-unavoidable");
|
|
400
|
+
else if (selected.profile.executorConfig?.provisioningModel !== "SPOT") codes.push("non-preemptible-profile-selected");
|
|
401
|
+
return codes;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function selectionRationale(selected, requirement, profileOverride, selection) {
|
|
405
|
+
const selectionPrefix = selection.requiredExecutor || selection.requiredProvisioningModel
|
|
406
|
+
? `Applied runtime fallback constraints${selection.requiredExecutor ? ` executor=${selection.requiredExecutor}` : ""}${selection.requiredProvisioningModel ? ` provisioningModel=${selection.requiredProvisioningModel}` : ""}${selection.reason ? ` after ${selection.reason}` : ""}. `
|
|
407
|
+
: "";
|
|
408
|
+
const prefix = selectionPrefix + (profileOverride
|
|
409
|
+
? `Profile ${selected.profile.id} was specified via entry parameters. `
|
|
410
|
+
: `Selected ${selected.profile.id} after applying ${requirement.classification}, CPU, and memory compatibility, preferring non-preemptible profiles over unsafe long-running Spot profiles, then estimated total cost. `);
|
|
411
|
+
const estimate = selected.estimatedCostUsd !== null
|
|
412
|
+
? `Estimated compute cost is ${selected.estimatedCostUsd} USD.`
|
|
413
|
+
: selected.estimatedCostCny !== null
|
|
414
|
+
? `Estimated compute cost is at most ${selected.estimatedCostCny} CNY using the profile price cap.`
|
|
415
|
+
: "The catalog lacks sufficient duration or pricing data, so the cost is marked unknown.";
|
|
416
|
+
const resilience = selected.unsafeSpot
|
|
417
|
+
? " No compatible non-Spot profile was selected even though the estimated duration exceeds the non-resumable Spot threshold; interruption may require restarting scientific computation."
|
|
418
|
+
: selected.profile.executorConfig?.provisioningModel !== "SPOT"
|
|
419
|
+
? " The selected profile avoids Spot preemption risk."
|
|
420
|
+
: "";
|
|
421
|
+
return `${prefix}${estimate}${resilience}`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function normalizeSelectionConstraints(value, defaults = {}) {
|
|
425
|
+
if (value !== undefined && value !== null && (typeof value !== "object" || Array.isArray(value))) {
|
|
426
|
+
throw new CiteArkError("计算选择约束必须是对象");
|
|
427
|
+
}
|
|
428
|
+
const source = value ?? {};
|
|
429
|
+
const requiredExecutor = typeof source.requiredExecutor === "string" && source.requiredExecutor.trim()
|
|
430
|
+
? source.requiredExecutor.trim()
|
|
431
|
+
: null;
|
|
432
|
+
const rawProvisioningModel = typeof source.requiredProvisioningModel === "string"
|
|
433
|
+
? source.requiredProvisioningModel.trim().toUpperCase()
|
|
434
|
+
: "";
|
|
435
|
+
if (rawProvisioningModel && !new Set(["STANDARD", "SPOT", "FLEX_START"]).has(rawProvisioningModel)) {
|
|
436
|
+
throw new CiteArkError("计算选择约束的 requiredProvisioningModel 无效");
|
|
437
|
+
}
|
|
438
|
+
const reason = typeof source.reason === "string" && source.reason.trim()
|
|
439
|
+
? source.reason.trim()
|
|
440
|
+
: null;
|
|
441
|
+
return {
|
|
442
|
+
requiredExecutor,
|
|
443
|
+
requiredProvisioningModel: rawProvisioningModel || null,
|
|
444
|
+
allowFallbackProfiles: source.allowFallbackProfiles === true
|
|
445
|
+
|| defaults.allowFallbackProfiles === true,
|
|
446
|
+
reason,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function roundMoney(value) {
|
|
451
|
+
return Number(value.toFixed(6));
|
|
452
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CiteArkError, sha256Value } from "../util.mjs";
|
|
2
|
+
|
|
3
|
+
// Work identity is scientific, not an experiment/claim id. Keep consumers so
|
|
4
|
+
// the same union drives the quote and the Agent's checkpoint reuse plan.
|
|
5
|
+
export function sharedWorkloads(experiments) {
|
|
6
|
+
const work = new Map();
|
|
7
|
+
for (const experiment of experiments) for (const item of experiment.compute?.workloads ?? []) {
|
|
8
|
+
const previous = work.get(item.id);
|
|
9
|
+
if (previous && sha256Value(previous.item) !== sha256Value(item)) {
|
|
10
|
+
throw new CiteArkError(`共享计算 ${item.id} 的定义不一致,不能合并估价`);
|
|
11
|
+
}
|
|
12
|
+
if (!previous) work.set(item.id, { item, experimentIds: new Set() });
|
|
13
|
+
work.get(item.id).experimentIds.add(experiment.id);
|
|
14
|
+
}
|
|
15
|
+
return [...work.values()].map(({ item, experimentIds }) => ({
|
|
16
|
+
...item, experimentIds: [...experimentIds].sort(),
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function workloadMinutes(item, accelerator) {
|
|
21
|
+
const durations = item.estimatedDurationMinutes ?? {};
|
|
22
|
+
// CPU-only work runs on the CPU of the shared GPU machine, at that machine's
|
|
23
|
+
// rate. An explicitly unknown GPU rate must stay unknown.
|
|
24
|
+
return accelerator === "gpu" && !Object.hasOwn(durations, "gpu")
|
|
25
|
+
? durations.cpu : durations[accelerator];
|
|
26
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { captureProcess } from "../process.mjs";
|
|
2
|
+
import { copyFile, mkdir, writeFile, rm } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { CiteArkError } from "../util.mjs";
|
|
5
|
+
|
|
6
|
+
export const REMOTE_STAGE_DIRECTORIES = Object.freeze([
|
|
7
|
+
"input",
|
|
8
|
+
"workspace",
|
|
9
|
+
"output",
|
|
10
|
+
"runtime-home",
|
|
11
|
+
"execution",
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
// These paths are reproducible caches or repository metadata, not scientific
|
|
15
|
+
// inputs. Keeping them out of every remote stage makes transfer cost depend on
|
|
16
|
+
// the fixed source snapshot instead of local package-manager and Agent caches.
|
|
17
|
+
export const REMOTE_STAGE_EXCLUDES = Object.freeze([
|
|
18
|
+
"runtime-home/codex/auth.json",
|
|
19
|
+
"runtime-home/.cache",
|
|
20
|
+
"runtime-home/.npm",
|
|
21
|
+
"runtime-home/.local/share/uv",
|
|
22
|
+
"runtime-home/.local/share/pnpm",
|
|
23
|
+
"runtime-home/.bun/install/cache",
|
|
24
|
+
"runtime-home/.config/opencode/node_modules",
|
|
25
|
+
"runtime-home/.local/share/opencode/log",
|
|
26
|
+
"runtime-home/.local/share/opencode/snapshot",
|
|
27
|
+
"runtime-home/.local/share/opencode/repos",
|
|
28
|
+
"workspace/repository/.git",
|
|
29
|
+
"workspace/repository/*/.git",
|
|
30
|
+
"workspace/repository/.cache",
|
|
31
|
+
"workspace/repository/*/.cache",
|
|
32
|
+
"workspace/repository/.mypy_cache",
|
|
33
|
+
"workspace/repository/*/.mypy_cache",
|
|
34
|
+
"workspace/repository/.pytest_cache",
|
|
35
|
+
"workspace/repository/*/.pytest_cache",
|
|
36
|
+
"workspace/repository/.venv",
|
|
37
|
+
"workspace/repository/*/.venv",
|
|
38
|
+
"workspace/repository/__pycache__",
|
|
39
|
+
"workspace/repository/*/__pycache__",
|
|
40
|
+
"workspace/repository/node_modules",
|
|
41
|
+
"workspace/repository/*/node_modules",
|
|
42
|
+
"workspace/repository/.env",
|
|
43
|
+
"workspace/repository/.env.*",
|
|
44
|
+
"workspace/repository/*/.env",
|
|
45
|
+
"workspace/repository/*/.env.*",
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
export async function createRemoteStageArchive({
|
|
49
|
+
runDirectory,
|
|
50
|
+
destination,
|
|
51
|
+
label = "远程",
|
|
52
|
+
capture = captureProcess,
|
|
53
|
+
paperBudget,
|
|
54
|
+
}) {
|
|
55
|
+
await mkdir(path.join(runDirectory, "input"), { recursive: true });
|
|
56
|
+
await copyFile(new URL("../../docker/claude-code/codex-pro-relay.mjs", import.meta.url),
|
|
57
|
+
path.join(runDirectory, "input/runner-provider-relay.mjs"));
|
|
58
|
+
await copyFile(new URL("./codex-account-session.mjs", import.meta.url), path.join(runDirectory, "input/runner-codex-account.mjs"));
|
|
59
|
+
await copyFile(new URL("../runtime/codex-capacity-retry.mjs", import.meta.url), path.join(runDirectory, "input/runner-codex-capacity-retry.mjs"));
|
|
60
|
+
const budgetPath = path.join(runDirectory, "input/runner-paper-budget.json");
|
|
61
|
+
if (paperBudget) await writeFile(budgetPath, JSON.stringify(paperBudget), { mode: 0o600 });
|
|
62
|
+
for (const relative of ["scripts/check-execution-feasibility.mjs", "src/contracts/execution-workload.mjs"]) {
|
|
63
|
+
const target = path.join(runDirectory, "input/runtime-tools", relative);
|
|
64
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
65
|
+
await copyFile(new URL(`../../${relative}`, import.meta.url), target);
|
|
66
|
+
}
|
|
67
|
+
let result;
|
|
68
|
+
try { result = await capture("tar", [
|
|
69
|
+
"-czf",
|
|
70
|
+
destination,
|
|
71
|
+
...REMOTE_STAGE_EXCLUDES.map((pattern) => `--exclude=${pattern}`),
|
|
72
|
+
"-C",
|
|
73
|
+
runDirectory,
|
|
74
|
+
...REMOTE_STAGE_DIRECTORIES,
|
|
75
|
+
]); } finally { await rm(budgetPath, { force: true }); }
|
|
76
|
+
if (result.code !== 0) {
|
|
77
|
+
throw new CiteArkError(`创建 ${label} 暂存包失败:\n${result.stderr.trim()}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { mergeExecutionGraphContexts } from '../research/execution-graph-context.mjs';
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createExecutionContract, validateExecutionContract } from "./execution-contract.mjs";
|
|
4
|
+
import { reproductionCampaign } from "../compute/campaign.mjs";
|
|
5
|
+
import { resolveComputeRequirement } from "../compute/requirements.mjs";
|
|
6
|
+
import { sharedWorkloads } from "../compute/shared-workloads.mjs";
|
|
7
|
+
import { sha256Value, writeJson } from "../util.mjs";
|
|
8
|
+
|
|
9
|
+
export function campaignTargetKey(target) { return `${target.experimentId}:${target.claimId}`; }
|
|
10
|
+
export function campaignTargetDirectory(directory, target) {
|
|
11
|
+
return path.join(directory, "campaign-targets", sha256Value(campaignTargetKey(target)).slice(0, 24));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** All individual policy commitments are created before the shared Agent starts. */
|
|
15
|
+
export async function createCampaignContract({ research, researchPath, targets, directory, outputPath, tolerance }) {
|
|
16
|
+
const campaign = reproductionCampaign(research, targets);
|
|
17
|
+
const members = [];
|
|
18
|
+
for (const target of campaign.targets) {
|
|
19
|
+
const targetDirectory = campaignTargetDirectory(directory, target);
|
|
20
|
+
const { contract } = await createExecutionContract({ researchPath, claimId: target.claimId,
|
|
21
|
+
experimentId: target.experimentId, restrictToAssignedMeasurements: true, tolerance,
|
|
22
|
+
outputPath: path.join(targetDirectory, "contract.json"), policyPath: path.join(targetDirectory, "policy.json") });
|
|
23
|
+
const measurementMap = Object.fromEntries(contract.measurements.map(m =>
|
|
24
|
+
[m.measurementId, `${target.experimentId}::${m.measurementId}`]));
|
|
25
|
+
members.push({ target, contract, measurementMap });
|
|
26
|
+
}
|
|
27
|
+
const primary = members[0].contract;
|
|
28
|
+
const measurements = [...new Map(members.flatMap(member => member.contract.measurements.map(m => {
|
|
29
|
+
const measurement = { ...m, measurementId: member.measurementMap[m.measurementId] };
|
|
30
|
+
return [measurement.measurementId, measurement];
|
|
31
|
+
}))).values()];
|
|
32
|
+
const { contractDigest: ignored, ...body } = primary;
|
|
33
|
+
const researchGraph = mergeExecutionGraphContexts(members.map(member => member.contract));
|
|
34
|
+
const contractBody = { ...body, name: campaign.id,
|
|
35
|
+
...(researchGraph ? { researchGraph } : {}),
|
|
36
|
+
research: { ...primary.research, measurementIds: measurements.map(m => m.measurementId),
|
|
37
|
+
claimMeasurementIds: measurements.map(m => m.measurementId), uncoveredMeasurementIds: [], claimCoverage: "complete",
|
|
38
|
+
...(campaign.claimIds.length > 1 ? { executionClaimIds: campaign.claimIds } : {}) },
|
|
39
|
+
protocol: campaign.experiment.protocol, computeRequirement: resolveComputeRequirement(campaign.experiment),
|
|
40
|
+
environment: { ...primary.environment, timeoutMinutes: campaign.timeoutMinutes,
|
|
41
|
+
cpus: campaign.experiment.compute.cpuCores, memoryGb: campaign.experiment.compute.memoryGb,
|
|
42
|
+
gpu: campaign.experiment.compute.accelerator === "none" ? "none" : "all" },
|
|
43
|
+
measurements,
|
|
44
|
+
workspacePlan: { ...primary.workspacePlan, version: 2,
|
|
45
|
+
measurements: measurements.map(({ parser, ...m }) => m) },
|
|
46
|
+
campaign: { version: 1, id: campaign.id, members, workloads: sharedWorkloads(campaign.experiments) },
|
|
47
|
+
};
|
|
48
|
+
const contract = { ...contractBody, contractDigest: `sha256:${sha256Value(contractBody)}` };
|
|
49
|
+
validateExecutionContract(contract);
|
|
50
|
+
await writeJson(outputPath, contract);
|
|
51
|
+
return { contract, campaign };
|
|
52
|
+
}
|