@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.
Files changed (347) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +128 -0
  3. package/data/dataset-source-registry.v1.json +300 -0
  4. package/dist/arkgraph/boot.js +6 -0
  5. package/dist/arkgraph/index.html +1 -0
  6. package/dist/arkgraph/viewer.css +1 -0
  7. package/dist/arkgraph/viewer.en.css +1 -0
  8. package/dist/arkgraph/viewer.en.js +49 -0
  9. package/dist/arkgraph/viewer.en.js.LEGAL.txt +56 -0
  10. package/dist/arkgraph/viewer.js +49 -0
  11. package/dist/arkgraph/viewer.js.LEGAL.txt +56 -0
  12. package/docker/claude-code/Dockerfile +97 -0
  13. package/docker/claude-code/codex-pro-relay.mjs +466 -0
  14. package/docker/claude-code/runtime-contract-check.mjs +79 -0
  15. package/docs/arkgraph-reading.md +79 -0
  16. package/docs/configuration.md +100 -0
  17. package/docs/integration.md +92 -0
  18. package/docs/maturity-plan.md +27 -0
  19. package/docs/npm-release.md +44 -0
  20. package/docs/paper-reading.md +40 -0
  21. package/docs/research-plan-granularity.md +27 -0
  22. package/docs/terminal.md +49 -0
  23. package/examples/toy-evaluation/compile-task.json +27 -0
  24. package/examples/toy-evaluation/paper.md +5 -0
  25. package/examples/toy-evaluation/repository/README.md +9 -0
  26. package/examples/toy-evaluation/repository/checkpoint.json +4 -0
  27. package/examples/toy-evaluation/repository/evaluate.py +17 -0
  28. package/examples/toy-evaluation/task.json +81 -0
  29. package/package.json +59 -0
  30. package/prompts/compile-research.md +58 -0
  31. package/prompts/execute-contract.md +72 -0
  32. package/prompts/execute-workspace-simple.md +51 -0
  33. package/prompts/execute-workspace.md +34 -0
  34. package/prompts/prepare-reproduction.md +82 -0
  35. package/prompts/repair-research.md +45 -0
  36. package/protocol/CAP.md +129 -0
  37. package/protocol/LICENSE +12 -0
  38. package/protocol/MAPPINGS.md +72 -0
  39. package/protocol/README.md +38 -0
  40. package/protocol/conformance-v2.0-alpha.1.json +36 -0
  41. package/protocol/examples/arkgraph/checkpoint-evaluation.json +309 -0
  42. package/protocol/examples/arkgraph/fixtures.mjs +49 -0
  43. package/protocol/examples/arkgraph/paper-free.json +291 -0
  44. package/protocol/examples/arkgraph/partial-failure.json +344 -0
  45. package/protocol/examples/arkgraph/training-evaluation.json +443 -0
  46. package/protocol/profiles/agent-trace.md +16 -0
  47. package/protocol/profiles/computational-run.md +16 -0
  48. package/protocol/profiles/core.md +15 -0
  49. package/protocol/profiles/public-bundle.md +18 -0
  50. package/protocol/profiles/reproduction.md +29 -0
  51. package/protocol/profiles/research-compilation.md +44 -0
  52. package/protocol/profiles/research-plan.md +39 -0
  53. package/protocol/profiles/restricted-evidence.md +15 -0
  54. package/runtime/bootstrap-autodl-runtime.sh +314 -0
  55. package/runtime/create-runtime-venv.sh +41 -0
  56. package/runtime/install-local-cpu-runtime.sh +23 -0
  57. package/runtime/install-scientific-runtime.sh +153 -0
  58. package/runtime/mineru/parse.py +62 -0
  59. package/runtime/mineru/requirements.txt +4 -0
  60. package/runtime/requirements-baseline.txt +38 -0
  61. package/schemas/cap/v2/activity.schema.json +47 -0
  62. package/schemas/cap/v2/agent.schema.json +32 -0
  63. package/schemas/cap/v2/assertion.schema.json +110 -0
  64. package/schemas/cap/v2/descriptor.schema.json +243 -0
  65. package/schemas/cap/v2/entity.schema.json +64 -0
  66. package/schemas/cap/v2/manifest.schema.json +67 -0
  67. package/schemas/cap/v2/relation.schema.json +82 -0
  68. package/schemas/compute-catalog.schema.json +63 -0
  69. package/schemas/compute-decision.schema.json +27 -0
  70. package/schemas/execution-contract.schema.json +1024 -0
  71. package/schemas/research-card.schema.json +30 -0
  72. package/schemas/research-inventory-draft.schema.json +366 -0
  73. package/schemas/research.schema.json +1044 -0
  74. package/schemas/result.schema.json +173 -0
  75. package/schemas/verification-policy.schema.json +47 -0
  76. package/schemas/verified-conclusion.schema.json +58 -0
  77. package/schemas/workspace-summary.schema.json +24 -0
  78. package/scripts/build-arkgraph-view.mjs +12 -0
  79. package/scripts/check-execution-feasibility.mjs +24 -0
  80. package/scripts/check-syntax.mjs +15 -0
  81. package/scripts/deterministic-asset-preparation.py +438 -0
  82. package/scripts/package-cap.mjs +23 -0
  83. package/scripts/package-local-agent.mjs +23 -0
  84. package/scripts/preview-arkgraph.mjs +25 -0
  85. package/scripts/replay-research-compiler-candidate.mjs +134 -0
  86. package/scripts/review-compiler-sources.mjs +44 -0
  87. package/scripts/run-asset-preparation.sh +17 -0
  88. package/scripts/run-research-plan.mjs +98 -0
  89. package/scripts/validate-asset-preparation.py +290 -0
  90. package/scripts/verify-local-runtime.mjs +57 -0
  91. package/scripts/verify-npm-package.mjs +57 -0
  92. package/src/adapters/paper2agent.mjs +107 -0
  93. package/src/assets/cache.mjs +159 -0
  94. package/src/assets/compute.mjs +98 -0
  95. package/src/assets/executor.mjs +145 -0
  96. package/src/assets/lifecycle.mjs +213 -0
  97. package/src/assets/manifest.mjs +242 -0
  98. package/src/assets/opportunistic-preparation.mjs +81 -0
  99. package/src/assets/plan.mjs +411 -0
  100. package/src/assets/prompts.mjs +29 -0
  101. package/src/assets/public-asset-probe.mjs +525 -0
  102. package/src/assets/qualification.mjs +119 -0
  103. package/src/assets/readiness.mjs +130 -0
  104. package/src/assets/reproduction-admission.mjs +355 -0
  105. package/src/assets/requirements.mjs +152 -0
  106. package/src/assets/source-grounding.mjs +341 -0
  107. package/src/assets/source-policy.mjs +118 -0
  108. package/src/autodl/client.mjs +260 -0
  109. package/src/autodl/ssh.mjs +380 -0
  110. package/src/autodl/tools.mjs +129 -0
  111. package/src/cap/redaction.mjs +38 -0
  112. package/src/cap/v2/archive.mjs +152 -0
  113. package/src/cap/v2/attestation.mjs +204 -0
  114. package/src/cap/v2/canonical-json.mjs +114 -0
  115. package/src/cap/v2/compilation-artifact.mjs +240 -0
  116. package/src/cap/v2/core.mjs +282 -0
  117. package/src/cap/v2/measurement-assessment-records.mjs +23 -0
  118. package/src/cap/v2/pipeline-artifact.mjs +922 -0
  119. package/src/cap/v2/read.mjs +41 -0
  120. package/src/cap/v2/reassessment-artifact.mjs +383 -0
  121. package/src/cap/v2/research-artifact.mjs +231 -0
  122. package/src/cap/v2/research-map-records.mjs +46 -0
  123. package/src/cap/v2/research-object-records.mjs +163 -0
  124. package/src/cap/v2/research-records.mjs +187 -0
  125. package/src/cap/v2/verify.mjs +642 -0
  126. package/src/cli.mjs +1146 -0
  127. package/src/compute/autodl-pro-compiler.mjs +347 -0
  128. package/src/compute/autodl-pro-executor.mjs +459 -0
  129. package/src/compute/autodl-pro-job.mjs +843 -0
  130. package/src/compute/autodl-pro-network.mjs +295 -0
  131. package/src/compute/autodl-pro-remote.mjs +810 -0
  132. package/src/compute/autodl-pro-staging.mjs +117 -0
  133. package/src/compute/campaign.mjs +110 -0
  134. package/src/compute/catalog.mjs +123 -0
  135. package/src/compute/checkpoint-protocol.mjs +154 -0
  136. package/src/compute/codex-account-lock.mjs +111 -0
  137. package/src/compute/codex-account-session.mjs +107 -0
  138. package/src/compute/compiler-profile.mjs +38 -0
  139. package/src/compute/compiler-router.mjs +23 -0
  140. package/src/compute/coordinator-recovery.mjs +210 -0
  141. package/src/compute/executor-router.mjs +29 -0
  142. package/src/compute/gcp-batch-compiler.mjs +685 -0
  143. package/src/compute/gcp-batch-executor.mjs +1215 -0
  144. package/src/compute/gcp-batch-failure.mjs +92 -0
  145. package/src/compute/gcp-batch-job.mjs +527 -0
  146. package/src/compute/gcp-batch-lifecycle.mjs +81 -0
  147. package/src/compute/gcp-checkpoint-worker.mjs +1633 -0
  148. package/src/compute/local-codex-compiler.mjs +52 -0
  149. package/src/compute/measurement-hardware.mjs +128 -0
  150. package/src/compute/remote-attempt.mjs +226 -0
  151. package/src/compute/requirements.mjs +124 -0
  152. package/src/compute/research-phases.mjs +48 -0
  153. package/src/compute/scheduler.mjs +452 -0
  154. package/src/compute/shared-workloads.mjs +26 -0
  155. package/src/compute/stage-archive.mjs +79 -0
  156. package/src/contracts/campaign-contract.mjs +52 -0
  157. package/src/contracts/execution-contract.mjs +819 -0
  158. package/src/contracts/execution-mode.mjs +19 -0
  159. package/src/contracts/execution-timeouts.mjs +45 -0
  160. package/src/contracts/execution-workload.mjs +68 -0
  161. package/src/contracts/preflight-schema.mjs +25 -0
  162. package/src/contracts/public-contract.mjs +63 -0
  163. package/src/contracts/subject-tags.mjs +31 -0
  164. package/src/dashboard/data.mjs +898 -0
  165. package/src/dashboard/server.mjs +79 -0
  166. package/src/dashboard/static/dashboard.css +366 -0
  167. package/src/dashboard/static/dashboard.js +560 -0
  168. package/src/dashboard/static/index.html +85 -0
  169. package/src/deployment/community-policy.mjs +9 -0
  170. package/src/deployment/environment.mjs +112 -0
  171. package/src/deployment/guided.mjs +98 -0
  172. package/src/deployment/handoff.mjs +102 -0
  173. package/src/deployment/local-contract.mjs +31 -0
  174. package/src/deployment/local.mjs +100 -0
  175. package/src/deployment/prepare.mjs +46 -0
  176. package/src/deployment/recipe.mjs +108 -0
  177. package/src/deployment/supplement.mjs +51 -0
  178. package/src/deployment/terminal.mjs +43 -0
  179. package/src/diagnosis/renderer.mjs +75 -0
  180. package/src/diagnosis/target-failure.mjs +46 -0
  181. package/src/evidence/parser-registry.mjs +54 -0
  182. package/src/evidence/parsers/fasttext-classification.mjs +82 -0
  183. package/src/evidence/parsers/json-scalar.mjs +96 -0
  184. package/src/evidence/parsers/simcse-senteval.mjs +104 -0
  185. package/src/evidence/parsers/starspace-classification.mjs +78 -0
  186. package/src/evidence/registry.mjs +147 -0
  187. package/src/execution/runner-audit.mjs +473 -0
  188. package/src/gcp/auth.mjs +106 -0
  189. package/src/gcp/batch-client.mjs +120 -0
  190. package/src/gcp/resource-discovery.mjs +177 -0
  191. package/src/gcp/rest.mjs +82 -0
  192. package/src/gcp/secret-manager.mjs +34 -0
  193. package/src/gcp/signed-url.mjs +133 -0
  194. package/src/gcp/storage.mjs +220 -0
  195. package/src/graph/command.mjs +41 -0
  196. package/src/graph/execution.mjs +97 -0
  197. package/src/graph/model.mjs +37 -0
  198. package/src/graph/presentation.mjs +110 -0
  199. package/src/graph/query.mjs +159 -0
  200. package/src/graph/research-relations.mjs +69 -0
  201. package/src/graph/source-page.mjs +12 -0
  202. package/src/graph/source-preview.mjs +34 -0
  203. package/src/graph/validate.mjs +76 -0
  204. package/src/job.mjs +496 -0
  205. package/src/network/autodl-routing-proxy.mjs +462 -0
  206. package/src/network/egress-proxy.mjs +158 -0
  207. package/src/observability/event-contract.mjs +230 -0
  208. package/src/observability/pipeline-monitor.mjs +166 -0
  209. package/src/pipeline/orchestrator.mjs +1281 -0
  210. package/src/pipeline/recovery-error.mjs +11 -0
  211. package/src/pipeline/replay.mjs +304 -0
  212. package/src/pipeline/shared-execution.mjs +115 -0
  213. package/src/pipeline/stage-checkpoint.mjs +86 -0
  214. package/src/pipeline/stage-recovery.mjs +101 -0
  215. package/src/pipeline/targets.mjs +110 -0
  216. package/src/process.mjs +143 -0
  217. package/src/protocol.mjs +312 -0
  218. package/src/provider/codex-account.mjs +44 -0
  219. package/src/provider/codex-completion.mjs +49 -0
  220. package/src/provider/completion.mjs +292 -0
  221. package/src/provider/model-client.mjs +44 -0
  222. package/src/provider/model-route.mjs +29 -0
  223. package/src/provider/openrouter-readiness.mjs +189 -0
  224. package/src/provider/reader-bridge.mjs +35 -0
  225. package/src/provider/relay.mjs +263 -0
  226. package/src/provider/runtime-auth.mjs +40 -0
  227. package/src/public/cap.d.mts +90 -0
  228. package/src/public/cap.mjs +12 -0
  229. package/src/public/contracts.d.mts +2 -0
  230. package/src/public/host.mjs +171 -0
  231. package/src/public/operations.d.mts +11 -0
  232. package/src/public/presentation.d.mts +4 -0
  233. package/src/records/views.mjs +26 -0
  234. package/src/remote/command.mjs +178 -0
  235. package/src/remote/ssh.mjs +59 -0
  236. package/src/repository-origin.mjs +81 -0
  237. package/src/reproduction/evidence-feedback.mjs +96 -0
  238. package/src/reproduction/incomplete-initialization.mjs +25 -0
  239. package/src/reproduction/lifecycle.mjs +253 -0
  240. package/src/reproduction/plan.mjs +132 -0
  241. package/src/reproduction/prompts.mjs +70 -0
  242. package/src/reproduction/runner.mjs +188 -0
  243. package/src/reproduction/summary.mjs +130 -0
  244. package/src/reproduction/workspace-mode.mjs +7 -0
  245. package/src/research/automatic-admission.mjs +156 -0
  246. package/src/research/compiler-coverage.mjs +85 -0
  247. package/src/research/compiler-failure.mjs +24 -0
  248. package/src/research/compiler-normalization-guards.mjs +112 -0
  249. package/src/research/compiler-repair.mjs +3 -0
  250. package/src/research/compiler.mjs +853 -0
  251. package/src/research/continuation-selection.mjs +26 -0
  252. package/src/research/execution-graph-context.mjs +43 -0
  253. package/src/research/experiment-importance.mjs +15 -0
  254. package/src/research/inventory-handoff.mjs +104 -0
  255. package/src/research/inventory-revisions.mjs +32 -0
  256. package/src/research/mineru-local.mjs +73 -0
  257. package/src/research/paper-command.mjs +19 -0
  258. package/src/research/paper-markdown.mjs +180 -0
  259. package/src/research/paper-source-map.mjs +69 -0
  260. package/src/research/planning-policy.mjs +88 -0
  261. package/src/research/reference-materials.mjs +11 -0
  262. package/src/research/reproduction-scope.mjs +30 -0
  263. package/src/research/research-map.mjs +94 -0
  264. package/src/research/research-objects.mjs +88 -0
  265. package/src/research/source-discovery.mjs +646 -0
  266. package/src/research/source-observations.mjs +75 -0
  267. package/src/research/source-review-cli-mcp.mjs +26 -0
  268. package/src/research/source-review-input.mjs +209 -0
  269. package/src/research/source-review-local-codex.mjs +36 -0
  270. package/src/research/source-review-model.mjs +70 -0
  271. package/src/research/source-review.mjs +173 -0
  272. package/src/research/structure.mjs +3163 -0
  273. package/src/research-card/renderer.mjs +277 -0
  274. package/src/research-card/verified-conclusion.mjs +143 -0
  275. package/src/results/output-registry.mjs +183 -0
  276. package/src/runtime/claude-code.mjs +52 -0
  277. package/src/runtime/codex-capacity-retry.mjs +87 -0
  278. package/src/runtime/codex.mjs +64 -0
  279. package/src/runtime/config.mjs +157 -0
  280. package/src/runtime/final-output.mjs +40 -0
  281. package/src/runtime/index.mjs +21 -0
  282. package/src/runtime/local-codex.mjs +74 -0
  283. package/src/runtime/opencode.mjs +95 -0
  284. package/src/runtime/prompt.mjs +13 -0
  285. package/src/sandbox/docker.mjs +363 -0
  286. package/src/settings/command.mjs +297 -0
  287. package/src/settings/store.mjs +119 -0
  288. package/src/telemetry/pricing.mjs +68 -0
  289. package/src/telemetry/usage.mjs +265 -0
  290. package/src/terminal/events.mjs +97 -0
  291. package/src/terminal/input.mjs +40 -0
  292. package/src/terminal/plain.mjs +40 -0
  293. package/src/terminal/remote-stream.mjs +22 -0
  294. package/src/terminal/screen.mjs +214 -0
  295. package/src/terminal/transcript.mjs +69 -0
  296. package/src/util.mjs +107 -0
  297. package/src/verification/ai-assessor.mjs +534 -0
  298. package/src/verification/claim-evaluator.mjs +242 -0
  299. package/src/verification/evidence-context.mjs +165 -0
  300. package/src/verification/evidence-reader.mjs +95 -0
  301. package/src/verification/integrity.mjs +570 -0
  302. package/src/verification/tolerance.mjs +32 -0
  303. package/src/workloads/cpu-research-preparation.mjs +56 -0
  304. package/src/workloads/definition.mjs +74 -0
  305. package/src/workloads/phase-aware-reproduction.mjs +46 -0
  306. package/src/workloads/reproduction.mjs +85 -0
  307. package/src/workspace/command.mjs +242 -0
  308. package/src/workspace/control.mjs +49 -0
  309. package/src/workspace/entry.mjs +28 -0
  310. package/src/workspace/input.mjs +93 -0
  311. package/src/workspace/interactive.mjs +94 -0
  312. package/src/workspace/jobs.mjs +418 -0
  313. package/src/workspace/session.mjs +97 -0
  314. package/src/workspace/worker.mjs +137 -0
  315. package/ui/arkgraph/ambient-motion.mjs +10 -0
  316. package/ui/arkgraph/app.jsx +153 -0
  317. package/ui/arkgraph/boot.js +6 -0
  318. package/ui/arkgraph/camera-motion.mjs +20 -0
  319. package/ui/arkgraph/context-reveal.mjs +39 -0
  320. package/ui/arkgraph/details.css +3 -0
  321. package/ui/arkgraph/entry.jsx +28 -0
  322. package/ui/arkgraph/experiment-curves.mjs +17 -0
  323. package/ui/arkgraph/experiment-selection.mjs +15 -0
  324. package/ui/arkgraph/experiment-style.css +26 -0
  325. package/ui/arkgraph/experiment-ui.jsx +32 -0
  326. package/ui/arkgraph/frame.html +1 -0
  327. package/ui/arkgraph/graph-gestures.mjs +62 -0
  328. package/ui/arkgraph/label-layout.mjs +57 -0
  329. package/ui/arkgraph/locales/en.json +229 -0
  330. package/ui/arkgraph/locales/source-types.json +15 -0
  331. package/ui/arkgraph/localization-build.mjs +27 -0
  332. package/ui/arkgraph/material-build.mjs +23 -0
  333. package/ui/arkgraph/material-colors.mjs +39 -0
  334. package/ui/arkgraph/material-style.css +15 -0
  335. package/ui/arkgraph/open-graph.jsx +326 -0
  336. package/ui/arkgraph/outline.jsx +49 -0
  337. package/ui/arkgraph/package-lock.json +888 -0
  338. package/ui/arkgraph/package.json +17 -0
  339. package/ui/arkgraph/reading-layout.mjs +130 -0
  340. package/ui/arkgraph/reading-presentation.mjs +73 -0
  341. package/ui/arkgraph/record-detail.css +51 -0
  342. package/ui/arkgraph/record-details.jsx +29 -0
  343. package/ui/arkgraph/research-types.mjs +31 -0
  344. package/ui/arkgraph/selection-mark.jsx +6 -0
  345. package/ui/arkgraph/soft-spine.mjs +26 -0
  346. package/ui/arkgraph/steering-style.css +187 -0
  347. 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
+ }