@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,843 @@
|
|
|
1
|
+
import { AutoDlApiError, AutoDlProClient } from "../autodl/client.mjs";
|
|
2
|
+
import { AutoDlSshClient, parseAutoDlSshConnection } from "../autodl/ssh.mjs";
|
|
3
|
+
import { GcpSecretManagerClient } from "../gcp/secret-manager.mjs";
|
|
4
|
+
import { remoteProviderEnvironment } from "./gcp-batch-job.mjs";
|
|
5
|
+
import { CiteArkError, shellQuote } from "../util.mjs";
|
|
6
|
+
|
|
7
|
+
export function resolveAutoDlProConfig({ bundle, options = {}, requireToken = true }) {
|
|
8
|
+
const profile = bundle.computeDecision?.selectedProfile?.executorConfig ?? {};
|
|
9
|
+
const computeProfileId = first(
|
|
10
|
+
options.computeProfileId,
|
|
11
|
+
bundle.computeDecision?.selectedProfile?.id,
|
|
12
|
+
);
|
|
13
|
+
const apiToken = first(options.apiToken, process.env.AUTODL_API_TOKEN);
|
|
14
|
+
const legacyImageUuid = first(
|
|
15
|
+
options.imageUuid,
|
|
16
|
+
profile.imageUuid,
|
|
17
|
+
process.env.AUTODL_IMAGE_UUID,
|
|
18
|
+
);
|
|
19
|
+
const legacyDataCenterList = stringList(
|
|
20
|
+
options.dataCenterList
|
|
21
|
+
?? profile.dataCenterList
|
|
22
|
+
?? commaList(process.env.AUTODL_DATA_CENTER_LIST),
|
|
23
|
+
);
|
|
24
|
+
const regionImageCandidates = normalizeRegionImageCandidates(
|
|
25
|
+
options.regionImageCandidates
|
|
26
|
+
?? profile.regionImageCandidates
|
|
27
|
+
?? parseAutoDlRegionImagePairs(process.env.AUTODL_REGION_IMAGE_PAIRS),
|
|
28
|
+
{
|
|
29
|
+
imageUuid: legacyImageUuid,
|
|
30
|
+
dataCenterList: legacyDataCenterList,
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
const primaryRegionImage = regionImageCandidates?.[0] ?? null;
|
|
34
|
+
const imageUuid = primaryRegionImage?.imageUuid ?? legacyImageUuid;
|
|
35
|
+
const dataCenterList = primaryRegionImage?.dataCenterList ?? legacyDataCenterList;
|
|
36
|
+
const gpuSpecUuid = first(
|
|
37
|
+
options.forceGpuSpecOverride ? options.gpuSpecUuid : undefined,
|
|
38
|
+
profile.gpuSpecUuid,
|
|
39
|
+
options.gpuSpecUuid,
|
|
40
|
+
process.env.AUTODL_GPU_SPEC_UUID,
|
|
41
|
+
);
|
|
42
|
+
const gpuSpecFallbackUuids = uniqueStrings(stringList(
|
|
43
|
+
(options.forceGpuSpecOverride
|
|
44
|
+
? options.gpuSpecFallbackUuids
|
|
45
|
+
: profile.gpuSpecFallbackUuids ?? options.gpuSpecFallbackUuids)
|
|
46
|
+
?? commaList(process.env.AUTODL_GPU_SPEC_FALLBACK_UUIDS),
|
|
47
|
+
) ?? []).filter((value) => value !== gpuSpecUuid);
|
|
48
|
+
const providerSecret = first(
|
|
49
|
+
options.providerSecret,
|
|
50
|
+
process.env.CITEARK_GCP_PROVIDER_SECRET,
|
|
51
|
+
);
|
|
52
|
+
const liguseSgTunnelSecretResource = first(
|
|
53
|
+
options.liguseSgTunnelSecretResource,
|
|
54
|
+
process.env.CITEARK_LIGUSE_SG_TUNNEL_SECRET,
|
|
55
|
+
);
|
|
56
|
+
const credentialSource = options.credentialSource ?? profile.credentialSource ?? 'gcp-secret';
|
|
57
|
+
const issues = [];
|
|
58
|
+
if (!['gcp-secret', 'task-key'].includes(credentialSource)) issues.push('credentialSource must be gcp-secret or task-key');
|
|
59
|
+
if (credentialSource === 'task-key' && options.allowRemoteModelKey !== true) issues.push('Sending a model key to an AutoDL task requires --allow-remote-model-key');
|
|
60
|
+
if (credentialSource === 'task-key' && !bundle.runtimeAgent?.api?.apiKey) issues.push('The selected model configuration has no API key');
|
|
61
|
+
if (requireToken && !apiToken) issues.push("缺少 AUTODL_API_TOKEN");
|
|
62
|
+
if (!imageUuid) issues.push("缺少 AutoDL 私有镜像 UUID");
|
|
63
|
+
if (!gpuSpecUuid) issues.push("缺少 AutoDL GPU 规格 UUID");
|
|
64
|
+
if (credentialSource === 'gcp-secret' && !providerSecret) issues.push("Provide a Secret Manager reference or explicitly configure task-key credentials");
|
|
65
|
+
if (issues.length) {
|
|
66
|
+
throw new TypeError(`AutoDL Pro 执行参数无效:\n- ${issues.join("\n- ")}`);
|
|
67
|
+
}
|
|
68
|
+
const hourlyCny = optionalNumber(
|
|
69
|
+
options.hourlyCny ?? bundle.computeDecision?.selectedProfile?.hourlyCny,
|
|
70
|
+
"hourlyCny",
|
|
71
|
+
);
|
|
72
|
+
const maxHourlyCny = optionalNumber(
|
|
73
|
+
options.maxHourlyCny ?? profile.maxHourlyCny ?? process.env.AUTODL_MAX_HOURLY_CNY,
|
|
74
|
+
"maxHourlyCny",
|
|
75
|
+
);
|
|
76
|
+
const stagingBucket = first(
|
|
77
|
+
options.stagingBucket,
|
|
78
|
+
profile.stagingBucket,
|
|
79
|
+
process.env.CITEARK_GCP_STAGING_BUCKET,
|
|
80
|
+
);
|
|
81
|
+
const stageSigningServiceAccount = first(
|
|
82
|
+
options.stageSigningServiceAccount,
|
|
83
|
+
profile.stageSigningServiceAccount,
|
|
84
|
+
process.env.AUTODL_STAGE_SIGNING_SERVICE_ACCOUNT,
|
|
85
|
+
process.env.CITEARK_GCP_BATCH_SERVICE_ACCOUNT,
|
|
86
|
+
);
|
|
87
|
+
const waitTimeoutMinutes = positiveInteger(
|
|
88
|
+
options.waitTimeoutMinutes ?? profile.waitTimeoutMinutes,
|
|
89
|
+
Math.ceil(bundle.executionEnvironment.timeoutMinutes + 30),
|
|
90
|
+
"waitTimeoutMinutes",
|
|
91
|
+
);
|
|
92
|
+
const startupTimeoutMinutes = positiveInteger(
|
|
93
|
+
options.startupTimeoutMinutes ?? profile.startupTimeoutMinutes,
|
|
94
|
+
15,
|
|
95
|
+
"startupTimeoutMinutes",
|
|
96
|
+
);
|
|
97
|
+
const startupMaximumTimeoutMinutes = Math.max(
|
|
98
|
+
startupTimeoutMinutes,
|
|
99
|
+
positiveInteger(
|
|
100
|
+
options.startupMaximumTimeoutMinutes
|
|
101
|
+
?? profile.startupMaximumTimeoutMinutes
|
|
102
|
+
?? process.env.AUTODL_STARTUP_MAXIMUM_TIMEOUT_MINUTES,
|
|
103
|
+
Math.max(180, startupTimeoutMinutes),
|
|
104
|
+
"startupMaximumTimeoutMinutes",
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
return {
|
|
108
|
+
apiToken,
|
|
109
|
+
apiBaseUrl: first(options.apiBaseUrl, profile.apiBaseUrl) ?? "https://api.autodl.com",
|
|
110
|
+
imageUuid,
|
|
111
|
+
regionImageCandidates,
|
|
112
|
+
computeProfileId,
|
|
113
|
+
gpuSpecUuid,
|
|
114
|
+
gpuSpecFallbackUuids,
|
|
115
|
+
providerSecret,
|
|
116
|
+
credentialSource,
|
|
117
|
+
allowRemoteModelKey: options.allowRemoteModelKey === true,
|
|
118
|
+
liguseSgTunnelSecretResource,
|
|
119
|
+
networkTurboScript: first(
|
|
120
|
+
options.networkTurboScript,
|
|
121
|
+
process.env.AUTODL_NETWORK_TURBO_SCRIPT,
|
|
122
|
+
) ?? "/etc/network_turbo",
|
|
123
|
+
dataCenterList,
|
|
124
|
+
gpuCount: positiveInteger(options.gpuCount ?? profile.gpuCount, 1, "gpuCount"),
|
|
125
|
+
cudaVersionFrom: positiveInteger(
|
|
126
|
+
options.cudaVersionFrom ?? profile.cudaVersionFrom,
|
|
127
|
+
118,
|
|
128
|
+
"cudaVersionFrom",
|
|
129
|
+
),
|
|
130
|
+
expandSystemDiskGb: nonNegativeInteger(
|
|
131
|
+
options.expandSystemDiskGb ?? profile.expandSystemDiskGb,
|
|
132
|
+
0,
|
|
133
|
+
"expandSystemDiskGb",
|
|
134
|
+
),
|
|
135
|
+
waitTimeoutMinutes,
|
|
136
|
+
startupTimeoutMinutes,
|
|
137
|
+
startupMaximumTimeoutMinutes,
|
|
138
|
+
remoteInfrastructureRetries: nonNegativeInteger(
|
|
139
|
+
options.remoteInfrastructureRetries
|
|
140
|
+
?? profile.remoteInfrastructureRetries
|
|
141
|
+
?? process.env.AUTODL_REMOTE_INFRASTRUCTURE_RETRIES,
|
|
142
|
+
2,
|
|
143
|
+
"remoteInfrastructureRetries",
|
|
144
|
+
),
|
|
145
|
+
pollIntervalMs: positiveInteger(
|
|
146
|
+
options.pollIntervalMs ?? profile.pollIntervalMs,
|
|
147
|
+
5_000,
|
|
148
|
+
"pollIntervalMs",
|
|
149
|
+
),
|
|
150
|
+
hourlyUsd: bundle.computeDecision?.selectedProfile?.hourlyUsd ?? null,
|
|
151
|
+
hourlyCny,
|
|
152
|
+
maxHourlyCny,
|
|
153
|
+
stagingBucket,
|
|
154
|
+
stageSigningServiceAccount,
|
|
155
|
+
stageSignedUrlExpiresSeconds: positiveInteger(
|
|
156
|
+
options.stageSignedUrlExpiresSeconds
|
|
157
|
+
?? profile.stageSignedUrlExpiresSeconds
|
|
158
|
+
?? process.env.AUTODL_STAGE_SIGNED_URL_EXPIRES_SECONDS,
|
|
159
|
+
30 * 60,
|
|
160
|
+
"stageSignedUrlExpiresSeconds",
|
|
161
|
+
),
|
|
162
|
+
maximumLifetimeMinutes: positiveInteger(
|
|
163
|
+
options.maximumLifetimeMinutes
|
|
164
|
+
?? profile.maximumLifetimeMinutes
|
|
165
|
+
?? process.env.AUTODL_MAXIMUM_LIFETIME_MINUTES,
|
|
166
|
+
waitTimeoutMinutes + 60,
|
|
167
|
+
"maximumLifetimeMinutes",
|
|
168
|
+
),
|
|
169
|
+
lifecycleHeartbeatIntervalMs: positiveInteger(
|
|
170
|
+
options.lifecycleHeartbeatIntervalMs
|
|
171
|
+
?? process.env.AUTODL_LIFECYCLE_HEARTBEAT_INTERVAL_MS,
|
|
172
|
+
2 * 60_000,
|
|
173
|
+
"lifecycleHeartbeatIntervalMs",
|
|
174
|
+
),
|
|
175
|
+
maximumExternalBytes: positiveInteger(
|
|
176
|
+
options.maximumExternalBytes
|
|
177
|
+
?? profile.maximumExternalBytes
|
|
178
|
+
?? process.env.AUTODL_MAXIMUM_EXTERNAL_BYTES,
|
|
179
|
+
8 * 1024 ** 3,
|
|
180
|
+
"maximumExternalBytes",
|
|
181
|
+
),
|
|
182
|
+
instanceNamePrefix: first(options.instanceNamePrefix, profile.instanceNamePrefix)
|
|
183
|
+
?? "citeark",
|
|
184
|
+
lifecycle: options.lifecycle ?? null,
|
|
185
|
+
keepInstance: options.keepInstance ?? profile.keepInstance ?? false,
|
|
186
|
+
keepFailedInstance: options.keepFailedInstance ?? profile.keepFailedInstance ?? false,
|
|
187
|
+
checkpointing: {
|
|
188
|
+
enabled: options.checkpointEnabled !== false && profile.checkpointEnabled !== false,
|
|
189
|
+
criticalIntervalSeconds: positiveInteger(
|
|
190
|
+
options.criticalCheckpointIntervalSeconds
|
|
191
|
+
?? profile.criticalCheckpointIntervalSeconds,
|
|
192
|
+
20,
|
|
193
|
+
"criticalCheckpointIntervalSeconds",
|
|
194
|
+
),
|
|
195
|
+
fullIntervalSeconds: positiveInteger(
|
|
196
|
+
options.fullCheckpointIntervalSeconds ?? profile.fullCheckpointIntervalSeconds,
|
|
197
|
+
300,
|
|
198
|
+
"fullCheckpointIntervalSeconds",
|
|
199
|
+
),
|
|
200
|
+
retention: positiveInteger(
|
|
201
|
+
options.checkpointRetention ?? profile.checkpointRetention,
|
|
202
|
+
3,
|
|
203
|
+
"checkpointRetention",
|
|
204
|
+
),
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function autoDlInstanceRequest(config, runId) {
|
|
210
|
+
const maximumLifetimeMinutes = positiveInteger(
|
|
211
|
+
config.maximumLifetimeMinutes,
|
|
212
|
+
config.waitTimeoutMinutes + 60,
|
|
213
|
+
"maximumLifetimeMinutes",
|
|
214
|
+
);
|
|
215
|
+
return {
|
|
216
|
+
dataCenterList: config.dataCenterList,
|
|
217
|
+
gpuCount: config.gpuCount,
|
|
218
|
+
expandSystemDiskGb: config.expandSystemDiskGb,
|
|
219
|
+
gpuSpecUuid: config.gpuSpecUuid,
|
|
220
|
+
imageUuid: config.imageUuid,
|
|
221
|
+
cudaVersionFrom: config.cudaVersionFrom,
|
|
222
|
+
instanceName: `${safeIdentifier(config.instanceNamePrefix)}-${safeIdentifier(runId)}`.slice(0, 80),
|
|
223
|
+
startCommand: `exec timeout --signal=TERM --kill-after=60s ${maximumLifetimeMinutes}m sleep infinity`,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export async function acquireAutoDlProLease({
|
|
228
|
+
config,
|
|
229
|
+
runId,
|
|
230
|
+
clients = {},
|
|
231
|
+
progress = () => {},
|
|
232
|
+
}) {
|
|
233
|
+
const api = clients.api ?? new AutoDlProClient({
|
|
234
|
+
token: config.apiToken,
|
|
235
|
+
baseUrl: config.apiBaseUrl,
|
|
236
|
+
});
|
|
237
|
+
let request;
|
|
238
|
+
let instanceUuid;
|
|
239
|
+
let instanceCreatedAtMs;
|
|
240
|
+
let selectedConfig;
|
|
241
|
+
const instanceCandidates = autoDlInstanceCandidates(config, runId);
|
|
242
|
+
let lastInventoryError = null;
|
|
243
|
+
for (const [candidateIndex, candidateConfig] of instanceCandidates.entries()) {
|
|
244
|
+
const candidateRequest = autoDlInstanceRequest(candidateConfig, runId);
|
|
245
|
+
try {
|
|
246
|
+
instanceUuid = await api.createInstance(candidateRequest);
|
|
247
|
+
instanceCreatedAtMs = Date.now();
|
|
248
|
+
selectedConfig = candidateConfig;
|
|
249
|
+
request = candidateRequest;
|
|
250
|
+
break;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (!isInventoryError(error)) throw error;
|
|
253
|
+
lastInventoryError = error;
|
|
254
|
+
const nextCandidate = instanceCandidates[candidateIndex + 1];
|
|
255
|
+
if (nextCandidate) {
|
|
256
|
+
progress(
|
|
257
|
+
`AutoDL ${candidateConfig.dataCenterList.join("/")} 的 ${candidateConfig.gpuSpecUuid} 暂无库存,尝试 ${nextCandidate.dataCenterList.join("/")} 的 ${nextCandidate.gpuSpecUuid}`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (!instanceUuid || !request || !selectedConfig) {
|
|
263
|
+
throw lastInventoryError ?? new CiteArkError("AutoDL 没有可用的算力规格");
|
|
264
|
+
}
|
|
265
|
+
let ssh = null;
|
|
266
|
+
let snapshot = null;
|
|
267
|
+
let released = false;
|
|
268
|
+
try {
|
|
269
|
+
await invokeLifecycle(selectedConfig.lifecycle, "created", {
|
|
270
|
+
instanceUuid,
|
|
271
|
+
runId,
|
|
272
|
+
request,
|
|
273
|
+
maximumLifetimeSeconds: selectedConfig.maximumLifetimeMinutes * 60,
|
|
274
|
+
});
|
|
275
|
+
progress(`AutoDL ${runId}: 已创建实例 ${instanceUuid},等待 GPU 节点启动`);
|
|
276
|
+
await waitForAutoDlStartup({ api, instanceUuid, config: selectedConfig, progress });
|
|
277
|
+
const deadline = Date.now() + selectedConfig.startupTimeoutMinutes * 60_000;
|
|
278
|
+
let connection;
|
|
279
|
+
while (Date.now() < deadline) {
|
|
280
|
+
snapshot = await api.getInstance(instanceUuid);
|
|
281
|
+
try {
|
|
282
|
+
connection = parseAutoDlSshConnection(snapshot);
|
|
283
|
+
break;
|
|
284
|
+
} catch {
|
|
285
|
+
await sleep(selectedConfig.pollIntervalMs);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (!connection) throw new CiteArkError(`AutoDL 实例 ${instanceUuid} 未返回 SSH 信息`);
|
|
289
|
+
const observedHourlyCny = autoDlHourlyCny(snapshot);
|
|
290
|
+
if (
|
|
291
|
+
typeof selectedConfig.maxHourlyCny === "number"
|
|
292
|
+
&& typeof observedHourlyCny === "number"
|
|
293
|
+
&& observedHourlyCny > selectedConfig.maxHourlyCny
|
|
294
|
+
) {
|
|
295
|
+
throw new CiteArkError(
|
|
296
|
+
`AutoDL 实例价格 ¥${observedHourlyCny.toFixed(3)}/时超过预算上限 ¥${selectedConfig.maxHourlyCny.toFixed(3)}/时`,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
ssh = clients.sshFactory
|
|
300
|
+
? clients.sshFactory(connection)
|
|
301
|
+
: new AutoDlSshClient(connection);
|
|
302
|
+
await retry(
|
|
303
|
+
() => ssh.connect(),
|
|
304
|
+
{ attempts: 8, delayMs: selectedConfig.pollIntervalMs },
|
|
305
|
+
);
|
|
306
|
+
const readiness = await ssh.exec(
|
|
307
|
+
"command -v bash node python3 jq tar git timeout ssh curl uv sha256sum >/dev/null && id node >/dev/null && test -x /opt/citeark/venv/bin/python && test -r /opt/citeark/runtime-manifest.json",
|
|
308
|
+
{ timeoutMs: 60_000 },
|
|
309
|
+
);
|
|
310
|
+
if (readiness.code !== 0) {
|
|
311
|
+
throw new CiteArkError(
|
|
312
|
+
"AutoDL 私有镜像缺少 CiteArk Runner 或科研基线依赖:需要 bash、node、python3、jq、tar、git、timeout、ssh、curl、uv、sha256sum、node 用户、/opt/citeark/venv 和运行时清单",
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
const lease = new AutoDlProLease({
|
|
316
|
+
api,
|
|
317
|
+
ssh,
|
|
318
|
+
config: selectedConfig,
|
|
319
|
+
instanceUuid,
|
|
320
|
+
snapshot,
|
|
321
|
+
observedHourlyCny,
|
|
322
|
+
request,
|
|
323
|
+
runId,
|
|
324
|
+
progress,
|
|
325
|
+
createdAtMs: instanceCreatedAtMs,
|
|
326
|
+
});
|
|
327
|
+
await lease.startLifecycle();
|
|
328
|
+
return lease;
|
|
329
|
+
} catch (error) {
|
|
330
|
+
ssh?.close();
|
|
331
|
+
let cleanupError = null;
|
|
332
|
+
await powerOffAndMaybeRelease({
|
|
333
|
+
api,
|
|
334
|
+
instanceUuid,
|
|
335
|
+
config: selectedConfig,
|
|
336
|
+
release: !config.keepFailedInstance,
|
|
337
|
+
progress,
|
|
338
|
+
}).then((value) => { released = value.released; }).catch((releaseError) => {
|
|
339
|
+
cleanupError = releaseError;
|
|
340
|
+
});
|
|
341
|
+
await invokeLifecycle(selectedConfig.lifecycle, "finished", {
|
|
342
|
+
instanceUuid,
|
|
343
|
+
runId,
|
|
344
|
+
released,
|
|
345
|
+
error: cleanupError instanceof Error
|
|
346
|
+
? cleanupError.message
|
|
347
|
+
: error instanceof Error ? error.message : String(error),
|
|
348
|
+
}).catch(() => undefined);
|
|
349
|
+
if (released) progress(`AutoDL ${instanceUuid}: 启动失败,实例已释放`);
|
|
350
|
+
throw error;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Build the complete region × compatible-GPU candidate list. Regions have
|
|
356
|
+
* equal priority: a stable run-id hash rotates the first region, then each GPU
|
|
357
|
+
* specification is tried across every region before moving to the next one.
|
|
358
|
+
*/
|
|
359
|
+
export function autoDlInstanceCandidates(config, runId) {
|
|
360
|
+
const regions = configuredRegionImageCandidates(config);
|
|
361
|
+
const gpuSpecUuids = uniqueStrings([
|
|
362
|
+
config.gpuSpecUuid,
|
|
363
|
+
...(config.gpuSpecFallbackUuids ?? []),
|
|
364
|
+
]);
|
|
365
|
+
const offset = stableRegionOffset(runId, regions.length);
|
|
366
|
+
const orderedRegions = regions.map((_, index) =>
|
|
367
|
+
regions[(index + offset) % regions.length]
|
|
368
|
+
);
|
|
369
|
+
return gpuSpecUuids.flatMap((gpuSpecUuid) =>
|
|
370
|
+
orderedRegions.map((regionImage) => ({
|
|
371
|
+
...config,
|
|
372
|
+
imageUuid: regionImage.imageUuid,
|
|
373
|
+
dataCenterList: regionImage.dataCenterList,
|
|
374
|
+
gpuSpecUuid,
|
|
375
|
+
}))
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function isInventoryError(error) {
|
|
380
|
+
return error instanceof AutoDlApiError
|
|
381
|
+
&& /库存|stock|capacity/i.test(error.message);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export class AutoDlProLease {
|
|
385
|
+
constructor({
|
|
386
|
+
api,
|
|
387
|
+
ssh,
|
|
388
|
+
config,
|
|
389
|
+
instanceUuid,
|
|
390
|
+
snapshot,
|
|
391
|
+
observedHourlyCny,
|
|
392
|
+
request,
|
|
393
|
+
runId,
|
|
394
|
+
progress,
|
|
395
|
+
createdAtMs = Date.now(),
|
|
396
|
+
}) {
|
|
397
|
+
this.api = api;
|
|
398
|
+
this.ssh = ssh;
|
|
399
|
+
this.config = config;
|
|
400
|
+
this.instanceUuid = instanceUuid;
|
|
401
|
+
this.snapshot = snapshot;
|
|
402
|
+
this.observedHourlyCny = observedHourlyCny;
|
|
403
|
+
this.request = request;
|
|
404
|
+
this.runId = runId;
|
|
405
|
+
this.progress = progress;
|
|
406
|
+
this.closed = false;
|
|
407
|
+
this.released = false;
|
|
408
|
+
this.lifecycleHeartbeat = null;
|
|
409
|
+
this.createdAtMs = createdAtMs;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async startLifecycle() {
|
|
413
|
+
await this.heartbeatLifecycle();
|
|
414
|
+
if (!this.config.lifecycle) return;
|
|
415
|
+
this.lifecycleHeartbeat = setInterval(() => {
|
|
416
|
+
this.heartbeatLifecycle().catch((error) => {
|
|
417
|
+
this.progress(
|
|
418
|
+
`AutoDL ${this.instanceUuid}: 持久化租约心跳失败:${error instanceof Error ? error.message : String(error)}`,
|
|
419
|
+
);
|
|
420
|
+
});
|
|
421
|
+
}, this.config.lifecycleHeartbeatIntervalMs);
|
|
422
|
+
this.lifecycleHeartbeat.unref?.();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async heartbeatLifecycle() {
|
|
426
|
+
await invokeLifecycle(this.config.lifecycle, "heartbeat", {
|
|
427
|
+
instanceUuid: this.instanceUuid,
|
|
428
|
+
runId: this.runId,
|
|
429
|
+
observedHourlyCny: this.observedHourlyCny,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
publicRecord() {
|
|
434
|
+
return {
|
|
435
|
+
instanceUuid: this.instanceUuid,
|
|
436
|
+
region: text(this.snapshot?.region_name) ?? text(this.snapshot?.region_sign),
|
|
437
|
+
gpu: text(this.snapshot?.snapshot_gpu_alias_name),
|
|
438
|
+
observedHourlyCny: this.observedHourlyCny,
|
|
439
|
+
imageUuid: this.config.imageUuid,
|
|
440
|
+
gpuSpecUuid: this.config.gpuSpecUuid,
|
|
441
|
+
hostKeySha256: this.ssh.fingerprint ?? null,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async isHealthy() {
|
|
446
|
+
if (this.closed) return false;
|
|
447
|
+
try {
|
|
448
|
+
const status = String(await this.api.getStatus(this.instanceUuid)).trim().toLowerCase();
|
|
449
|
+
if (status !== "running") return false;
|
|
450
|
+
const readiness = await this.ssh.exec("true", { timeoutMs: 30_000 });
|
|
451
|
+
return readiness.code === 0;
|
|
452
|
+
} catch {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async cleanTargetWorkspace(runId) {
|
|
458
|
+
if (this.closed) throw new CiteArkError(`AutoDL 实例 ${this.instanceUuid} 已关闭`);
|
|
459
|
+
const remoteDirectory = `/root/citeark/${safeIdentifier(runId)}`;
|
|
460
|
+
const cleanup = await this.ssh.exec(
|
|
461
|
+
`rm -rf -- /job ${shellQuote(remoteDirectory)}`,
|
|
462
|
+
{ timeoutMs: 60_000 },
|
|
463
|
+
);
|
|
464
|
+
if (cleanup.code !== 0) {
|
|
465
|
+
throw new CiteArkError(
|
|
466
|
+
`AutoDL 目标工作区清理失败:${cleanup.stderr.trim() || `退出码 ${cleanup.code}`}`,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async close({ successful = false } = {}) {
|
|
472
|
+
if (this.closed) return { released: this.released };
|
|
473
|
+
this.closed = true;
|
|
474
|
+
if (this.lifecycleHeartbeat) clearInterval(this.lifecycleHeartbeat);
|
|
475
|
+
this.ssh.close();
|
|
476
|
+
if (this.config.keepInstance) return { released: false };
|
|
477
|
+
const release = successful || !this.config.keepFailedInstance;
|
|
478
|
+
try {
|
|
479
|
+
const cleanup = await powerOffAndMaybeRelease({
|
|
480
|
+
api: this.api,
|
|
481
|
+
instanceUuid: this.instanceUuid,
|
|
482
|
+
config: this.config,
|
|
483
|
+
release,
|
|
484
|
+
progress: this.progress,
|
|
485
|
+
});
|
|
486
|
+
this.released = cleanup.released;
|
|
487
|
+
await invokeLifecycle(this.config.lifecycle, "finished", {
|
|
488
|
+
instanceUuid: this.instanceUuid,
|
|
489
|
+
runId: this.runId,
|
|
490
|
+
released: cleanup.released,
|
|
491
|
+
});
|
|
492
|
+
return cleanup;
|
|
493
|
+
} catch (error) {
|
|
494
|
+
await invokeLifecycle(this.config.lifecycle, "finished", {
|
|
495
|
+
instanceUuid: this.instanceUuid,
|
|
496
|
+
runId: this.runId,
|
|
497
|
+
released: false,
|
|
498
|
+
error: error instanceof Error ? error.message : String(error),
|
|
499
|
+
}).catch(() => undefined);
|
|
500
|
+
throw error;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export class AutoDlProLeasePool {
|
|
506
|
+
constructor({ acquireLease = acquireAutoDlProLease, progress = () => {} } = {}) {
|
|
507
|
+
this.acquireLease = acquireLease;
|
|
508
|
+
this.progress = progress;
|
|
509
|
+
this.lease = null;
|
|
510
|
+
this.borrowed = false;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async acquire({ config, runId, clients, progress = this.progress }) {
|
|
514
|
+
if (this.borrowed) throw new CiteArkError("AutoDL 共享租约仍被另一个目标占用");
|
|
515
|
+
if (this.lease) {
|
|
516
|
+
const compatible = canReuseAutoDlProLease(this.lease, config);
|
|
517
|
+
const healthy = compatible && await this.lease.isHealthy();
|
|
518
|
+
if (compatible && healthy) {
|
|
519
|
+
this.borrowed = true;
|
|
520
|
+
await this.lease.heartbeatLifecycle();
|
|
521
|
+
progress(`AutoDL ${runId}: 复用已运行实例 ${this.lease.instanceUuid}`);
|
|
522
|
+
return this.lease;
|
|
523
|
+
}
|
|
524
|
+
progress(
|
|
525
|
+
compatible
|
|
526
|
+
? `AutoDL ${this.lease.instanceUuid}: 健康检查失败,先释放再重建实例`
|
|
527
|
+
: `AutoDL ${this.lease.instanceUuid}: 与下一目标资源不兼容,先释放再创建实例`,
|
|
528
|
+
);
|
|
529
|
+
await this.#closeCurrent();
|
|
530
|
+
}
|
|
531
|
+
this.lease = await this.acquireLease({ config, runId, clients, progress });
|
|
532
|
+
this.borrowed = true;
|
|
533
|
+
return this.lease;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async release(lease, { reusable = true, runId } = {}) {
|
|
537
|
+
if (lease !== this.lease) {
|
|
538
|
+
return await lease.close({ successful: true });
|
|
539
|
+
}
|
|
540
|
+
try {
|
|
541
|
+
if (runId) await lease.cleanTargetWorkspace(runId);
|
|
542
|
+
} catch (error) {
|
|
543
|
+
reusable = false;
|
|
544
|
+
this.progress(
|
|
545
|
+
`AutoDL ${lease.instanceUuid}: 目标隔离清理失败,将释放实例:${error instanceof Error ? error.message : String(error)}`,
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
this.borrowed = false;
|
|
549
|
+
if (!reusable) return await this.#closeCurrent();
|
|
550
|
+
return { released: false };
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async close() {
|
|
554
|
+
this.borrowed = false;
|
|
555
|
+
await this.#closeCurrent();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async #closeCurrent() {
|
|
559
|
+
const lease = this.lease;
|
|
560
|
+
this.lease = null;
|
|
561
|
+
if (lease) return await lease.close({ successful: true });
|
|
562
|
+
return { released: false };
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function canReuseAutoDlProLease(lease, config, { now = Date.now() } = {}) {
|
|
567
|
+
if (!lease || lease.closed) return false;
|
|
568
|
+
const current = lease.config ?? {};
|
|
569
|
+
const allowedGpuSpecs = new Set([
|
|
570
|
+
config.gpuSpecUuid,
|
|
571
|
+
...(config.gpuSpecFallbackUuids ?? []),
|
|
572
|
+
]);
|
|
573
|
+
const requiredLifetimeMs = (config.waitTimeoutMinutes + 5) * 60_000;
|
|
574
|
+
const maximumLifetimeMs = current.maximumLifetimeMinutes * 60_000;
|
|
575
|
+
const ageMs = Math.max(0, now - lease.createdAtMs);
|
|
576
|
+
const regionImageCompatible = configuredRegionImageCandidates(config).some(
|
|
577
|
+
(candidate) =>
|
|
578
|
+
candidate.imageUuid === current.imageUuid
|
|
579
|
+
&& sameStringSet(candidate.dataCenterList, current.dataCenterList),
|
|
580
|
+
);
|
|
581
|
+
return current.computeProfileId === config.computeProfileId
|
|
582
|
+
&& current.apiBaseUrl === config.apiBaseUrl
|
|
583
|
+
&& regionImageCompatible
|
|
584
|
+
&& allowedGpuSpecs.has(current.gpuSpecUuid)
|
|
585
|
+
&& current.gpuCount === config.gpuCount
|
|
586
|
+
&& current.cudaVersionFrom === config.cudaVersionFrom
|
|
587
|
+
&& current.expandSystemDiskGb === config.expandSystemDiskGb
|
|
588
|
+
&& current.providerSecret === config.providerSecret
|
|
589
|
+
&& current.liguseSgTunnelSecretResource === config.liguseSgTunnelSecretResource
|
|
590
|
+
&& (typeof config.maxHourlyCny !== "number"
|
|
591
|
+
|| typeof lease.observedHourlyCny !== "number"
|
|
592
|
+
|| lease.observedHourlyCny <= config.maxHourlyCny)
|
|
593
|
+
&& ageMs + requiredLifetimeMs < maximumLifetimeMs;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async function waitForAutoDlStartup({ api, instanceUuid, config, progress }) {
|
|
597
|
+
const softTimeoutMs = config.startupTimeoutMinutes * 60_000;
|
|
598
|
+
const maximumTimeoutMs = config.startupMaximumTimeoutMinutes * 60_000;
|
|
599
|
+
const wait = async (timeoutMs) => await api.waitForStatus(instanceUuid, "running", {
|
|
600
|
+
timeoutMs,
|
|
601
|
+
pollIntervalMs: config.pollIntervalMs,
|
|
602
|
+
onStatus: (status) => progress(`AutoDL ${instanceUuid}: ${status}`),
|
|
603
|
+
});
|
|
604
|
+
try {
|
|
605
|
+
await wait(softTimeoutMs);
|
|
606
|
+
return;
|
|
607
|
+
} catch (error) {
|
|
608
|
+
if (
|
|
609
|
+
!(error instanceof AutoDlApiError)
|
|
610
|
+
|| !error.retryable
|
|
611
|
+
|| !/等待.*超时|wait.*timeout/i.test(error.message)
|
|
612
|
+
|| maximumTimeoutMs <= softTimeoutMs
|
|
613
|
+
) {
|
|
614
|
+
throw error;
|
|
615
|
+
}
|
|
616
|
+
const status = String(await api.getStatus(instanceUuid)).trim().toLowerCase();
|
|
617
|
+
if (status === "running") return;
|
|
618
|
+
if (!new Set(["creating", "sys_volume_creating", "starting", "pending"]).has(status)) {
|
|
619
|
+
throw error;
|
|
620
|
+
}
|
|
621
|
+
progress(
|
|
622
|
+
`AutoDL ${instanceUuid}: ${config.startupTimeoutMinutes} 分钟后仍处于 ${status},镜像或系统盘仍在准备;继续等待,硬上限 ${config.startupMaximumTimeoutMinutes} 分钟`,
|
|
623
|
+
);
|
|
624
|
+
await wait(maximumTimeoutMs - softTimeoutMs);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
async function invokeLifecycle(lifecycle, event, payload) {
|
|
629
|
+
const callback = lifecycle?.[event];
|
|
630
|
+
if (typeof callback === "function") await callback(payload);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export async function resolveAutoDlProviderEnvironment({
|
|
634
|
+
agent,
|
|
635
|
+
providerSecret,
|
|
636
|
+
secretManager,
|
|
637
|
+
credentialSource = 'gcp-secret',
|
|
638
|
+
allowRemoteModelKey = false,
|
|
639
|
+
}) {
|
|
640
|
+
if (credentialSource === 'task-key' && !allowRemoteModelKey) throw Error('Remote model credential transfer was not authorized for this task.');
|
|
641
|
+
const descriptor = remoteProviderEnvironment(agent, providerSecret);
|
|
642
|
+
const resolver = secretManager ?? new GcpSecretManagerClient();
|
|
643
|
+
const environment = { ...descriptor.variables };
|
|
644
|
+
const credentialName = environment.CITEARK_PROVIDER_SECRET_ENV_NAME;
|
|
645
|
+
if (!/^(ANTHROPIC_API_KEY|OPENAI_API_KEY|DEEPSEEK_API_KEY|CITEARK_API_KEY)$/.test(
|
|
646
|
+
String(credentialName ?? ""),
|
|
647
|
+
)) {
|
|
648
|
+
throw new CiteArkError("AutoDL 模型密钥环境变量名无效");
|
|
649
|
+
}
|
|
650
|
+
if (credentialSource === 'task-key') {
|
|
651
|
+
if (!agent.api?.apiKey) throw Error('The selected model configuration has no API key');
|
|
652
|
+
environment[credentialName] = agent.api.apiKey;
|
|
653
|
+
delete environment.CITEARK_PROVIDER_SECRET_RESOURCE;
|
|
654
|
+
} else environment[credentialName] = await resolver.accessVersion(providerSecret);
|
|
655
|
+
for (const [name, resource] of Object.entries(descriptor.secretVariables ?? {})) {
|
|
656
|
+
environment[name] = await resolver.accessVersion(resource);
|
|
657
|
+
}
|
|
658
|
+
return environment;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
export function shellEnvironmentFile(environment) {
|
|
662
|
+
const lines = ["# Generated for one AutoDL lease; never persist or publish this file."];
|
|
663
|
+
for (const [name, value] of Object.entries(environment)) {
|
|
664
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(name)) {
|
|
665
|
+
throw new TypeError(`AutoDL 环境变量名无效:${name}`);
|
|
666
|
+
}
|
|
667
|
+
lines.push(`export ${name}=${shellQuote(String(value))}`);
|
|
668
|
+
}
|
|
669
|
+
return `${lines.join("\n")}\n`;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
export function autoDlHourlyCny(snapshot) {
|
|
673
|
+
const raw = Number(snapshot?.payg_price);
|
|
674
|
+
if (!Number.isFinite(raw) || raw < 0) return null;
|
|
675
|
+
return Number.isInteger(raw) ? raw / 1000 : raw;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
async function powerOffAndMaybeRelease({ api, instanceUuid, config, release, progress }) {
|
|
679
|
+
await api.powerOff(instanceUuid).catch((error) => {
|
|
680
|
+
if (!/stopp|关机|released|释放/i.test(String(error?.message ?? ""))) throw error;
|
|
681
|
+
});
|
|
682
|
+
await api.waitForStatus(instanceUuid, ["stopped", "shutdown", "shutoff"], {
|
|
683
|
+
timeoutMs: 5 * 60_000,
|
|
684
|
+
pollIntervalMs: config.pollIntervalMs,
|
|
685
|
+
onStatus: (status) => progress(`AutoDL ${instanceUuid}: ${status}`),
|
|
686
|
+
}).catch(() => undefined);
|
|
687
|
+
if (!release) {
|
|
688
|
+
progress(`AutoDL ${instanceUuid}: 已关机并保留失败现场`);
|
|
689
|
+
return { released: false };
|
|
690
|
+
}
|
|
691
|
+
await api.release(instanceUuid);
|
|
692
|
+
progress(`AutoDL ${instanceUuid}: 已关机并释放`);
|
|
693
|
+
return { released: true };
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
async function retry(operation, { attempts, delayMs }) {
|
|
697
|
+
let lastError;
|
|
698
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
699
|
+
try {
|
|
700
|
+
return await operation();
|
|
701
|
+
} catch (error) {
|
|
702
|
+
lastError = error;
|
|
703
|
+
if (attempt < attempts) await sleep(delayMs);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
throw lastError;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function safeIdentifier(value) {
|
|
710
|
+
return String(value ?? "run")
|
|
711
|
+
.toLowerCase()
|
|
712
|
+
.replaceAll(/[^a-z0-9-]/g, "-")
|
|
713
|
+
.replaceAll(/^-+|-+$/g, "")
|
|
714
|
+
.slice(0, 60) || "run";
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function first(...values) {
|
|
718
|
+
return values.find((value) => typeof value === "string" && value.trim())?.trim() ?? null;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function stringList(value) {
|
|
722
|
+
if (value === undefined || value === null || value.length === 0) return undefined;
|
|
723
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || !item.trim())) {
|
|
724
|
+
throw new TypeError("AutoDL dataCenterList 必须是字符串数组");
|
|
725
|
+
}
|
|
726
|
+
return value.map((item) => item.trim());
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function uniqueStrings(values) {
|
|
730
|
+
return [...new Set(values)];
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function sameStringSet(left, right) {
|
|
734
|
+
const a = [...new Set(left ?? [])].sort();
|
|
735
|
+
const b = [...new Set(right ?? [])].sort();
|
|
736
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function commaList(value) {
|
|
740
|
+
return typeof value === "string"
|
|
741
|
+
? value.split(/[;,]/).map((item) => item.trim()).filter(Boolean)
|
|
742
|
+
: undefined;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
export function parseAutoDlRegionImagePairs(value) {
|
|
746
|
+
if (value === undefined || value === null || value === "") return undefined;
|
|
747
|
+
if (typeof value !== "string") {
|
|
748
|
+
throw new TypeError("AUTODL_REGION_IMAGE_PAIRS 必须是分号分隔的 地区=镜像UUID 列表");
|
|
749
|
+
}
|
|
750
|
+
return value.split(";").map((entry) => {
|
|
751
|
+
const separator = entry.indexOf("=");
|
|
752
|
+
const dataCenter = separator >= 0 ? entry.slice(0, separator).trim() : "";
|
|
753
|
+
const imageUuid = separator >= 0 ? entry.slice(separator + 1).trim() : "";
|
|
754
|
+
if (!dataCenter || !imageUuid) {
|
|
755
|
+
throw new TypeError(
|
|
756
|
+
"AUTODL_REGION_IMAGE_PAIRS 必须使用 地区=镜像UUID;地区=镜像UUID 格式",
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
return { dataCenterList: [dataCenter], imageUuid };
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function normalizeRegionImageCandidates(value, fallback) {
|
|
764
|
+
if (value === undefined || value === null) {
|
|
765
|
+
return fallback.imageUuid
|
|
766
|
+
? [{
|
|
767
|
+
dataCenterList: fallback.dataCenterList,
|
|
768
|
+
imageUuid: fallback.imageUuid,
|
|
769
|
+
}]
|
|
770
|
+
: undefined;
|
|
771
|
+
}
|
|
772
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
773
|
+
throw new TypeError("AutoDL regionImageCandidates 必须是非空数组");
|
|
774
|
+
}
|
|
775
|
+
const normalized = value.map((candidate) => {
|
|
776
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
777
|
+
throw new TypeError("AutoDL regionImageCandidates 项无效");
|
|
778
|
+
}
|
|
779
|
+
const imageUuid = first(candidate.imageUuid);
|
|
780
|
+
const dataCenterList = stringList(
|
|
781
|
+
candidate.dataCenterList
|
|
782
|
+
?? (typeof candidate.dataCenter === "string" ? [candidate.dataCenter] : undefined),
|
|
783
|
+
);
|
|
784
|
+
if (!imageUuid || !dataCenterList?.length) {
|
|
785
|
+
throw new TypeError("AutoDL 地区镜像项必须同时包含地区和镜像 UUID");
|
|
786
|
+
}
|
|
787
|
+
return { dataCenterList, imageUuid };
|
|
788
|
+
});
|
|
789
|
+
const regions = normalized.flatMap((candidate) => candidate.dataCenterList);
|
|
790
|
+
if (new Set(regions).size !== regions.length) {
|
|
791
|
+
throw new TypeError("AutoDL 地区镜像列表不能重复地区");
|
|
792
|
+
}
|
|
793
|
+
return normalized;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function configuredRegionImageCandidates(config) {
|
|
797
|
+
return config.regionImageCandidates?.length
|
|
798
|
+
? config.regionImageCandidates
|
|
799
|
+
: [{ dataCenterList: config.dataCenterList, imageUuid: config.imageUuid }];
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function stableRegionOffset(runId, regionCount) {
|
|
803
|
+
if (regionCount <= 1) return 0;
|
|
804
|
+
let hash = 2_166_136_261;
|
|
805
|
+
for (const character of String(runId ?? "")) {
|
|
806
|
+
hash ^= character.codePointAt(0);
|
|
807
|
+
hash = Math.imul(hash, 16_777_619);
|
|
808
|
+
}
|
|
809
|
+
return (hash >>> 0) % regionCount;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function positiveInteger(value, fallback, field) {
|
|
813
|
+
const numeric = value === undefined || value === null || value === "" ? fallback : Number(value);
|
|
814
|
+
if (!Number.isInteger(numeric) || numeric <= 0) {
|
|
815
|
+
throw new TypeError(`AutoDL ${field} 必须是正整数`);
|
|
816
|
+
}
|
|
817
|
+
return numeric;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function nonNegativeInteger(value, fallback, field) {
|
|
821
|
+
const numeric = value === undefined || value === null || value === "" ? fallback : Number(value);
|
|
822
|
+
if (!Number.isInteger(numeric) || numeric < 0) {
|
|
823
|
+
throw new TypeError(`AutoDL ${field} 必须是非负整数`);
|
|
824
|
+
}
|
|
825
|
+
return numeric;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function optionalNumber(value, field) {
|
|
829
|
+
if (value === undefined || value === null || value === "") return null;
|
|
830
|
+
const numeric = Number(value);
|
|
831
|
+
if (!Number.isFinite(numeric) || numeric < 0) {
|
|
832
|
+
throw new TypeError(`AutoDL ${field} 必须是非负数`);
|
|
833
|
+
}
|
|
834
|
+
return numeric;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function text(value) {
|
|
838
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function sleep(milliseconds) {
|
|
842
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
843
|
+
}
|