@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,292 @@
1
+ import { researchSignal } from "../workspace/control.mjs";
2
+ import { AGENT_RUNTIMES } from "../runtime/config.mjs";
3
+ import {
4
+ isOpenRouterBaseUrl,
5
+ providerEndpoint,
6
+ runtimeProviderConfiguration,
7
+ } from "./runtime-auth.mjs";
8
+
9
+ // Opaque provider continuation stays in memory, never in transcripts or CAP.
10
+ const continuation = Symbol("providerContinuation");
11
+ export function modelProtocol(agent = {}) {
12
+ // Hosted OpenRouter reviews use the gateway's common completion interface.
13
+ if (isOpenRouterBaseUrl(runtimeProviderConfiguration(agent).baseUrl))
14
+ return "openai-chat-completions";
15
+ return (
16
+ agent.api?.protocol ??
17
+ AGENT_RUNTIMES[agent.runtime]?.apiProtocol ??
18
+ "openai-chat-completions"
19
+ );
20
+ }
21
+
22
+ export function buildModelRequest({
23
+ agent,
24
+ apiKey,
25
+ messages,
26
+ tools = [],
27
+ format,
28
+ maxTokens = 12000,
29
+ reasoning,
30
+ signal,
31
+ }) {
32
+ if (agent?.codexAccountId || agent?.billingMode === "subscription") throw new Error("Subscription model calls must use createModelClient, never an API request");
33
+ if (researchSignal())
34
+ signal = signal
35
+ ? AbortSignal.any([signal, researchSignal()])
36
+ : researchSignal();
37
+ const { baseUrl, model } = runtimeProviderConfiguration(agent);
38
+ const protocol = modelProtocol(agent);
39
+ let resource, body;
40
+ const headers = {
41
+ "Content-Type": "application/json",
42
+ Accept: "application/json",
43
+ };
44
+ if (protocol === "anthropic-messages") {
45
+ resource = "messages";
46
+ if (apiKey) headers["x-api-key"] = apiKey;
47
+ headers["anthropic-version"] = "2023-06-01";
48
+ const converted = [];
49
+ for (const message of messages.filter((m) => m.role !== "system")) {
50
+ const role = message.role === "tool" ? "user" : message.role;
51
+ const blocks = message[continuation] ?? anthropicBlocks(message);
52
+ if (converted.at(-1)?.role === role)
53
+ converted.at(-1).content.push(...blocks);
54
+ else converted.push({ role, content: [...blocks] });
55
+ }
56
+ body = {
57
+ model,
58
+ max_tokens: maxTokens,
59
+ system: messages
60
+ .filter((m) => m.role === "system")
61
+ .map((m) => m.content)
62
+ .join("\n"),
63
+ messages: converted,
64
+ ...(tools.length
65
+ ? {
66
+ tools: tools.map(({ function: f }) => ({
67
+ name: f.name,
68
+ description: f.description,
69
+ input_schema: f.parameters,
70
+ })),
71
+ }
72
+ : {}),
73
+ ...(format?.type === "json_schema"
74
+ ? {
75
+ output_config: {
76
+ format: {
77
+ type: "json_schema",
78
+ schema: format.json_schema.schema,
79
+ },
80
+ },
81
+ }
82
+ : {}),
83
+ };
84
+ } else if (protocol === "openai-responses") {
85
+ resource = "responses";
86
+ if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
87
+ body = {
88
+ model,
89
+ store: false,
90
+ include: ["reasoning.encrypted_content"],
91
+ max_output_tokens: maxTokens,
92
+ input: messages.flatMap((message) => {
93
+ if (message[continuation]) return message[continuation];
94
+ if (message.role === "tool")
95
+ return [
96
+ {
97
+ type: "function_call_output",
98
+ call_id: message.tool_call_id,
99
+ output: message.content,
100
+ },
101
+ ];
102
+ const output = [];
103
+ if (message.content)
104
+ output.push({
105
+ role: message.role,
106
+ content: responseContent(message.content, message.role),
107
+ });
108
+ for (const call of message.tool_calls ?? [])
109
+ output.push({
110
+ type: "function_call",
111
+ call_id: call.id,
112
+ name: call.function.name,
113
+ arguments: call.function.arguments,
114
+ });
115
+ return output;
116
+ }),
117
+ ...(tools.length
118
+ ? {
119
+ tools: tools.map(({ function: f }) => ({
120
+ type: "function",
121
+ ...f,
122
+ strict: false,
123
+ })),
124
+ tool_choice: "auto",
125
+ }
126
+ : {}),
127
+ ...(format
128
+ ? {
129
+ text: {
130
+ format:
131
+ format.type === "json_schema"
132
+ ? { type: "json_schema", ...format.json_schema }
133
+ : format,
134
+ },
135
+ }
136
+ : {}),
137
+ };
138
+ } else if (protocol === "openai-chat-completions") {
139
+ resource = "chat/completions";
140
+ if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
141
+ body = {
142
+ model,
143
+ messages,
144
+ max_tokens: maxTokens,
145
+ ...(tools.length ? { tools, tool_choice: "auto" } : {}),
146
+ ...(format ? { response_format: format } : {}),
147
+ ...(isOpenRouterBaseUrl(baseUrl)
148
+ ? {
149
+ reasoning: reasoning ?? { effort: agent.effort ?? "low" },
150
+ provider: { require_parameters: true },
151
+ }
152
+ : {}),
153
+ };
154
+ } else throw Error(`Unsupported model protocol: ${protocol}`);
155
+ return {
156
+ protocol,
157
+ url: providerEndpoint(baseUrl, resource),
158
+ init: { method: "POST", headers, body: JSON.stringify(body), signal },
159
+ };
160
+ }
161
+
162
+ export function readModelResponse(payload, protocol) {
163
+ if (protocol === "openai-chat-completions") return payload;
164
+ const blocks =
165
+ protocol === "openai-responses"
166
+ ? (payload.output ?? [])
167
+ : (payload.content ?? []);
168
+ const refused = blocks.some(
169
+ (b) =>
170
+ b.type === "refusal" ||
171
+ b.content?.some?.((item) => item.type === "refusal"),
172
+ );
173
+ const calls =
174
+ protocol === "openai-responses"
175
+ ? blocks
176
+ .filter((b) => b.type === "function_call")
177
+ .map((b) => ({
178
+ id: b.call_id,
179
+ type: "function",
180
+ function: { name: b.name, arguments: b.arguments },
181
+ }))
182
+ : blocks
183
+ .filter((b) => b.type === "tool_use")
184
+ .map((b) => ({
185
+ id: b.id,
186
+ type: "function",
187
+ function: { name: b.name, arguments: JSON.stringify(b.input) },
188
+ }));
189
+ const content =
190
+ protocol === "openai-responses"
191
+ ? blocks
192
+ .filter((b) => b.type === "message")
193
+ .flatMap((b) => b.content ?? [])
194
+ .filter((b) => b.type === "output_text")
195
+ .map((b) => b.text)
196
+ .join("\n")
197
+ : blocks
198
+ .filter((b) => b.type === "text")
199
+ .map((b) => b.text)
200
+ .join("\n");
201
+ const message = {
202
+ role: "assistant",
203
+ content: content || null,
204
+ ...(calls.length ? { tool_calls: calls } : {}),
205
+ };
206
+ Object.defineProperty(message, continuation, {
207
+ value: blocks,
208
+ enumerable: true,
209
+ });
210
+ const usage = payload.usage;
211
+ const incomplete =
212
+ refused ||
213
+ payload.error ||
214
+ ["incomplete", "failed", "cancelled"].includes(payload.status) ||
215
+ ["max_tokens", "refusal"].includes(payload.stop_reason);
216
+ return {
217
+ id: payload.id,
218
+ model: payload.model,
219
+ usage: usage
220
+ ? {
221
+ ...usage,
222
+ prompt_tokens: usage.input_tokens,
223
+ completion_tokens: usage.output_tokens,
224
+ prompt_tokens_details: {
225
+ cached_tokens:
226
+ usage.input_tokens_details?.cached_tokens ??
227
+ usage.cache_read_input_tokens ??
228
+ 0,
229
+ },
230
+ }
231
+ : undefined,
232
+ choices: [
233
+ {
234
+ message,
235
+ finish_reason: incomplete
236
+ ? "length"
237
+ : calls.length
238
+ ? "tool_calls"
239
+ : "stop",
240
+ },
241
+ ],
242
+ };
243
+ }
244
+
245
+ function responseContent(content, role) {
246
+ if (typeof content === "string") return content;
247
+ return content.map((block) =>
248
+ block.type === "image_url"
249
+ ? {
250
+ type: "input_image",
251
+ image_url: block.image_url.url,
252
+ detail: block.image_url.detail ?? "auto",
253
+ }
254
+ : {
255
+ type: role === "assistant" ? "output_text" : "input_text",
256
+ text: block.text,
257
+ },
258
+ );
259
+ }
260
+
261
+ function anthropicBlocks(message) {
262
+ if (message.role === "tool")
263
+ return [
264
+ {
265
+ type: "tool_result",
266
+ tool_use_id: message.tool_call_id,
267
+ content: message.content,
268
+ },
269
+ ];
270
+ const blocks =
271
+ typeof message.content === "string"
272
+ ? [{ type: "text", text: message.content }]
273
+ : (message.content ?? []).map((block) => {
274
+ if (block.type !== "image_url")
275
+ return { type: "text", text: block.text };
276
+ const data = /^data:([^;]+);base64,(.*)$/s.exec(block.image_url.url);
277
+ return {
278
+ type: "image",
279
+ source: data
280
+ ? { type: "base64", media_type: data[1], data: data[2] }
281
+ : { type: "url", url: block.image_url.url },
282
+ };
283
+ });
284
+ for (const call of message.tool_calls ?? [])
285
+ blocks.push({
286
+ type: "tool_use",
287
+ id: call.id,
288
+ name: call.function.name,
289
+ input: JSON.parse(call.function.arguments),
290
+ });
291
+ return blocks;
292
+ }
@@ -0,0 +1,44 @@
1
+ import { buildModelRequest, readModelResponse } from './completion.mjs';
2
+ import { resolveProviderApiKey } from './runtime-auth.mjs';
3
+ import { modelRoute } from './model-route.mjs';
4
+ import { completeWithCodex } from './codex-completion.mjs';
5
+ import { executeObservedExternalRequest, emitStructuredObservation } from '../observability/event-contract.mjs';
6
+ import { createBudgetedProviderFetch } from '../../docker/claude-code/codex-pro-relay.mjs';
7
+
8
+ // All bounded research model calls select transport and credentials here.
9
+ // Scientific callers own prompts, evidence tools and validation, never routing.
10
+ export function createModelClient({ runtimeAgent, providerSecret, fetchImpl = fetch, secretManager,
11
+ paperBudget, observe, correlation, codex = {} } = {}) {
12
+ const route = modelRoute(runtimeAgent);
13
+ let credential;
14
+ const request = paperBudget ? createBudgetedProviderFetch({ budget: paperBudget, fetchImpl }) : fetchImpl;
15
+ return { route, async complete({ phase = 'model_completion', component = 'research_model', reader,
16
+ timeoutMs, maxToolCalls, ...input }) {
17
+ if (route.transport === 'codex_subscription') {
18
+ const startedAt = new Date().toISOString();
19
+ try {
20
+ const result = await completeWithCodex({ ...codex, ...input, runtimeAgent, reader, timeoutMs, maxToolCalls });
21
+ (observe ?? emitStructuredObservation)({ schemaVersion: '1.0', kind: 'model_call', component, operation: phase,
22
+ correlation, model: route.model, transport: route.transport, billingMode: route.billingMode,
23
+ outcome: { status: 'succeeded' }, sessionId: result.id, usage: result.usage,
24
+ timing: { startedAt, finishedAt: new Date().toISOString() } });
25
+ return result;
26
+ } catch (error) {
27
+ (observe ?? emitStructuredObservation)({ schemaVersion: '1.0', kind: 'model_call', component, operation: phase,
28
+ correlation, model: route.model, transport: route.transport, billingMode: route.billingMode,
29
+ outcome: { status: 'failed', code: error.failureCode }, timing: { startedAt, finishedAt: new Date().toISOString() } });
30
+ throw error;
31
+ }
32
+ }
33
+ if (runtimeAgent.paperBudgetEnforced && !paperBudget) throw Error('required paper budget is missing');
34
+ if (!route.baseUrl || !route.model) throw Error('Model API is not configured');
35
+ credential ??= await resolveProviderApiKey({ runtimeAgent, providerSecret, secretManager });
36
+ if (!credential && route.provider === 'openrouter.ai') throw Error('Model provider credential is missing');
37
+ const prepared = buildModelRequest({ ...input, agent: runtimeAgent, apiKey: credential, tools: reader?.definitions ?? input.tools });
38
+ const response = await executeObservedExternalRequest({ component, service: route.provider,
39
+ operation: phase, method: 'POST', url: prepared.url, correlation, observe, retryableStatus: () => false,
40
+ request: () => request(prepared.url, prepared.init) });
41
+ if (!response.ok) throw Object.assign(Error(`Model provider HTTP ${response.status}`), { httpStatus: response.status });
42
+ return { ...readModelResponse(await response.json(), prepared.protocol), transport: route.transport, billingMode: route.billingMode };
43
+ } };
44
+ }
@@ -0,0 +1,29 @@
1
+ import { CiteArkError } from '../util.mjs';
2
+ import { runtimeProviderConfiguration } from './runtime-auth.mjs';
3
+
4
+ // A selected subscription is authoritative. No API endpoint, key, or fallback
5
+ // may turn a failed subscription operation into a metered provider request.
6
+ export function modelRoute(agent = {}) {
7
+ if (agent.codexAccountId || agent.billingMode === 'subscription') {
8
+ if (agent.runtime !== 'codex' || !/^[a-z0-9][a-z0-9-]{0,47}$/.test(agent.codexAccountId ?? '')
9
+ || !validNativeModel(agent.model)
10
+ || (agent.reasoningMode && agent.reasoningMode !== 'standard')) {
11
+ throw subscriptionUnavailable('Codex 订阅配置无效', 'codex.account_invalid_configuration');
12
+ }
13
+ return { transport: 'codex_subscription', billingMode: 'subscription', provider: 'OpenAI',
14
+ model: agent.model, nativeModel: agent.model.replace(/^openai\//, ''), accountId: agent.codexAccountId };
15
+ }
16
+ const { baseUrl, model } = runtimeProviderConfiguration(agent);
17
+ return { transport: 'api', billingMode: agent.billingMode ?? 'platform',
18
+ provider: baseUrl ? new URL(baseUrl).hostname : null, model, baseUrl };
19
+ }
20
+
21
+ export function subscriptionUnavailable(message, failureCode = 'codex.subscription_unavailable', details = {}) {
22
+ return new CiteArkError(message, { retryable: false, failureCode, failureCategory: 'provider',
23
+ retryDisposition: 'never', failureOwner: 'platform',
24
+ recoveryAction: '恢复同一 Codex 订阅账号后从已保存阶段继续;不会转用 API 或其他账号', ...details });
25
+ }
26
+
27
+ export function validNativeModel(model) {
28
+ return typeof model === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(model.replace(/^openai\//, ''));
29
+ }
@@ -0,0 +1,189 @@
1
+ import { modelRoute } from "./model-route.mjs";
2
+ import { CiteArkError } from "../util.mjs";
3
+ import {
4
+ isOpenRouterBaseUrl,
5
+ providerEndpoint,
6
+ resolveProviderApiKey,
7
+ runtimeProviderConfiguration,
8
+ } from "./runtime-auth.mjs";
9
+
10
+ const DEFAULT_MINIMUM_BALANCE_USD = 2;
11
+
12
+ /**
13
+ * 在启动 Research Compiler、资产准备或 GPU 前检查 OpenRouter 账户是否可用。
14
+ * 普通 API Key 无权访问 /credits 时退回 /key;如果两者都无法给出余额,发送最多
15
+ * 1 个输出 token 的同模型探针,以便至少在余额为零或模型不可用时提前停止。
16
+ */
17
+ export async function checkOpenRouterReadiness({
18
+ runtimeAgent,
19
+ providerSecret,
20
+ minimumBalanceUsd = DEFAULT_MINIMUM_BALANCE_USD,
21
+ fetchImpl = fetch,
22
+ secretManager,
23
+ timeoutMs = 15_000,
24
+ } = {}) {
25
+ const route = modelRoute(runtimeAgent);
26
+ if (route.transport === "codex_subscription") return { status: "not_applicable", provider: "codex_subscription", billingMode: "subscription", checkedAt: new Date().toISOString() };
27
+ const configuration = runtimeProviderConfiguration(runtimeAgent);
28
+ if (!isOpenRouterBaseUrl(configuration.baseUrl)) {
29
+ return { status: "not_applicable", provider: "other", checkedAt: new Date().toISOString() };
30
+ }
31
+ const apiKey = await resolveProviderApiKey({ runtimeAgent, providerSecret, secretManager });
32
+ if (!apiKey) throw providerActionRequired("OpenRouter API 密钥不可用", "provider_credential_unavailable");
33
+
34
+ const headers = { Authorization: `Bearer ${apiKey}`, Accept: "application/json" };
35
+ const credits = await providerGet({
36
+ url: providerEndpoint(configuration.baseUrl, "credits"),
37
+ headers,
38
+ fetchImpl,
39
+ timeoutMs,
40
+ });
41
+ let source = "credits";
42
+ let remainingUsd = credits.ok ? creditsRemaining(credits.body) : null;
43
+ if (!credits.ok && credits.status !== 401) {
44
+ const key = await providerGet({
45
+ url: providerEndpoint(configuration.baseUrl, "key"),
46
+ headers,
47
+ fetchImpl,
48
+ timeoutMs,
49
+ });
50
+ source = "key";
51
+ if (!key.ok) throw providerHttpError(key.status);
52
+ remainingUsd = keyRemaining(key.body);
53
+ } else if (!credits.ok) {
54
+ throw providerHttpError(credits.status);
55
+ }
56
+
57
+ const threshold = finiteNonNegative(minimumBalanceUsd, DEFAULT_MINIMUM_BALANCE_USD);
58
+ if (remainingUsd !== null && remainingUsd < threshold) {
59
+ throw providerActionRequired(
60
+ `OpenRouter 可用余额 $${remainingUsd.toFixed(2)},低于启动下一阶段所需的安全下限 $${threshold.toFixed(2)}`,
61
+ "provider_balance_insufficient",
62
+ );
63
+ }
64
+ let probe = null;
65
+ if (remainingUsd === null) {
66
+ probe = await providerProbe({
67
+ url: providerEndpoint(configuration.baseUrl, "chat/completions"),
68
+ headers,
69
+ model: configuration.model,
70
+ fetchImpl,
71
+ timeoutMs,
72
+ });
73
+ }
74
+ return {
75
+ status: remainingUsd === null ? "available_unquantified" : "available",
76
+ provider: "openrouter",
77
+ source,
78
+ remainingUsd,
79
+ minimumBalanceUsd: threshold,
80
+ ...(probe ? { probe } : {}),
81
+ checkedAt: new Date().toISOString(),
82
+ };
83
+ }
84
+
85
+ async function providerProbe({ url, headers, model, fetchImpl, timeoutMs }) {
86
+ if (!model) throw providerActionRequired("OpenRouter 预检缺少目标模型", "provider_model_unavailable");
87
+ let response;
88
+ try {
89
+ response = await fetchImpl(url, {
90
+ method: "POST",
91
+ headers: { ...headers, "Content-Type": "application/json" },
92
+ body: JSON.stringify({
93
+ model,
94
+ messages: [{ role: "user", content: "Reply OK." }],
95
+ max_tokens: 1,
96
+ temperature: 0,
97
+ reasoning: { effort: "low", exclude: true },
98
+ }),
99
+ signal: AbortSignal.timeout(timeoutMs),
100
+ });
101
+ } catch (error) {
102
+ throw new CiteArkError("OpenRouter 同模型探针暂时无法连接供应商", {
103
+ cause: error,
104
+ retryable: true,
105
+ failureCode: "provider_readiness_unavailable",
106
+ failureCategory: "provider",
107
+ retryDisposition: "retry_same_inputs",
108
+ });
109
+ }
110
+ if (!response.ok) throw providerHttpError(response.status);
111
+ return { status: "passed", model, maximumOutputTokens: 1 };
112
+ }
113
+
114
+ async function providerGet({ url, headers, fetchImpl, timeoutMs }) {
115
+ let response;
116
+ try {
117
+ response = await fetchImpl(url, {
118
+ method: "GET",
119
+ headers,
120
+ signal: AbortSignal.timeout(timeoutMs),
121
+ });
122
+ } catch (error) {
123
+ throw new CiteArkError("OpenRouter 余额预检暂时无法连接供应商", {
124
+ cause: error,
125
+ retryable: true,
126
+ failureCode: "provider_readiness_unavailable",
127
+ failureCategory: "provider",
128
+ retryDisposition: "retry_same_inputs",
129
+ });
130
+ }
131
+ let body = null;
132
+ try {
133
+ body = await response.json();
134
+ } catch {
135
+ // 状态码足以分类;不保留可能包含敏感内容的响应正文。
136
+ }
137
+ return { ok: response.ok, status: response.status, body };
138
+ }
139
+
140
+ function creditsRemaining(body) {
141
+ const data = body?.data ?? body;
142
+ const total = finite(data?.total_credits);
143
+ const used = finite(data?.total_usage);
144
+ return total === null || used === null ? null : Math.max(0, total - used);
145
+ }
146
+
147
+ function keyRemaining(body) {
148
+ const data = body?.data ?? body;
149
+ return finite(data?.limit_remaining);
150
+ }
151
+
152
+ function providerHttpError(status) {
153
+ if (status === 401 || status === 403) {
154
+ return providerActionRequired("OpenRouter 拒绝了当前 API 密钥", "provider_credential_rejected");
155
+ }
156
+ if (status === 402) {
157
+ return providerActionRequired("OpenRouter 账户余额不足", "provider_balance_insufficient");
158
+ }
159
+ return new CiteArkError(`OpenRouter 余额预检失败(HTTP ${status})`, {
160
+ retryable: status === 408 || status === 429 || status >= 500,
161
+ failureCode: "provider_readiness_unavailable",
162
+ failureCategory: "provider",
163
+ retryDisposition: "retry_same_inputs",
164
+ });
165
+ }
166
+
167
+ function providerActionRequired(message, failureCode) {
168
+ return new CiteArkError(message, {
169
+ retryable: false,
170
+
171
+ failureCode,
172
+ failureCategory: "provider",
173
+ retryDisposition: "never",
174
+ recoveryAction: "充值或更换可用的 OpenRouter API 密钥后,从原任务现场继续",
175
+ });
176
+ }
177
+
178
+ function finite(value) {
179
+ if (value === null || value === undefined || value === "") return null;
180
+ const number = Number(value);
181
+ return Number.isFinite(number) && number >= 0 ? number : null;
182
+ }
183
+
184
+ function finiteNonNegative(value, fallback) {
185
+ const number = Number(value);
186
+ return Number.isFinite(number) && number >= 0 ? number : fallback;
187
+ }
188
+
189
+ export const checkModelReadiness = checkOpenRouterReadiness;
@@ -0,0 +1,35 @@
1
+ import { createServer } from "node:http";
2
+ import { randomBytes } from "node:crypto";
3
+
4
+ export async function startModelReaderBridge(reader, { maxToolCalls = Infinity, onLimitExceeded = () => {} } = {}) {
5
+ const token = randomBytes(24).toString("hex");
6
+ let calls = 0, exhausted = false;
7
+ const definitions = reader.definitions.map(({ function: tool }) => ({ name: tool.name, description: tool.description,
8
+ inputSchema: tool.parameters, annotations: { readOnlyHint: true, destructiveHint: false } }));
9
+ const server = createServer(async (request, response) => {
10
+ try {
11
+ if (request.method !== "POST" || request.headers.authorization !== `Bearer ${token}`) { response.writeHead(403).end(); return; }
12
+ const chunks = []; let size = 0;
13
+ for await (const chunk of request) { size += chunk.length; if (size > 1_000_000) throw new Error("Oversized tool request"); chunks.push(chunk); }
14
+ const message = JSON.parse(Buffer.concat(chunks));
15
+ let value;
16
+ if (message.method === "tools/list") value = { tools: definitions };
17
+ else if (message.method === "tools/call") {
18
+ if (++calls > maxToolCalls) {
19
+ exhausted = true; onLimitExceeded();
20
+ throw new Error("Source inspection exceeded its bounded tool allowance");
21
+ }
22
+ if (!definitions.some((tool) => tool.name === message.params?.name)) throw new Error("Source inspection tool is unavailable");
23
+ const output = await reader.execute(message.params.name, message.params.arguments ?? {});
24
+ value = { content: [{ type: "text", text: JSON.stringify(output.value) },
25
+ ...(output.images ?? []).flatMap((image) => [{ type: "text", text: `Original physical PDF page ${image.page}` },
26
+ { type: "image", data: image.data, mimeType: "image/jpeg" }])] };
27
+ } else throw new Error("Unsupported reader method");
28
+ response.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(value));
29
+ } catch (error) { response.writeHead(400).end(JSON.stringify({ error: error.message })); }
30
+ });
31
+ await new Promise((resolve, reject) => { server.once("error", reject); server.listen(0, "127.0.0.1", resolve); });
32
+ return { endpoint: `http://127.0.0.1:${server.address().port}`, token, get exhausted() { return exhausted; },
33
+ close: () => new Promise((resolve) => { server.close(resolve); server.closeAllConnections(); }) };
34
+ }
35
+