0sec-cli 0.18.0 → 0.21.4
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/{0sec.js → 0.js} +84 -76
- package/LICENSE +1 -1
- package/README.md +74 -43
- package/chunks/adapt-loop-IGBRQAXO.js +18 -0
- package/chunks/{adgraph-JLGA6RYI.js → adgraph-AUDQTBYA.js} +5 -5
- package/chunks/agent/skills/frameworks/entra-id.yaml +2 -2
- package/chunks/agent/skills/techniques/assumption-mining.yaml +2 -2
- package/chunks/agent/skills/techniques/cve-poc-adaptation.yaml +2 -2
- package/chunks/agent/skills/techniques/entra-attack-paths.yaml +1 -1
- package/chunks/agent/skills/techniques/kernel-weaponization.yaml +1 -1
- package/chunks/agent/skills/techniques/llm-prompt-injection.yaml +2 -2
- package/chunks/agent/skills/techniques/npm-ecosystem.yaml +1 -1
- package/chunks/agent/skills/techniques/poc-verification.yaml +1 -1
- package/chunks/agent/skills/techniques/seedless-depth-review.yaml +20 -38
- package/chunks/{artifact-scraper-HQ7HLP6V.js → artifact-scraper-LNQJUORS.js} +6 -6
- package/chunks/{assumption-mining-5SNIVVDN.js → assumption-mining-4MJTP6H5.js} +24 -26
- package/chunks/{chunk-P6WKNFWX.js → chunk-2OQOQ2FZ.js} +8 -8
- package/chunks/{chunk-5G3ZXFBW.js → chunk-2WNCI664.js} +3 -3
- package/chunks/{chunk-AHTBZC3E.js → chunk-2WSXFFJZ.js} +4 -4
- package/chunks/{chunk-DW5UWPFY.js → chunk-4VGLO2YS.js} +6 -5
- package/chunks/{chunk-3KPWWJCI.js → chunk-5T6D5BVY.js} +88 -88
- package/chunks/{chunk-OEFNRYI2.js → chunk-6GHSR47F.js} +3 -6
- package/chunks/chunk-6YNKRUMC.js +4474 -0
- package/chunks/{chunk-BZNEY2ZS.js → chunk-ANDKY54G.js} +22390 -19895
- package/chunks/{chunk-53G27VPS.js → chunk-AZ7L3HFD.js} +7 -2
- package/chunks/{chunk-UR4ELX2Y.js → chunk-BACXRTQ4.js} +633 -316
- package/chunks/{chunk-UCYRA73C.js → chunk-CAJJRUTV.js} +4 -4
- package/chunks/{chunk-57ZENEX2.js → chunk-CGHCEN7W.js} +3 -3
- package/chunks/{chunk-AD7WXDH6.js → chunk-CL5KAUM6.js} +16 -16
- package/chunks/{chunk-ADOLI6DT.js → chunk-CXKDWFNO.js} +6 -6
- package/chunks/{chunk-RWONANDA.js → chunk-DQTNI3KY.js} +5 -5
- package/chunks/{chunk-BFR2CDV5.js → chunk-E2UOY5PJ.js} +288 -100
- package/chunks/{chunk-ATATQACO.js → chunk-ER3SUMYJ.js} +40 -18
- package/chunks/{chunk-5JI7L7KV.js → chunk-FAH6V4QM.js} +30515 -32242
- package/chunks/{chunk-6LKRLK2R.js → chunk-FHC2B7LN.js} +3 -3
- package/chunks/{chunk-XM5NLHYU.js → chunk-FWAYJ2IV.js} +16 -16
- package/chunks/{chunk-WVTBZEQO.js → chunk-G34QP2XW.js} +8 -8
- package/chunks/{chunk-MYVT64FN.js → chunk-GHWODZMR.js} +2 -2
- package/chunks/{chunk-F3WBKITT.js → chunk-GIQKESMN.js} +10 -10
- package/chunks/{chunk-2RMLOJVB.js → chunk-GVD6SJGH.js} +2 -2
- package/chunks/{chunk-WXQ6BJ6A.js → chunk-HBKCGJAV.js} +7 -7
- package/chunks/{chunk-SF4KZ4O3.js → chunk-HLFS6WV2.js} +2 -2
- package/chunks/chunk-HPQZHOIU.js +67 -0
- package/chunks/{chunk-TSK2AG6J.js → chunk-IMGWPNQM.js} +231 -1308
- package/chunks/{chunk-3MOLBTLS.js → chunk-K2PWUPUI.js} +2 -2
- package/chunks/{chunk-N7BOUYEV.js → chunk-NDJU5ZZ7.js} +14 -14
- package/chunks/{chunk-BVAZO4WA.js → chunk-NOPBXZUV.js} +3 -3
- package/chunks/{chunk-2JCCA2JL.js → chunk-O6SD4A36.js} +3 -3
- package/chunks/{chunk-SAFFWQW4.js → chunk-P6YG6TEZ.js} +6 -6
- package/chunks/{chunk-6L26OBWI.js → chunk-PC6RCKQV.js} +198 -11
- package/chunks/{chunk-LP3HHYQU.js → chunk-PD2BBBKT.js} +2 -2
- package/chunks/{chunk-BKZFDZ23.js → chunk-QCUFBVIZ.js} +3 -3
- package/chunks/{chunk-KLTNTE2Z.js → chunk-QZVG3BG4.js} +14 -14
- package/chunks/{chunk-ASUR522M.js → chunk-RF5QGKLR.js} +9 -9
- package/chunks/{chunk-YYRBQVFE.js → chunk-SBMB4OL3.js} +9 -9
- package/chunks/{chunk-IOL7D5YV.js → chunk-SQCBHFWN.js} +3 -3
- package/chunks/{chunk-BFRB6HB2.js → chunk-TF46UWFU.js} +3 -3
- package/chunks/chunk-TH2LW437.js +7829 -0
- package/chunks/chunk-TRH2PJSN.js +547 -0
- package/chunks/{chunk-RJWOYEOG.js → chunk-UEX7PFSE.js} +5 -5
- package/chunks/{chunk-VQG4FT5D.js → chunk-UFHN6RIU.js} +2 -2
- package/chunks/{chunk-WU6AFRAZ.js → chunk-V36HVTUL.js} +12 -12
- package/chunks/{chunk-IR537GON.js → chunk-VCSFCHAJ.js} +2 -2
- package/chunks/{chunk-YLMN3N25.js → chunk-VIT5ALXF.js} +3 -3
- package/chunks/{chunk-U5FLXGHT.js → chunk-VOAXLO4A.js} +10 -10
- package/chunks/{chunk-WEBMRWEG.js → chunk-W4JOXE4N.js} +83 -75
- package/chunks/{chunk-C2WXWFBB.js → chunk-WF5W75WY.js} +25 -25
- package/chunks/{chunk-SVJEDVYK.js → chunk-WPQLBN4S.js} +5 -5
- package/chunks/{chunk-72CISA2D.js → chunk-WZISK5VC.js} +37 -35
- package/chunks/{chunk-CMKXP5RR.js → chunk-Y5H6E24H.js} +3 -3
- package/chunks/{chunk-BATRQBOR.js → chunk-YA4PUM4H.js} +41 -41
- package/chunks/{chunk-JQHOLLTD.js → chunk-YHWWF4QY.js} +9 -9
- package/chunks/{chunk-GMT2AZKM.js → chunk-YPOA3W4O.js} +28 -28
- package/chunks/{chunk-7DQEV5QI.js → chunk-Z3IKZX3X.js} +4 -4
- package/chunks/codex-models-TYYV4JNN.js +15 -0
- package/chunks/{commands-TBC2F5CE.js → commands-WF5RG4I2.js} +3834 -931
- package/chunks/corpus-v1.json +2 -2
- package/chunks/data/appsec-archetypes.json +2 -2
- package/chunks/data/chromium-archetypes.json +1 -1
- package/chunks/data/freebsd-archetypes.json +1 -1
- package/chunks/data/kernel-archetypes.json +2 -2
- package/chunks/db-U3WQZVAO.js +16 -0
- package/chunks/{disclose-EKTWSCGW.js → disclose-67GZCDBO.js} +5 -5
- package/chunks/{dist-FMGSR3DW.js → dist-ACC3DZUZ.js} +11 -5
- package/chunks/{dist-4MAW5X6L.js → dist-EVTN3UTT.js} +6 -6
- package/chunks/{dist-4YICS2J2.js → dist-F6X62TQK.js} +286 -327
- package/chunks/dist-KEULE5IQ.js +21914 -0
- package/chunks/dist-XZB53PS5.js +53 -0
- package/chunks/eval-runner-SA6OUGHZ.js +26 -0
- package/chunks/example-manifest.json +2 -2
- package/chunks/{exploit-agent-45YQOHPJ.js → exploit-agent-36Q7XQFP.js} +4 -4
- package/chunks/{exploit-autoclimb-UZGEMGYT.js → exploit-autoclimb-J4OTSTZ6.js} +6 -6
- package/chunks/{exploit-climb-SG23KB2G.js → exploit-climb-PYEJDIC4.js} +12 -12
- package/chunks/fix-J2KBRJDV.js +12 -0
- package/chunks/{github-issues-MJ6OYOOU.js → github-issues-IGTTWM7V.js} +7 -7
- package/chunks/harness-GDUMKGSE.js +22 -0
- package/chunks/http-conformance-U5F6GKYG.js +11 -0
- package/chunks/http-sender-I5XFFY35.js +10 -0
- package/chunks/hunt-scan-UDJSCISU.js +36 -0
- package/chunks/{identity-6ZAIIWOR.js → identity-JTIVJSV7.js} +5 -5
- package/chunks/{kernel-primitive-7U6CWHXD.js → kernel-primitive-F2IXUFAX.js} +6 -6
- package/chunks/{kernel-vm-runner-3JAU4O6J.js → kernel-vm-runner-276LHIOV.js} +6 -6
- package/chunks/memsafety-scan-NHUOGTEX.js +16 -0
- package/chunks/{native-loop-RJCJPOUS.js → native-loop-3BSYVDUI.js} +17 -18
- package/chunks/{npm-detectors-KB5Y5ZFX.js → npm-detectors-VIYJXKFQ.js} +6 -6
- package/chunks/npm-dynamic-discovery-5J7FT3XM.js +13 -0
- package/chunks/orchestrate-DHBFRTSI.js +59 -0
- package/chunks/{pre-recon-cve-66EB6G4M.js → pre-recon-cve-X2K2P5YQ.js} +4 -4
- package/chunks/prepare-KYHGFWCJ.js +13 -0
- package/chunks/process-PNMC36JI.js +15 -0
- package/chunks/{replay-runner-45G3VU7X.js → replay-runner-LK2XFIVG.js} +7 -7
- package/chunks/{run-D56MPDKY.js → run-CBGE6DRQ.js} +2201 -1694
- package/chunks/{runtime-G7YWIO3B.js → runtime-PXO5G6UV.js} +11 -11
- package/chunks/runtime-XFWJTBUO.js +12 -0
- package/chunks/{scan-stream-FHI2FYZE.js → scan-stream-BMOCPKQX.js} +7 -7
- package/chunks/{scope-BI7BF4ZY.js → scope-NJHIDDTN.js} +4 -4
- package/chunks/{session-store-GZ4A4KYT.js → session-store-46WBZM22.js} +6 -6
- package/chunks/source-files-REHOM44I.js +12 -0
- package/chunks/{specdrift-WCLH6TTQ.js → specdrift-AFS54WEJ.js} +5 -5
- package/chunks/token-KRWP5JYA.js +72 -0
- package/chunks/token-util-DWTL33S3.js +8 -0
- package/chunks/variant-candidates-UH2WC2HA.js +13 -0
- package/chunks/{web-recon-prepass-JJGPEOLE.js → web-recon-prepass-SFXU44SS.js} +9 -9
- package/dashboard/assets/desktop-BT7RkC4q.js +32 -0
- package/dashboard/assets/desktop-OaBI0E0e.css +1 -0
- package/dashboard/assets/{findings-page-CACW9nw2.js → findings-page-B4rwF6fZ.js} +2 -2
- package/dashboard/assets/{format-PoISqsES.js → format-LNQSSpn6.js} +1 -1
- package/dashboard/assets/live-page-DoNwpPAO.js +1 -0
- package/dashboard/assets/{meta-tile-CcskIV_o.js → meta-tile-BMtBC-Qq.js} +1 -1
- package/dashboard/assets/operations-CMFVrXEe.css +2 -0
- package/dashboard/assets/operations-app-ER0gg7GN.js +2 -0
- package/dashboard/assets/{operations-D8nUP5_m.js → operations-e2CGYKB1.js} +2 -2
- package/dashboard/assets/{overview-page-DTGS8dPo.js → overview-page-CmeWH17A.js} +1 -1
- package/dashboard/assets/{page-header-MFVvEtZW.js → page-header-CIJyycfz.js} +1 -1
- package/dashboard/assets/scans-page-CuhzWhHx.js +1 -0
- package/dashboard/assets/{table-BYS-nIVA.js → table-BgsSBdke.js} +1 -1
- package/dashboard/assets/{tabs-DivxbhQy.js → tabs-vn6v7Bk6.js} +1 -1
- package/dashboard/desktop.html +3 -3
- package/dashboard/index.html +3 -3
- package/package.json +11 -11
- package/chunks/adapt-loop-SMH6V4PD.js +0 -18
- package/chunks/appsec-catalog-CBWHGTEL.js +0 -24
- package/chunks/chunk-242TMK6G.js +0 -110
- package/chunks/chunk-H2FFLZNK.js +0 -3
- package/chunks/chunk-QI233I24.js +0 -333
- package/chunks/chunk-RRMJC3ZE.js +0 -105
- package/chunks/chunk-Z5FA2XOX.js +0 -2798
- package/chunks/cost-ledger-NU63MYZM.js +0 -13
- package/chunks/db-KFWOAUUB.js +0 -16
- package/chunks/eval-runner-7ZF472KV.js +0 -27
- package/chunks/fix-IG7LYXH3.js +0 -12
- package/chunks/harness-7ODOLOWU.js +0 -22
- package/chunks/http-conformance-DU66MZIU.js +0 -11
- package/chunks/http-sender-GWH2IYEA.js +0 -10
- package/chunks/hunt-scan-MMKUOETU.js +0 -38
- package/chunks/memsafety-scan-J7SECV5W.js +0 -16
- package/chunks/npm-dynamic-discovery-AHROOZDB.js +0 -13
- package/chunks/orchestrate-OC2VZN2X.js +0 -62
- package/chunks/pipeline-BZK5BLJR.js +0 -14
- package/chunks/prepare-MYY743TK.js +0 -13
- package/chunks/process-LQRCS5OY.js +0 -13
- package/chunks/runtime-J7PLXZNM.js +0 -12
- package/chunks/source-files-PWRQR6LY.js +0 -12
- package/chunks/variant-candidates-YERCYMPD.js +0 -13
- package/dashboard/assets/0sec-icon-66SreztZ.gif +0 -0
- package/dashboard/assets/desktop-CXTQonNm.js +0 -32
- package/dashboard/assets/desktop-NOekk4QS.css +0 -1
- package/dashboard/assets/live-page-DrgQqQK5.js +0 -1
- package/dashboard/assets/operations-BKE8T-vY.css +0 -2
- package/dashboard/assets/operations-app-CQQhG54n.js +0 -2
- package/dashboard/assets/scans-page-ChVtq8Us.js +0 -1
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createRequire as
|
|
3
|
-
const require =
|
|
2
|
+
import { createRequire as __0CreateRequire } from "node:module";
|
|
3
|
+
const require = __0CreateRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
5
|
VERSION,
|
|
6
|
-
|
|
7
|
-
} from "./chunk-
|
|
6
|
+
cloudStateDir
|
|
7
|
+
} from "./chunk-PC6RCKQV.js";
|
|
8
8
|
|
|
9
9
|
// packages/core/dist/agent/feature-presets.js
|
|
10
10
|
var FP_MOAT_FLAGS = [
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
11
|
+
"ZERO_FEATURE_REACHABILITY_GATE",
|
|
12
|
+
"ZERO_FEATURE_MULTIMODAL",
|
|
13
|
+
"ZERO_FEATURE_PUBLISHABILITY_GATE",
|
|
14
|
+
"ZERO_FEATURE_POV_GATE",
|
|
15
|
+
"ZERO_FEATURE_POC_GEN_STATIC",
|
|
16
|
+
"ZERO_FEATURE_CONSENSUS_VERIFY"
|
|
17
17
|
];
|
|
18
18
|
var FEATURE_PRESETS = Object.freeze({
|
|
19
19
|
"fp-moat": FP_MOAT_FLAGS
|
|
@@ -41,7 +41,7 @@ function applyFeaturePreset(preset, env2 = process.env) {
|
|
|
41
41
|
return { preset, applied, preserved };
|
|
42
42
|
}
|
|
43
43
|
function applyFeaturePresetFromEnv(env2 = process.env) {
|
|
44
|
-
const raw = env2["
|
|
44
|
+
const raw = env2["ZERO_FEATURE_PRESET"];
|
|
45
45
|
if (!raw)
|
|
46
46
|
return void 0;
|
|
47
47
|
const preset = resolveFeaturePreset(raw);
|
|
@@ -63,73 +63,73 @@ var features = {
|
|
|
63
63
|
*
|
|
64
64
|
* Turn count is also a cost PROXY, and cost is already bounded directly by
|
|
65
65
|
* the token budget, so this never was the control that kept spend in check.
|
|
66
|
-
* Enable with `
|
|
66
|
+
* Enable with `ZERO_FEATURE_EARLY_STOP=1` for benchmark or A/B runs where a
|
|
67
67
|
* fixed turn budget per attempt is the point.
|
|
68
68
|
*/
|
|
69
69
|
get earlyStopRetry() {
|
|
70
|
-
return env("
|
|
70
|
+
return env("ZERO_FEATURE_EARLY_STOP", false);
|
|
71
71
|
},
|
|
72
72
|
/** Detect A-A-A and A-B-A-B loop patterns, inject warning */
|
|
73
73
|
get loopDetection() {
|
|
74
|
-
return env("
|
|
74
|
+
return env("ZERO_FEATURE_LOOP_DETECTION", true);
|
|
75
75
|
},
|
|
76
76
|
/** Compress middle messages when context exceeds 30k tokens */
|
|
77
77
|
get contextCompaction() {
|
|
78
|
-
return env("
|
|
78
|
+
return env("ZERO_FEATURE_CONTEXT_COMPACTION", true);
|
|
79
79
|
},
|
|
80
80
|
/**
|
|
81
81
|
* Re-send the opaque, model-bound Responses output item array on the next
|
|
82
82
|
* turn. Default ON; set to 0 only for matched retained-reasoning A/B runs.
|
|
83
83
|
*/
|
|
84
84
|
get retainedReasoning() {
|
|
85
|
-
return env("
|
|
85
|
+
return env("ZERO_FEATURE_RETAINED_REASONING", true);
|
|
86
86
|
},
|
|
87
87
|
/** Exploit script templates in shell prompt (blind SQLi, SSTI, auth chain) */
|
|
88
88
|
get scriptTemplates() {
|
|
89
|
-
return env("
|
|
89
|
+
return env("ZERO_FEATURE_SCRIPT_TEMPLATES", true);
|
|
90
90
|
},
|
|
91
91
|
/** Dynamic vulnerability playbooks injected after recon phase */
|
|
92
92
|
get dynamicPlaybooks() {
|
|
93
|
-
return env("
|
|
93
|
+
return env("ZERO_FEATURE_DYNAMIC_PLAYBOOKS", false);
|
|
94
94
|
},
|
|
95
95
|
/** Just-in-time atomic DO/DON'T rules injected on a matching tool action */
|
|
96
96
|
get ruleInjection() {
|
|
97
|
-
return env("
|
|
97
|
+
return env("ZERO_FEATURE_RULE_INJECTION", false);
|
|
98
98
|
},
|
|
99
99
|
/** Agent writes plan/creds to disk, injected at reflection checkpoints */
|
|
100
100
|
get externalMemory() {
|
|
101
|
-
return env("
|
|
101
|
+
return env("ZERO_FEATURE_EXTERNAL_MEMORY", false);
|
|
102
102
|
},
|
|
103
103
|
/** Inject prior attempt findings when retrying (LLM-summarized progress handoff) */
|
|
104
104
|
get progressHandoff() {
|
|
105
|
-
return env("
|
|
105
|
+
return env("ZERO_FEATURE_PROGRESS_HANDOFF", true);
|
|
106
106
|
},
|
|
107
107
|
/** Allow the agent to search the web for CVE details, docs, and technique references */
|
|
108
108
|
get webSearch() {
|
|
109
|
-
return env("
|
|
109
|
+
return env("ZERO_FEATURE_WEB_SEARCH", false);
|
|
110
110
|
},
|
|
111
111
|
/** Interactive PTY sessions for exploits requiring interactivity (reverse shells, DB clients, SSH) */
|
|
112
112
|
get ptySession() {
|
|
113
|
-
return env("
|
|
113
|
+
return env("ZERO_FEATURE_PTY_SESSION", false);
|
|
114
114
|
},
|
|
115
115
|
/**
|
|
116
116
|
* Persistent, COMPUTE-ONLY Python REPL (`python_exec`, Phase-0). A framed
|
|
117
117
|
* python3 kernel keeps state across calls for payload/parse/crypto/encode
|
|
118
118
|
* work; networking is blocked at the socket source whenever an engagement is
|
|
119
|
-
* active. Default OFF — opt in via
|
|
119
|
+
* active. Default OFF — opt in via ZERO_FEATURE_PYTHON_EXEC=1. Getter so
|
|
120
120
|
* the CLI `--features` flag (set after this module is imported) is honored at
|
|
121
121
|
* tool-dispatch time.
|
|
122
122
|
*/
|
|
123
123
|
get pythonExec() {
|
|
124
|
-
return env("
|
|
124
|
+
return env("ZERO_FEATURE_PYTHON_EXEC", false);
|
|
125
125
|
},
|
|
126
126
|
/**
|
|
127
127
|
* Expose the path-confined `analyze_binary` bridge to 0verse. Default OFF:
|
|
128
128
|
* a model may request a long-running binary analysis only after an operator
|
|
129
|
-
* opts in with
|
|
129
|
+
* opts in with ZERO_FEATURE_ZEROVERSE=1.
|
|
130
130
|
*/
|
|
131
131
|
get zeroverse() {
|
|
132
|
-
return env("
|
|
132
|
+
return env("ZERO_FEATURE_ZEROVERSE", false);
|
|
133
133
|
},
|
|
134
134
|
/**
|
|
135
135
|
* EGATS specialist routing (#557, HPTSA-inspired). When ON, an EGATS branch
|
|
@@ -146,22 +146,22 @@ var features = {
|
|
|
146
146
|
* benchmark harness. Implemented as a getter so the CLI `--features` flag
|
|
147
147
|
* (which sets the env var inside the command action, AFTER this module has
|
|
148
148
|
* been imported) is honored at routing time. Enable via
|
|
149
|
-
*
|
|
149
|
+
* ZERO_FEATURE_SPECIALIST_ROUTING=1.
|
|
150
150
|
*/
|
|
151
151
|
get specialistRouting() {
|
|
152
|
-
return env("
|
|
152
|
+
return env("ZERO_FEATURE_SPECIALIST_ROUTING", false);
|
|
153
153
|
},
|
|
154
154
|
/** Self-consistency voting: run the structured verify pipeline N times and take the majority vote */
|
|
155
155
|
get selfConsistencyVerify() {
|
|
156
|
-
return env("
|
|
156
|
+
return env("ZERO_FEATURE_CONSENSUS_VERIFY", false);
|
|
157
157
|
},
|
|
158
158
|
/** Multi-modal agreement: cross-validate findings against foxguard (Rust pattern scanner) */
|
|
159
159
|
get multiModalAgreement() {
|
|
160
|
-
return env("
|
|
160
|
+
return env("ZERO_FEATURE_MULTIMODAL", false);
|
|
161
161
|
},
|
|
162
162
|
/** Reachability gate: suppress findings whose sink is not reachable from an application entry point */
|
|
163
163
|
get reachabilityGate() {
|
|
164
|
-
return env("
|
|
164
|
+
return env("ZERO_FEATURE_REACHABILITY_GATE", false);
|
|
165
165
|
},
|
|
166
166
|
/**
|
|
167
167
|
* Publishability / in-scope gate (issue #537 / #539). Decides
|
|
@@ -173,14 +173,14 @@ var features = {
|
|
|
173
173
|
*
|
|
174
174
|
* Default OFF: this gate can suppress reproducible findings, so it must be
|
|
175
175
|
* explicitly opted into before any A/B claim. Disable/enable via
|
|
176
|
-
*
|
|
176
|
+
* ZERO_FEATURE_PUBLISHABILITY_GATE.
|
|
177
177
|
*/
|
|
178
178
|
get publishabilityGate() {
|
|
179
|
-
return env("
|
|
179
|
+
return env("ZERO_FEATURE_PUBLISHABILITY_GATE", false);
|
|
180
180
|
},
|
|
181
181
|
/** PoV gate: require a working, executable PoC per finding or downgrade to info */
|
|
182
182
|
get povGate() {
|
|
183
|
-
return env("
|
|
183
|
+
return env("ZERO_FEATURE_POV_GATE", false);
|
|
184
184
|
},
|
|
185
185
|
/**
|
|
186
186
|
* Intra-scan semantic dedupe post-pass (anchored incremental LLM
|
|
@@ -188,10 +188,10 @@ var features = {
|
|
|
188
188
|
* Marks duplicates with a canonical mapping + cluster reason instead of
|
|
189
189
|
* dropping them. Default OFF: it spends an LLM call per ≤50-finding batch
|
|
190
190
|
* after the scan, so it must be explicitly opted into before any A/B
|
|
191
|
-
* claim. Toggle via
|
|
191
|
+
* claim. Toggle via ZERO_FEATURE_SEMANTIC_DEDUPE.
|
|
192
192
|
*/
|
|
193
193
|
get semanticDedupe() {
|
|
194
|
-
return env("
|
|
194
|
+
return env("ZERO_FEATURE_SEMANTIC_DEDUPE", false);
|
|
195
195
|
},
|
|
196
196
|
/**
|
|
197
197
|
* Finding-specific remediation written by the model
|
|
@@ -207,33 +207,17 @@ var features = {
|
|
|
207
207
|
* degrade cost, never correctness.
|
|
208
208
|
*/
|
|
209
209
|
get llmRemediation() {
|
|
210
|
-
return env("
|
|
211
|
-
},
|
|
212
|
-
/**
|
|
213
|
-
* Per-finding impact assessment (`assessImpact`, `triage/impact-assessment.ts`)
|
|
214
|
-
* written by the model: reachability tier, weaponizability, blast radius,
|
|
215
|
-
* business-impact tier. Default OFF — one extra LLM call per non-false-positive
|
|
216
|
-
* finding at report time.
|
|
217
|
-
*
|
|
218
|
-
* When on, the assessment feeds three things it is otherwise absent from:
|
|
219
|
-
* a real CVSS exploitability vector (AV/PR/UI from the reachability tier
|
|
220
|
-
* rather than the AV:N/severity-floor guess), the advisory's Impact +
|
|
221
|
-
* attack-prerequisites section, and the vendor-notification impact line. When
|
|
222
|
-
* off, all three fall back to today's category/severity heuristics — so this
|
|
223
|
-
* flag strictly adds fidelity, never changes the no-assessment output.
|
|
224
|
-
*/
|
|
225
|
-
get impactAssessment() {
|
|
226
|
-
return env("0SEC_FEATURE_IMPACT_ASSESSMENT", false);
|
|
210
|
+
return env("ZERO_FEATURE_LLM_REMEDIATION", false);
|
|
227
211
|
},
|
|
228
212
|
/**
|
|
229
213
|
* Incremental finding ranking post-pass (decimal-insertion between ranked
|
|
230
214
|
* anchors, `triage/incremental-rank.ts`). Orders the report by comparative
|
|
231
215
|
* promise (exploitability × impact × evidence strength). Default OFF: it
|
|
232
216
|
* spends an LLM call per ≤50-finding batch; opt in before any A/B claim.
|
|
233
|
-
* Toggle via
|
|
217
|
+
* Toggle via ZERO_FEATURE_INCREMENTAL_RANK.
|
|
234
218
|
*/
|
|
235
219
|
get incrementalRank() {
|
|
236
|
-
return env("
|
|
220
|
+
return env("ZERO_FEATURE_INCREMENTAL_RANK", false);
|
|
237
221
|
},
|
|
238
222
|
/**
|
|
239
223
|
* Static-finding PoC generation (#666 / EPIC #674 Part A). For findings that
|
|
@@ -247,10 +231,10 @@ var features = {
|
|
|
247
231
|
*
|
|
248
232
|
* Default OFF: it spends LLM + execution budget per static finding and must
|
|
249
233
|
* be explicitly opted into before any A/B claim (A/B-able via the #656
|
|
250
|
-
* harness). Toggle via
|
|
234
|
+
* harness). Toggle via ZERO_FEATURE_POC_GEN_STATIC.
|
|
251
235
|
*/
|
|
252
236
|
get pocGenStatic() {
|
|
253
|
-
return env("
|
|
237
|
+
return env("ZERO_FEATURE_POC_GEN_STATIC", false);
|
|
254
238
|
},
|
|
255
239
|
/**
|
|
256
240
|
* Inline validation / validate-on-save (#554). When ON, the native attack
|
|
@@ -268,10 +252,10 @@ var features = {
|
|
|
268
252
|
* cost_per_flag claim. Implemented as a getter so the CLI `--features` flag
|
|
269
253
|
* (which sets the env var inside the command action, AFTER this module is
|
|
270
254
|
* imported) is honored at loop time. Enable via
|
|
271
|
-
*
|
|
255
|
+
* ZERO_FEATURE_INLINE_VALIDATION=1.
|
|
272
256
|
*/
|
|
273
257
|
get inlineValidation() {
|
|
274
|
-
return env("
|
|
258
|
+
return env("ZERO_FEATURE_INLINE_VALIDATION", false);
|
|
275
259
|
},
|
|
276
260
|
/**
|
|
277
261
|
* WordPress plugin/theme fingerprinter + OSV CVE lookup.
|
|
@@ -286,7 +270,7 @@ var features = {
|
|
|
286
270
|
* still honored at tool-dispatch time.
|
|
287
271
|
*/
|
|
288
272
|
get wpFingerprint() {
|
|
289
|
-
return env("
|
|
273
|
+
return env("ZERO_FEATURE_WP_FINGERPRINT", true);
|
|
290
274
|
},
|
|
291
275
|
/**
|
|
292
276
|
* MongoDB ObjectID forge tool. Exposes the `mongo_objectid` tool to the
|
|
@@ -296,23 +280,23 @@ var features = {
|
|
|
296
280
|
*
|
|
297
281
|
* Default ON — this is a pure-computation utility with no network or
|
|
298
282
|
* filesystem side effects, so there's no reason to gate it off. Disable
|
|
299
|
-
* via
|
|
283
|
+
* via ZERO_FEATURE_MONGO_OBJECTID_FORGE=0 or `--no-mongo-objectid-forge`
|
|
300
284
|
* for ablation. Implemented as a getter so the CLI `--features` flag
|
|
301
285
|
* (which sets the env var inside the command action, AFTER this module
|
|
302
286
|
* has been imported) is still honored at tool-dispatch time. Matches
|
|
303
287
|
* the wpFingerprint pattern above. See packages/core/src/agent/objectid-forge.ts.
|
|
304
288
|
*/
|
|
305
289
|
get mongoObjectIdForge() {
|
|
306
|
-
return env("
|
|
290
|
+
return env("ZERO_FEATURE_MONGO_OBJECTID_FORGE", true);
|
|
307
291
|
},
|
|
308
292
|
/**
|
|
309
|
-
* Live cloud-surface testing (
|
|
293
|
+
* Live cloud-surface testing (0#925). Exposes `cloud_s3_probe` and
|
|
310
294
|
* `cloud_validate_credentials` to the attack agent so it can test S3 buckets
|
|
311
295
|
* for public access + orphaned-bucket takeover and safely validate harvested
|
|
312
296
|
* AWS credentials (read-only). All probes are anonymous or read/verify-only —
|
|
313
297
|
* no writes, no data exfiltration beyond minimal proof.
|
|
314
298
|
*
|
|
315
|
-
* Default OFF (opt-in via
|
|
299
|
+
* Default OFF (opt-in via ZERO_FEATURE_CLOUD_SURFACE=1). Probing a target
|
|
316
300
|
* org's bucket-name space or validating its harvested credentials is recon
|
|
317
301
|
* AGAINST THAT ORG, so it is deny-by-default at two layers: this enablement
|
|
318
302
|
* flag, AND an engagement-scope check in the tool handlers (a configured
|
|
@@ -323,7 +307,7 @@ var features = {
|
|
|
323
307
|
* mongoObjectIdForge pattern above. See packages/core/src/agent/cloud-surface.ts.
|
|
324
308
|
*/
|
|
325
309
|
get cloudSurface() {
|
|
326
|
-
return env("
|
|
310
|
+
return env("ZERO_FEATURE_CLOUD_SURFACE", false);
|
|
327
311
|
},
|
|
328
312
|
/**
|
|
329
313
|
* #978 (ADR-060) — agent fan-out. When ON, the agent gets the `start_scan`
|
|
@@ -331,11 +315,11 @@ var features = {
|
|
|
331
315
|
* that run independently and report up the scan tree — the recursive
|
|
332
316
|
* sub-agent orchestration. Default OFF: fan-out multiplies scans/cost, so it
|
|
333
317
|
* stays opt-in even though the orchestrator enforces budget + a tree-level
|
|
334
|
-
* cap (max children/depth). Enable with
|
|
318
|
+
* cap (max children/depth). Enable with ZERO_FEATURE_AGENT_FANOUT=1.
|
|
335
319
|
* Getter so the CLI `--features` flag is honored at dispatch time.
|
|
336
320
|
*/
|
|
337
321
|
get agentFanout() {
|
|
338
|
-
return env("
|
|
322
|
+
return env("ZERO_FEATURE_AGENT_FANOUT", false);
|
|
339
323
|
},
|
|
340
324
|
// ── Phase-2 offensive-engine feature flags (dev-live-engine-recovery) ──
|
|
341
325
|
// Each gates a tool that RUNS/BUILDS untrusted code or WEAPONIZES. They are
|
|
@@ -347,21 +331,21 @@ var features = {
|
|
|
347
331
|
/**
|
|
348
332
|
* `memsafety_fuzz` — clones/builds/fuzzes a source tree (sanitizer builds +
|
|
349
333
|
* a fuzz harness), executing attacker-adjacent build scripts and native
|
|
350
|
-
* fuzz targets. Default OFF; opt in via
|
|
334
|
+
* fuzz targets. Default OFF; opt in via ZERO_FEATURE_MEMSAFETY=1. Building
|
|
351
335
|
* and running an untrusted tree is code execution, so it is deny-by-default
|
|
352
336
|
* behind this flag AND an engagement scope.
|
|
353
337
|
*/
|
|
354
338
|
get memsafetyFuzz() {
|
|
355
|
-
return env("
|
|
339
|
+
return env("ZERO_FEATURE_MEMSAFETY", false);
|
|
356
340
|
},
|
|
357
341
|
/**
|
|
358
342
|
* `npm_dynamic_discovery` — installs and RUNS untrusted npm packages under
|
|
359
343
|
* instrumentation to observe malicious install/runtime behaviour. Executing
|
|
360
344
|
* arbitrary package code is the whole point, so it is deny-by-default behind
|
|
361
|
-
* this flag AND an engagement scope. Opt in via
|
|
345
|
+
* this flag AND an engagement scope. Opt in via ZERO_FEATURE_NPM_DISCOVERY=1.
|
|
362
346
|
*/
|
|
363
347
|
get npmDynamicDiscovery() {
|
|
364
|
-
return env("
|
|
348
|
+
return env("ZERO_FEATURE_NPM_DISCOVERY", false);
|
|
365
349
|
},
|
|
366
350
|
/**
|
|
367
351
|
* `weaponize_kernel` — the kernel-exploit weaponization ladder. It only ever
|
|
@@ -369,19 +353,19 @@ var features = {
|
|
|
369
353
|
* present. Highest-caution capability: deny-by-default behind this flag AND
|
|
370
354
|
* an engagement scope AND a runtime artifact-presence check (the handler
|
|
371
355
|
* refuses when the kernel-VM assets are absent). Opt in via
|
|
372
|
-
*
|
|
356
|
+
* ZERO_FEATURE_KERNEL_WEAPONIZE=1.
|
|
373
357
|
*/
|
|
374
358
|
get kernelWeaponize() {
|
|
375
|
-
return env("
|
|
359
|
+
return env("ZERO_FEATURE_KERNEL_WEAPONIZE", false);
|
|
376
360
|
},
|
|
377
361
|
/**
|
|
378
362
|
* `cve_adapt` — adapts a public CVE PoC to the target and RUNS it to confirm
|
|
379
363
|
* exploitability. Running an adapted exploit is code execution against the
|
|
380
364
|
* target, so it is deny-by-default behind this flag AND an engagement scope.
|
|
381
|
-
* Opt in via
|
|
365
|
+
* Opt in via ZERO_FEATURE_CVE_ADAPT=1.
|
|
382
366
|
*/
|
|
383
367
|
get cveAdapt() {
|
|
384
|
-
return env("
|
|
368
|
+
return env("ZERO_FEATURE_CVE_ADAPT", false);
|
|
385
369
|
},
|
|
386
370
|
/**
|
|
387
371
|
* Anti-honeypot flag-shape validator. When the agent calls the `done`
|
|
@@ -392,7 +376,7 @@ var features = {
|
|
|
392
376
|
*
|
|
393
377
|
* Default ON because legitimate flags pass the shape check trivially
|
|
394
378
|
* and the false-positive rate on real flags should be near zero. Turn
|
|
395
|
-
* off via `
|
|
379
|
+
* off via `ZERO_FEATURE_DECOY_DETECTION=0` or the CLI flag
|
|
396
380
|
* `--no-decoy-detection` for ablation/testing.
|
|
397
381
|
*
|
|
398
382
|
* Implemented as a getter so the CLI flag (which flips the env var
|
|
@@ -402,7 +386,7 @@ var features = {
|
|
|
402
386
|
* packages/core/src/agent/flag-validator.ts.
|
|
403
387
|
*/
|
|
404
388
|
get decoyDetection() {
|
|
405
|
-
return env("
|
|
389
|
+
return env("ZERO_FEATURE_DECOY_DETECTION", true);
|
|
406
390
|
},
|
|
407
391
|
// ── Always-on triage filters (default ON, ablatable for A/B testing) ──
|
|
408
392
|
/**
|
|
@@ -411,13 +395,13 @@ var features = {
|
|
|
411
395
|
* sink names and rejects findings that look like "the function did its job".
|
|
412
396
|
*
|
|
413
397
|
* Default ON because that's the existing v0.6.0 behavior. Can be disabled
|
|
414
|
-
* via
|
|
398
|
+
* via ZERO_FEATURE_HOLDING_IT_WRONG=0 to test whether this filter is
|
|
415
399
|
* suppressing real signal — the ceiling-analysis from 2026-04-06 identified
|
|
416
400
|
* this as the strongest candidate for the unexplained XBOW finding-density
|
|
417
401
|
* collapse from 14 → 4 between `features=none` and `features=all`.
|
|
418
402
|
*/
|
|
419
403
|
get holdingItWrong() {
|
|
420
|
-
return env("
|
|
404
|
+
return env("ZERO_FEATURE_HOLDING_IT_WRONG", true);
|
|
421
405
|
},
|
|
422
406
|
/**
|
|
423
407
|
* `evidence_completeness <= 0.5` reject (`packages/core/src/agentic-scanner.ts:591`).
|
|
@@ -425,10 +409,10 @@ var features = {
|
|
|
425
409
|
* gather enough cross-source evidence (request + response + analysis + ...).
|
|
426
410
|
*
|
|
427
411
|
* Default ON because that's the existing v0.6.0 behavior. Can be disabled
|
|
428
|
-
* via
|
|
412
|
+
* via ZERO_FEATURE_EVIDENCE_GATE=0 for ablation.
|
|
429
413
|
*/
|
|
430
414
|
get evidenceGate() {
|
|
431
|
-
return env("
|
|
415
|
+
return env("ZERO_FEATURE_EVIDENCE_GATE", true);
|
|
432
416
|
},
|
|
433
417
|
/**
|
|
434
418
|
* Learned per-finding triage router (`packages/core/src/triage/learned-router.ts`).
|
|
@@ -439,17 +423,17 @@ var features = {
|
|
|
439
423
|
* the scan's slice type (xbow-wb, xbow-bb, npm).
|
|
440
424
|
*
|
|
441
425
|
* Default OFF until the router is validated via A/B testing on xbow-bench
|
|
442
|
-
* and npm-bench. See
|
|
426
|
+
* and npm-bench. See 0#113 for the design doc.
|
|
443
427
|
*/
|
|
444
428
|
get learnedRouter() {
|
|
445
|
-
return env("
|
|
429
|
+
return env("ZERO_FEATURE_LEARNED_ROUTER", false);
|
|
446
430
|
},
|
|
447
431
|
/**
|
|
448
432
|
* Dynamic per-finding triage routing (`packages/core/src/triage/router/`).
|
|
449
433
|
* When enabled, every finding is sent through a `RouterModel` that
|
|
450
434
|
* decides which subset of the 11 triage layers to invoke for that
|
|
451
435
|
* specific finding. v0 ships an explicit-rule router encoded from the
|
|
452
|
-
*
|
|
436
|
+
* 0#72 per-profile ablation; a learned classifier replaces the
|
|
453
437
|
* rules in a follow-up PR without touching the dispatch site.
|
|
454
438
|
*
|
|
455
439
|
* Distinct from `learnedRouter` above: `learnedRouter` is the XGBoost
|
|
@@ -458,25 +442,25 @@ var features = {
|
|
|
458
442
|
* the dispatch router gates which layers run AFTER the TP/FP score
|
|
459
443
|
* model has spoken.
|
|
460
444
|
*
|
|
461
|
-
* Default OFF — opt in via
|
|
462
|
-
*
|
|
445
|
+
* Default OFF — opt in via ZERO_FEATURE_DYNAMIC_TRIAGE=1. See
|
|
446
|
+
* 0#113 for the design doc and 0#67 for the joint paper plan.
|
|
463
447
|
*/
|
|
464
448
|
get dynamicTriageRouting() {
|
|
465
|
-
return env("
|
|
449
|
+
return env("ZERO_FEATURE_DYNAMIC_TRIAGE", false);
|
|
466
450
|
},
|
|
467
451
|
/**
|
|
468
452
|
* Opt-in cloud-sink webhook integration (`packages/core/src/cloud-sink.ts`).
|
|
469
|
-
* When enabled AND the user has set
|
|
453
|
+
* When enabled AND the user has set ZERO_CLOUD_SINK + ZERO_CLOUD_SCAN_ID,
|
|
470
454
|
* every finding and the final scan report are POSTed to the configured
|
|
471
455
|
* remote endpoint in real time.
|
|
472
456
|
*
|
|
473
457
|
* Default ON so the env-var trio is sufficient to enable streaming, but the
|
|
474
458
|
* flag exists so operators can force-disable the integration in environments
|
|
475
459
|
* where outbound HTTP from the scanner is not desired (e.g. air-gapped CI).
|
|
476
|
-
* Disable via
|
|
460
|
+
* Disable via ZERO_FEATURE_CLOUD_SINK=0.
|
|
477
461
|
*/
|
|
478
462
|
get cloudSink() {
|
|
479
|
-
return env("
|
|
463
|
+
return env("ZERO_FEATURE_CLOUD_SINK", true);
|
|
480
464
|
},
|
|
481
465
|
/**
|
|
482
466
|
* Pre-recon CVE check (`packages/core/src/pre-recon-cve.ts`).
|
|
@@ -487,10 +471,10 @@ var features = {
|
|
|
487
471
|
* where the agent has source access but no concrete leads.
|
|
488
472
|
*
|
|
489
473
|
* Default ON in white-box mode (no-op in black-box). Disable via
|
|
490
|
-
*
|
|
474
|
+
* ZERO_FEATURE_PRE_RECON_CVE=0 for ablation.
|
|
491
475
|
*/
|
|
492
476
|
get preReconCve() {
|
|
493
|
-
return env("
|
|
477
|
+
return env("ZERO_FEATURE_PRE_RECON_CVE", true);
|
|
494
478
|
},
|
|
495
479
|
/**
|
|
496
480
|
* Deterministic web-recon pre-pass (`packages/core/src/stages/web-recon-prepass.ts`).
|
|
@@ -501,25 +485,25 @@ var features = {
|
|
|
501
485
|
* checks. It EMITS findings directly for what it can prove and injects a
|
|
502
486
|
* "pursue these leads" block into the system prompt for what it can only hint.
|
|
503
487
|
*
|
|
504
|
-
* Default ON (no-op in non-web modes). Gated behind
|
|
488
|
+
* Default ON (no-op in non-web modes). Gated behind ZERO_FEATURE_WEB_RECON
|
|
505
489
|
* so it can be disabled for ablation or offline runs. Implemented as a getter
|
|
506
490
|
* so the CLI `--features` flag (which sets the env var inside the command
|
|
507
491
|
* action, AFTER this module has been imported) is honored at stage time.
|
|
508
492
|
*/
|
|
509
493
|
get webRecon() {
|
|
510
|
-
return env("
|
|
494
|
+
return env("ZERO_FEATURE_WEB_RECON", true);
|
|
511
495
|
},
|
|
512
496
|
/**
|
|
513
497
|
* Best-effort target-history preflight for source review. When a local repo
|
|
514
|
-
* path is known,
|
|
498
|
+
* path is known, 0 infers repository/package/product hints, queries live
|
|
515
499
|
* prior-vulnerability intel, and injects a compact audit-graph summary into
|
|
516
500
|
* the review prompt before the agent starts.
|
|
517
501
|
*
|
|
518
502
|
* Default ON for white-box/source-review modes. Disable via
|
|
519
|
-
*
|
|
503
|
+
* ZERO_FEATURE_TARGET_HISTORY_PRESEED=0 for offline or ablation runs.
|
|
520
504
|
*/
|
|
521
505
|
get targetHistoryPreseed() {
|
|
522
|
-
return env("
|
|
506
|
+
return env("ZERO_FEATURE_TARGET_HISTORY_PRESEED", true);
|
|
523
507
|
},
|
|
524
508
|
/**
|
|
525
509
|
* Preserve credential / exploit-bearing messages verbatim during
|
|
@@ -534,15 +518,15 @@ var features = {
|
|
|
534
518
|
* (a handful of extra messages preserved verbatim in the user
|
|
535
519
|
* compaction-summary block) is small. BoxPwnr-inspired: see
|
|
536
520
|
* `src/boxpwnr/solvers/single_loop_compactation.py` in 0ca/BoxPwnr,
|
|
537
|
-
* and
|
|
521
|
+
* and 0#229 for the design discussion.
|
|
538
522
|
*
|
|
539
523
|
* Implemented as a getter so the CLI `--features` flag — which sets
|
|
540
524
|
* the env var inside the command action AFTER this module is imported
|
|
541
525
|
* — is still honored at compaction time. Disable via
|
|
542
|
-
*
|
|
526
|
+
* ZERO_FEATURE_PRESERVE_CRITICAL_MESSAGES=0 for ablation.
|
|
543
527
|
*/
|
|
544
528
|
get preserveCriticalMessages() {
|
|
545
|
-
return env("
|
|
529
|
+
return env("ZERO_FEATURE_PRESERVE_CRITICAL_MESSAGES", true);
|
|
546
530
|
},
|
|
547
531
|
/**
|
|
548
532
|
* Two-stage budget-warning injection in the agent loop (#408).
|
|
@@ -558,14 +542,14 @@ var features = {
|
|
|
558
542
|
* single short user-message injection at two specific turn boundaries,
|
|
559
543
|
* and the win on long benchmarks (clean handoff instead of stray
|
|
560
544
|
* exploration on the last turn) is well-documented in Strix's
|
|
561
|
-
* implementation. Disable via
|
|
545
|
+
* implementation. Disable via ZERO_FEATURE_BUDGET_WARNINGS=0 for
|
|
562
546
|
* ablation. Implemented as a getter so the CLI `--features` flag —
|
|
563
547
|
* which sets the env var inside the command action AFTER this module
|
|
564
548
|
* is imported — is still honored at injection time (matches the
|
|
565
549
|
* wpFingerprint / preserveCriticalMessages pattern).
|
|
566
550
|
*/
|
|
567
551
|
get budgetWarnings() {
|
|
568
|
-
return env("
|
|
552
|
+
return env("ZERO_FEATURE_BUDGET_WARNINGS", true);
|
|
569
553
|
},
|
|
570
554
|
/**
|
|
571
555
|
* Per-file orchestration for the research and audit stages (#285).
|
|
@@ -579,14 +563,14 @@ var features = {
|
|
|
579
563
|
* Trade-off: total token spend grows roughly N × per-file budget instead
|
|
580
564
|
* of capped at a single session's budget. For a 50-file package, that
|
|
581
565
|
* could be a 5-10× cost increase on research. Disable via
|
|
582
|
-
* `
|
|
566
|
+
* `ZERO_FEATURE_PER_ITEM_ORCHESTRATION=0` to revert to the shared-session
|
|
583
567
|
* behavior — useful for cost-bounded benchmarks.
|
|
584
568
|
*
|
|
585
569
|
* Implemented as a getter so the env var is honored at orchestration time
|
|
586
570
|
* (matches the wpFingerprint / mongoObjectIdForge pattern).
|
|
587
571
|
*/
|
|
588
572
|
get perItemOrchestration() {
|
|
589
|
-
return env("
|
|
573
|
+
return env("ZERO_FEATURE_PER_ITEM_ORCHESTRATION", true);
|
|
590
574
|
},
|
|
591
575
|
/**
|
|
592
576
|
* JIT skill loading (`packages/core/src/agent/skills/`).
|
|
@@ -595,20 +579,21 @@ var features = {
|
|
|
595
579
|
* them into working context mid-scan. Skills replace the monolithic
|
|
596
580
|
* playbook injection with targeted, on-demand knowledge (#410, #457).
|
|
597
581
|
*
|
|
598
|
-
* Default OFF
|
|
582
|
+
* Default OFF unless an assigned cloud methodology manifest opts this run in.
|
|
583
|
+
* An explicit feature flag still takes precedence over that default.
|
|
599
584
|
* Implemented as a getter so the CLI `--features` flag — which sets
|
|
600
585
|
* the env var inside the command action, AFTER this module has been
|
|
601
586
|
* imported — is still honored at tool-dispatch time.
|
|
602
587
|
*/
|
|
603
588
|
get jitSkills() {
|
|
604
|
-
return env("
|
|
589
|
+
return env("ZERO_FEATURE_JIT_SKILLS", Boolean(process.env["ZERO_AUDIT_SKILLS_MANIFEST"]?.trim()));
|
|
605
590
|
},
|
|
606
591
|
/**
|
|
607
592
|
* Execution-journal shadow mode (#494, first additive slice).
|
|
608
593
|
*
|
|
609
594
|
* When ON, the live agent loop ALSO writes append-only journal entries
|
|
610
595
|
* (`tool_call`, `tool_result`, `finding`, `done`) to
|
|
611
|
-
* `~/.
|
|
596
|
+
* `~/.0/runs/<scanId>/journal.jsonl` as it runs — a durable,
|
|
612
597
|
* replayable trace alongside the existing in-memory conversation window.
|
|
613
598
|
* This is strictly additive: the loop continues to drive off its own
|
|
614
599
|
* conversation state, the journal is write-only here, and a failed
|
|
@@ -621,11 +606,11 @@ var features = {
|
|
|
621
606
|
* moat-ablation harness before any A/B claim. Implemented as a getter so
|
|
622
607
|
* the CLI `--features` flag (which sets the env var inside the command
|
|
623
608
|
* action, AFTER this module has been imported) is honored at loop time.
|
|
624
|
-
* Enable via
|
|
609
|
+
* Enable via ZERO_FEATURE_EXECUTION_JOURNAL=1 or `--features
|
|
625
610
|
* execution-journal`.
|
|
626
611
|
*/
|
|
627
612
|
get executionJournal() {
|
|
628
|
-
return env("
|
|
613
|
+
return env("ZERO_FEATURE_EXECUTION_JOURNAL", false);
|
|
629
614
|
},
|
|
630
615
|
/**
|
|
631
616
|
* Execution-journal context routing (#494, slice 2).
|
|
@@ -641,7 +626,7 @@ var features = {
|
|
|
641
626
|
* Independent of `executionJournal` (the shadow-WRITE flag) on purpose so
|
|
642
627
|
* the moat-ablation harness can toggle write and route separately for a
|
|
643
628
|
* clean A/B. Rehydrate is a READER, though, so it only does anything when
|
|
644
|
-
* a journal was written for the run — it reads `~/.
|
|
629
|
+
* a journal was written for the run — it reads `~/.0/runs/<scanId>/
|
|
645
630
|
* journal.jsonl` regardless of how it got there (shadow mode this slice,
|
|
646
631
|
* or specialists in a later slice). When the journal is missing, empty, or
|
|
647
632
|
* corrupt the loop falls back to the existing DB-blob / fresh-prompt
|
|
@@ -654,11 +639,11 @@ var features = {
|
|
|
654
639
|
* must be explicitly opted into before any A/B claim. Implemented as a
|
|
655
640
|
* getter so the CLI `--features` flag (which sets the env var inside the
|
|
656
641
|
* command action, AFTER this module has been imported) is honored at loop
|
|
657
|
-
* time. Enable via
|
|
642
|
+
* time. Enable via ZERO_FEATURE_JOURNAL_REHYDRATE=1 or `--features
|
|
658
643
|
* journal-rehydrate`.
|
|
659
644
|
*/
|
|
660
645
|
get journalRehydrate() {
|
|
661
|
-
return env("
|
|
646
|
+
return env("ZERO_FEATURE_JOURNAL_REHYDRATE", false);
|
|
662
647
|
},
|
|
663
648
|
/**
|
|
664
649
|
* Loot / foothold ledger for opportunistic exploit chaining (#567).
|
|
@@ -678,14 +663,14 @@ var features = {
|
|
|
678
663
|
* tool), matches the `preserveCriticalMessages` rationale — recovering a
|
|
679
664
|
* credential in turn 12 that's needed in turn 38 is a large win on long-tail
|
|
680
665
|
* challenges — and the cost (a short, size-capped block per turn) is small.
|
|
681
|
-
* Disable via
|
|
666
|
+
* Disable via ZERO_FEATURE_LOOT_LEDGER=0 or `--no-loot-ledger` for
|
|
682
667
|
* ablation. Implemented as a getter so the CLI `--features` flag (which sets
|
|
683
668
|
* the env var inside the command action, AFTER this module has been
|
|
684
669
|
* imported) is honored at tool-dispatch / injection time — matches the
|
|
685
670
|
* wpFingerprint / preserveCriticalMessages pattern.
|
|
686
671
|
*/
|
|
687
672
|
get lootLedger() {
|
|
688
|
-
return env("
|
|
673
|
+
return env("ZERO_FEATURE_LOOT_LEDGER", true);
|
|
689
674
|
},
|
|
690
675
|
/**
|
|
691
676
|
* Typed TODO / plan ledger (`packages/core/src/agent/task-ledger.ts`).
|
|
@@ -704,13 +689,13 @@ var features = {
|
|
|
704
689
|
* failure mode of an unused tool is a few hundred wasted schema tokens
|
|
705
690
|
* rather than wrong behavior. Note for whoever publishes benchmark numbers
|
|
706
691
|
* next: this DOES change the default tool list, so re-baseline before
|
|
707
|
-
* quoting a figure across this change. Disable via
|
|
692
|
+
* quoting a figure across this change. Disable via ZERO_FEATURE_AGENT_PLAN=0
|
|
708
693
|
* or `--features no-agent-plan` for ablation. Getter so the CLI `--features`
|
|
709
694
|
* flag (which sets the env var AFTER this module is imported) is honored at
|
|
710
695
|
* tool-dispatch time.
|
|
711
696
|
*/
|
|
712
697
|
get agentPlan() {
|
|
713
|
-
return env("
|
|
698
|
+
return env("ZERO_FEATURE_AGENT_PLAN", false);
|
|
714
699
|
},
|
|
715
700
|
/**
|
|
716
701
|
* Task-drift detection (`packages/core/src/agent/drift.ts`).
|
|
@@ -733,10 +718,10 @@ var features = {
|
|
|
733
718
|
* to a newly-discovered lead is lexically indistinguishable from a derail.
|
|
734
719
|
* Repo convention is explicit that behavior-steering features stay opt-in
|
|
735
720
|
* until A/B'd, and this is squarely one. Enable via
|
|
736
|
-
*
|
|
721
|
+
* ZERO_FEATURE_DRIFT_DETECTION=1 or `--features drift-detection`.
|
|
737
722
|
*/
|
|
738
723
|
get driftDetection() {
|
|
739
|
-
return env("
|
|
724
|
+
return env("ZERO_FEATURE_DRIFT_DETECTION", false);
|
|
740
725
|
},
|
|
741
726
|
/**
|
|
742
727
|
* OAST out-of-band interaction collaborator + oracle (#659).
|
|
@@ -748,12 +733,12 @@ var features = {
|
|
|
748
733
|
* evidence and feeds the loot ledger.
|
|
749
734
|
*
|
|
750
735
|
* Default OFF — the tools are inert without a deployed collaborator. Enable
|
|
751
|
-
* with
|
|
736
|
+
* with ZERO_FEATURE_OAST=1 AND point ZERO_OAST_URL at the self-hosted
|
|
752
737
|
* collaborator server (see packages/core/src/oast/server.ts). Getter (not a
|
|
753
738
|
* const) so the CLI `--features` flag is honored at tool-dispatch time.
|
|
754
739
|
*/
|
|
755
740
|
get oastCollaborator() {
|
|
756
|
-
return env("
|
|
741
|
+
return env("ZERO_FEATURE_OAST", false);
|
|
757
742
|
},
|
|
758
743
|
/**
|
|
759
744
|
* Anthropic prompt caching (`cache_control: {type: "ephemeral"}`) over the
|
|
@@ -776,17 +761,17 @@ var features = {
|
|
|
776
761
|
* never see an Anthropic-shaped field regardless of this flag (see
|
|
777
762
|
* `providerSupportsPromptCache`).
|
|
778
763
|
*
|
|
779
|
-
* Disable via
|
|
764
|
+
* Disable via ZERO_FEATURE_PROMPT_CACHE=0 — worth doing only to isolate a
|
|
780
765
|
* suspected provider-side caching bug, or to measure the uncached baseline.
|
|
781
766
|
* Implemented as a getter so a late env mutation (CLI `--features`, which
|
|
782
767
|
* runs after this module is imported) is honoured at request-build time.
|
|
783
768
|
*/
|
|
784
769
|
get promptCache() {
|
|
785
|
-
return env("
|
|
770
|
+
return env("ZERO_FEATURE_PROMPT_CACHE", true);
|
|
786
771
|
}
|
|
787
772
|
};
|
|
788
773
|
function presetRaisesDefault(key) {
|
|
789
|
-
const raw = process.env["
|
|
774
|
+
const raw = process.env["ZERO_FEATURE_PRESET"];
|
|
790
775
|
if (!raw)
|
|
791
776
|
return false;
|
|
792
777
|
const preset = resolveFeaturePreset(raw);
|
|
@@ -884,7 +869,7 @@ function sanitizeFields(input) {
|
|
|
884
869
|
var LEVEL_RANK = { info: 10, warn: 20, error: 30 };
|
|
885
870
|
var OFF_RANK = Number.POSITIVE_INFINITY;
|
|
886
871
|
function minimumRank() {
|
|
887
|
-
const raw = process.env["
|
|
872
|
+
const raw = process.env["ZERO_DIAG_LEVEL"];
|
|
888
873
|
if (!raw)
|
|
889
874
|
return LEVEL_RANK.info;
|
|
890
875
|
switch (raw.trim().toLowerCase()) {
|
|
@@ -904,9 +889,9 @@ function minimumRank() {
|
|
|
904
889
|
function formatDiagnosticLine(event) {
|
|
905
890
|
const keys = Object.keys(event.fields);
|
|
906
891
|
if (keys.length === 0)
|
|
907
|
-
return `[
|
|
892
|
+
return `[0] ${event.message}`;
|
|
908
893
|
const rendered = keys.map((k) => `${k}=${event.fields[k]}`).join(" ");
|
|
909
|
-
return `[
|
|
894
|
+
return `[0] ${event.message} (${rendered})`;
|
|
910
895
|
}
|
|
911
896
|
var stderrDiagnosticSink = {
|
|
912
897
|
emit(event) {
|
|
@@ -1087,19 +1072,22 @@ function loadCloudCredentials(opts = {}) {
|
|
|
1087
1072
|
const env2 = opts.env ?? process.env;
|
|
1088
1073
|
const warn = opts.warn ?? ((m) => process.stderr.write(`${m}
|
|
1089
1074
|
`));
|
|
1090
|
-
const envTok = env2["0SEC_CLOUD_TOKEN"]?.trim();
|
|
1075
|
+
const envTok = env2["ZERO_CLOUD_TOKEN"]?.trim() || env2["0SEC_CLOUD_TOKEN"]?.trim();
|
|
1091
1076
|
if (envTok) {
|
|
1092
|
-
const envHost = normaliseHost(env2["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
|
|
1077
|
+
const envHost = normaliseHost(env2["ZERO_CLOUD_HOST"]?.trim() ?? env2["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
|
|
1078
|
+
if (!env2["ZERO_CLOUD_TOKEN"]?.trim()) {
|
|
1079
|
+
warn("[0 cloud] using legacy 0SEC_CLOUD_* credentials; re-run `0 auth login` to migrate to ZERO_CLOUD_*.");
|
|
1080
|
+
}
|
|
1093
1081
|
return { host: envHost, token: envTok, source: "env" };
|
|
1094
1082
|
}
|
|
1095
|
-
const path = join(
|
|
1083
|
+
const path = join(cloudStateDir(opts.homeDir, env2), "cloud.env");
|
|
1096
1084
|
let raw;
|
|
1097
1085
|
try {
|
|
1098
1086
|
raw = readFileSync(path, "utf-8");
|
|
1099
1087
|
} catch (err) {
|
|
1100
1088
|
const code = err.code;
|
|
1101
1089
|
if (code === "ENOENT") {
|
|
1102
|
-
throw new CloudAuthMissingError(`
|
|
1090
|
+
throw new CloudAuthMissingError(`0-cloud credentials not found. Run \`${env2["ZERO_DEV_SOURCE_ROOT"]?.trim() ? "0dev" : "0"} auth login\`.`);
|
|
1103
1091
|
}
|
|
1104
1092
|
throw err;
|
|
1105
1093
|
}
|
|
@@ -1107,22 +1095,25 @@ function loadCloudCredentials(opts = {}) {
|
|
|
1107
1095
|
const st = statSync(path);
|
|
1108
1096
|
const mode = st.mode & 511;
|
|
1109
1097
|
if (mode !== 384) {
|
|
1110
|
-
warn(`[
|
|
1098
|
+
warn(`[0 cloud] WARNING: ${path} mode is ${mode.toString(8).padStart(3, "0")} (expected 600). Run: chmod 600 ${path}`);
|
|
1111
1099
|
}
|
|
1112
1100
|
} catch {
|
|
1113
1101
|
}
|
|
1114
1102
|
const parsed = parseEnvFile(raw);
|
|
1115
|
-
const fileTok = parsed["0SEC_CLOUD_TOKEN"]?.trim();
|
|
1103
|
+
const fileTok = parsed["ZERO_CLOUD_TOKEN"]?.trim() || parsed["0SEC_CLOUD_TOKEN"]?.trim();
|
|
1116
1104
|
if (!fileTok) {
|
|
1117
|
-
throw new CloudAuthMissingError(`
|
|
1105
|
+
throw new CloudAuthMissingError(`0-cloud credentials in ${path} are incomplete: ZERO_CLOUD_TOKEN is required.`);
|
|
1106
|
+
}
|
|
1107
|
+
if (!parsed["ZERO_CLOUD_TOKEN"]?.trim()) {
|
|
1108
|
+
warn("[0 cloud] using legacy 0SEC_CLOUD_* credentials; re-run `0 auth login` to migrate to ZERO_CLOUD_*.");
|
|
1118
1109
|
}
|
|
1119
|
-
const fileHost = normaliseHost(parsed["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
|
|
1110
|
+
const fileHost = normaliseHost(parsed["ZERO_CLOUD_HOST"]?.trim() ?? parsed["0SEC_CLOUD_HOST"]?.trim() ?? env2["ZERO_CLOUD_HOST"]?.trim() ?? env2["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
|
|
1120
1111
|
return { host: fileHost, token: fileTok, source: "file" };
|
|
1121
1112
|
}
|
|
1122
1113
|
function normaliseHost(host) {
|
|
1123
1114
|
let h = host;
|
|
1124
1115
|
if (!/^https?:\/\//.test(h)) {
|
|
1125
|
-
throw new CloudAuthMissingError(`
|
|
1116
|
+
throw new CloudAuthMissingError(`ZERO_CLOUD_HOST must be an http(s) URL (got ${JSON.stringify(host)}).`);
|
|
1126
1117
|
}
|
|
1127
1118
|
while (h.endsWith("/"))
|
|
1128
1119
|
h = h.slice(0, -1);
|
|
@@ -1167,26 +1158,92 @@ var CloudError = class extends Error {
|
|
|
1167
1158
|
};
|
|
1168
1159
|
var CloudUnauthorizedError = class extends CloudError {
|
|
1169
1160
|
constructor(path) {
|
|
1170
|
-
super(`
|
|
1161
|
+
super(`0-cloud auth rejected (HTTP 401) on ${path}. Run \`0 auth login\` to refresh.`, 401, path);
|
|
1171
1162
|
this.name = "CloudUnauthorizedError";
|
|
1172
1163
|
}
|
|
1173
1164
|
};
|
|
1174
1165
|
var CloudForbiddenError = class extends CloudError {
|
|
1175
1166
|
constructor(path) {
|
|
1176
|
-
super(`
|
|
1167
|
+
super(`0-cloud forbidden (HTTP 403) on ${path}. Token lacks scope for this resource.`, 403, path);
|
|
1177
1168
|
this.name = "CloudForbiddenError";
|
|
1178
1169
|
}
|
|
1179
1170
|
};
|
|
1180
1171
|
var CloudNetworkError = class extends CloudError {
|
|
1181
1172
|
constructor(message, path) {
|
|
1182
|
-
super(`
|
|
1173
|
+
super(`0-cloud network error on ${path}: ${message}`, void 0, path);
|
|
1183
1174
|
this.name = "CloudNetworkError";
|
|
1184
1175
|
}
|
|
1185
1176
|
};
|
|
1177
|
+
function isUsageAccount(raw) {
|
|
1178
|
+
if (!raw || typeof raw !== "object")
|
|
1179
|
+
return false;
|
|
1180
|
+
const obj = raw;
|
|
1181
|
+
if (obj.schemaVersion !== "usage-v2")
|
|
1182
|
+
return false;
|
|
1183
|
+
if (!isUtcDate(obj.snapshotAt))
|
|
1184
|
+
return false;
|
|
1185
|
+
if (!obj.scope || typeof obj.scope !== "object")
|
|
1186
|
+
return false;
|
|
1187
|
+
if (typeof obj.scope.orgId !== "string")
|
|
1188
|
+
return false;
|
|
1189
|
+
if (typeof obj.state !== "string")
|
|
1190
|
+
return false;
|
|
1191
|
+
if (!["ready", "disabled", "unavailable", "restricted"].includes(obj.state))
|
|
1192
|
+
return false;
|
|
1193
|
+
if (obj.reason !== null && typeof obj.reason !== "string")
|
|
1194
|
+
return false;
|
|
1195
|
+
const plan = obj.plan;
|
|
1196
|
+
if (!plan || typeof plan !== "object")
|
|
1197
|
+
return false;
|
|
1198
|
+
if (typeof plan.id !== "string" && plan.id !== null)
|
|
1199
|
+
return false;
|
|
1200
|
+
if (plan.id !== null && !["pro", "gold", "enterprise"].includes(plan.id))
|
|
1201
|
+
return false;
|
|
1202
|
+
if (typeof plan.name !== "string" && plan.name !== null)
|
|
1203
|
+
return false;
|
|
1204
|
+
if (!isUsd(plan.monthlyPriceUsd))
|
|
1205
|
+
return false;
|
|
1206
|
+
const included = obj.included;
|
|
1207
|
+
if (!included || typeof included !== "object")
|
|
1208
|
+
return false;
|
|
1209
|
+
if (typeof included.state !== "string")
|
|
1210
|
+
return false;
|
|
1211
|
+
if (!["active", "exhausted", "none", "unavailable"].includes(included.state))
|
|
1212
|
+
return false;
|
|
1213
|
+
if (included.usedPercent !== null && typeof included.usedPercent !== "number")
|
|
1214
|
+
return false;
|
|
1215
|
+
if (included.usedPercent !== null && (!Number.isFinite(included.usedPercent) || included.usedPercent < 0 || included.usedPercent > 100))
|
|
1216
|
+
return false;
|
|
1217
|
+
if (included.resetsAt !== null && !isUtcDate(included.resetsAt))
|
|
1218
|
+
return false;
|
|
1219
|
+
const prepaid = obj.prepaid;
|
|
1220
|
+
if (!prepaid || typeof prepaid !== "object")
|
|
1221
|
+
return false;
|
|
1222
|
+
if (!isUsd(prepaid.balanceUsd))
|
|
1223
|
+
return false;
|
|
1224
|
+
if (typeof prepaid.fallbackEnabled !== "boolean")
|
|
1225
|
+
return false;
|
|
1226
|
+
if (typeof obj.canManageBilling !== "boolean")
|
|
1227
|
+
return false;
|
|
1228
|
+
const admission = obj.admission;
|
|
1229
|
+
if (!admission || typeof admission !== "object")
|
|
1230
|
+
return false;
|
|
1231
|
+
if (typeof admission.eligible !== "boolean")
|
|
1232
|
+
return false;
|
|
1233
|
+
if (admission.reason !== null && typeof admission.reason !== "string")
|
|
1234
|
+
return false;
|
|
1235
|
+
return true;
|
|
1236
|
+
}
|
|
1237
|
+
function isUsd(value) {
|
|
1238
|
+
return value === null || typeof value === "string" && /^\d+(?:\.\d{1,9})?$/.test(value);
|
|
1239
|
+
}
|
|
1240
|
+
function isUtcDate(value) {
|
|
1241
|
+
return typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/.test(value) && Number.isFinite(Date.parse(value));
|
|
1242
|
+
}
|
|
1186
1243
|
function healthPath(host) {
|
|
1187
1244
|
try {
|
|
1188
1245
|
const hostname = new URL(host).hostname.toLowerCase();
|
|
1189
|
-
if (hostname === "cloud.
|
|
1246
|
+
if (hostname === "cloud.0.ai" || hostname === "cloud.0.security") {
|
|
1190
1247
|
return "/api/health";
|
|
1191
1248
|
}
|
|
1192
1249
|
} catch {
|
|
@@ -1219,25 +1276,46 @@ var CloudClient = class {
|
|
|
1219
1276
|
return this.getJson("/api/inference/v1/models");
|
|
1220
1277
|
}
|
|
1221
1278
|
/**
|
|
1222
|
-
* Fetch the organization's
|
|
1223
|
-
*
|
|
1224
|
-
*
|
|
1279
|
+
* Fetch the organization's credit account — usage-v2 shape including
|
|
1280
|
+
* plan, included allowance, prepaid balance, and admission status.
|
|
1281
|
+
*
|
|
1282
|
+
* Returns `null` when the response is a recognised HTTP 200 (customer is
|
|
1283
|
+
* authenticated) but the payload is missing, legacy, or structurally
|
|
1284
|
+
* unrecognised — not an auth failure. HTTP 401/403 still throw the
|
|
1285
|
+
* existing typed errors so the caller can distinguish a credential
|
|
1286
|
+
* problem from unsupported credit data.
|
|
1287
|
+
*
|
|
1288
|
+
* Monetary amounts are decimal strings (no Number coercion); the
|
|
1289
|
+
* caller preserves them for exact display.
|
|
1225
1290
|
*/
|
|
1226
1291
|
async getInferenceAccount() {
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1292
|
+
const raw = await this.getJson("/api/inference/account");
|
|
1293
|
+
if (!isUsageAccount(raw))
|
|
1294
|
+
return null;
|
|
1295
|
+
const { plan, included, prepaid, admission } = raw;
|
|
1296
|
+
return {
|
|
1297
|
+
schemaVersion: raw.schemaVersion,
|
|
1298
|
+
snapshotAt: raw.snapshotAt,
|
|
1299
|
+
scope: { orgId: raw.scope.orgId },
|
|
1300
|
+
state: raw.state,
|
|
1301
|
+
reason: raw.reason,
|
|
1302
|
+
plan: {
|
|
1303
|
+
id: plan.id,
|
|
1304
|
+
name: plan.name,
|
|
1305
|
+
monthlyPriceUsd: plan.monthlyPriceUsd
|
|
1306
|
+
},
|
|
1307
|
+
included: {
|
|
1308
|
+
state: included.state,
|
|
1309
|
+
usedPercent: included.usedPercent,
|
|
1310
|
+
resetsAt: included.resetsAt
|
|
1311
|
+
},
|
|
1312
|
+
prepaid: {
|
|
1313
|
+
balanceUsd: prepaid.balanceUsd,
|
|
1314
|
+
fallbackEnabled: prepaid.fallbackEnabled
|
|
1315
|
+
},
|
|
1316
|
+
canManageBilling: raw.canManageBilling,
|
|
1317
|
+
admission: { eligible: admission.eligible, reason: admission.reason }
|
|
1318
|
+
};
|
|
1241
1319
|
}
|
|
1242
1320
|
/**
|
|
1243
1321
|
* Fetch request-level usage metadata for the operator's hosted
|
|
@@ -1247,9 +1325,87 @@ var CloudClient = class {
|
|
|
1247
1325
|
async getInferenceUsage() {
|
|
1248
1326
|
return this.getJson("/api/inference/usage");
|
|
1249
1327
|
}
|
|
1328
|
+
// ── Audit-skills helpers (#audit-skills) ──
|
|
1329
|
+
/** List all audit skills for the authenticated organization. */
|
|
1330
|
+
async listAuditSkills() {
|
|
1331
|
+
return this.getJson("/api/audit-skills");
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Get a single audit skill with its revision history and project
|
|
1335
|
+
* assignments.
|
|
1336
|
+
*/
|
|
1337
|
+
async getAuditSkill(id) {
|
|
1338
|
+
return this.getJson(`/api/audit-skills/${encodeURIComponent(id)}`);
|
|
1339
|
+
}
|
|
1340
|
+
/** Create a new markdown-based audit skill. */
|
|
1341
|
+
async createAuditSkill(input) {
|
|
1342
|
+
return this.postJson("/api/audit-skills", input);
|
|
1343
|
+
}
|
|
1344
|
+
/** Import an audit skill from a GitHub repository. */
|
|
1345
|
+
async importAuditSkillFromGithub(input) {
|
|
1346
|
+
return this.postJson("/api/audit-skills/import", input);
|
|
1347
|
+
}
|
|
1348
|
+
/** Create a new revision of an audit skill (CAS — 409 on stale expectedRevision). */
|
|
1349
|
+
async createAuditSkillRevision(id, input) {
|
|
1350
|
+
return this.postJson(`/api/audit-skills/${encodeURIComponent(id)}/revisions`, input);
|
|
1351
|
+
}
|
|
1352
|
+
/** Re-fetch the skill from its original GitHub source. */
|
|
1353
|
+
async syncAuditSkill(id, expectedRevision) {
|
|
1354
|
+
return this.postJson(`/api/audit-skills/${encodeURIComponent(id)}/sync`, { expectedRevision });
|
|
1355
|
+
}
|
|
1356
|
+
/** Pin an audit skill revision to a project for future scans. */
|
|
1357
|
+
async assignAuditSkill(id, projectId, revisionId) {
|
|
1358
|
+
return this.postJson(`/api/audit-skills/${encodeURIComponent(id)}/projects/${encodeURIComponent(projectId)}`, { revisionId });
|
|
1359
|
+
}
|
|
1360
|
+
/** Unpin an audit skill from a project (future scans no longer use it). */
|
|
1361
|
+
async unassignAuditSkill(id, projectId) {
|
|
1362
|
+
return this.deleteJson(`/api/audit-skills/${encodeURIComponent(id)}/projects/${encodeURIComponent(projectId)}`);
|
|
1363
|
+
}
|
|
1364
|
+
/** Archive an audit skill (disables future bindings, preserves history). */
|
|
1365
|
+
async archiveAuditSkill(id) {
|
|
1366
|
+
await this.deleteJson(`/api/audit-skills/${encodeURIComponent(id)}`);
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* List audit skills available to a specific project, along with current
|
|
1370
|
+
* assignments for that project. The project must belong to the caller's org.
|
|
1371
|
+
*/
|
|
1372
|
+
async listAuditSkillsByProject(projectId) {
|
|
1373
|
+
return this.getJson(`/api/audit-skills/by-project/${encodeURIComponent(projectId)}`);
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Generic JSON DELETE helper with the same error mapping as getJson/postJson.
|
|
1377
|
+
* Used by `0 service disconnect` to remove scan schedules.
|
|
1378
|
+
*/
|
|
1379
|
+
async deleteJson(path) {
|
|
1380
|
+
const url = `${this.host}${path}`;
|
|
1381
|
+
let res;
|
|
1382
|
+
try {
|
|
1383
|
+
res = await this.fetchImpl(url, {
|
|
1384
|
+
method: "DELETE",
|
|
1385
|
+
headers: { ...this.headers(), "Content-Type": "application/json" }
|
|
1386
|
+
});
|
|
1387
|
+
} catch (err) {
|
|
1388
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1389
|
+
throw new CloudNetworkError(this.scrub(msg), path);
|
|
1390
|
+
}
|
|
1391
|
+
if (!res.ok) {
|
|
1392
|
+
let code;
|
|
1393
|
+
try {
|
|
1394
|
+
const parsed = await res.json();
|
|
1395
|
+
const raw = typeof parsed?.error === "object" ? parsed.error?.code : void 0;
|
|
1396
|
+
if (typeof raw === "string" && raw.length > 0)
|
|
1397
|
+
code = raw;
|
|
1398
|
+
} catch {
|
|
1399
|
+
}
|
|
1400
|
+
this.throwForStatus(res.status, path, code);
|
|
1401
|
+
}
|
|
1402
|
+
if (res.status === 204)
|
|
1403
|
+
return void 0;
|
|
1404
|
+
return await res.json();
|
|
1405
|
+
}
|
|
1250
1406
|
/**
|
|
1251
1407
|
* Generic JSON POST helper with the same error mapping as getJson.
|
|
1252
|
-
* Used by `
|
|
1408
|
+
* Used by `0 connect` to enqueue scans and schedules.
|
|
1253
1409
|
*/
|
|
1254
1410
|
async postJson(path, body) {
|
|
1255
1411
|
const url = `${this.host}${path}`;
|
|
@@ -1313,7 +1469,7 @@ var CloudClient = class {
|
|
|
1313
1469
|
throw new CloudUnauthorizedError(path);
|
|
1314
1470
|
if (status === 403)
|
|
1315
1471
|
throw new CloudForbiddenError(path);
|
|
1316
|
-
throw new CloudError(`
|
|
1472
|
+
throw new CloudError(`0-cloud request failed (HTTP ${status}${code ? ` ${code}` : ""}) on ${path}.`, status, path, code);
|
|
1317
1473
|
}
|
|
1318
1474
|
/**
|
|
1319
1475
|
* Throw a typed error for non-2xx responses. Public so direct callers
|
|
@@ -1326,14 +1482,14 @@ var CloudClient = class {
|
|
|
1326
1482
|
throw new CloudUnauthorizedError(path);
|
|
1327
1483
|
if (res.status === 403)
|
|
1328
1484
|
throw new CloudForbiddenError(path);
|
|
1329
|
-
throw new CloudError(`
|
|
1485
|
+
throw new CloudError(`0-cloud request failed (HTTP ${res.status}) on ${path}.`, res.status, path);
|
|
1330
1486
|
}
|
|
1331
1487
|
// ── internals ──
|
|
1332
1488
|
headers() {
|
|
1333
1489
|
return {
|
|
1334
1490
|
Authorization: `Bearer ${this.token}`,
|
|
1335
1491
|
Accept: "application/json",
|
|
1336
|
-
"User-Agent":
|
|
1492
|
+
"User-Agent": `@0/cli/${VERSION}`
|
|
1337
1493
|
};
|
|
1338
1494
|
}
|
|
1339
1495
|
/**
|
|
@@ -1355,6 +1511,54 @@ import { appendFileSync, existsSync, readFileSync as readFileSync2, renameSync,
|
|
|
1355
1511
|
import { homedir } from "node:os";
|
|
1356
1512
|
import { join as join2 } from "node:path";
|
|
1357
1513
|
|
|
1514
|
+
// packages/core/dist/runtime/hosted-request-queue.js
|
|
1515
|
+
var MAX_IN_FLIGHT = 4;
|
|
1516
|
+
var endpoints = /* @__PURE__ */ new Map();
|
|
1517
|
+
function acquireHostedRequestSlot(endpoint, token, signal) {
|
|
1518
|
+
signal?.throwIfAborted();
|
|
1519
|
+
let accounts = endpoints.get(endpoint);
|
|
1520
|
+
if (!accounts)
|
|
1521
|
+
endpoints.set(endpoint, accounts = /* @__PURE__ */ new Map());
|
|
1522
|
+
let queue = accounts.get(token);
|
|
1523
|
+
if (!queue)
|
|
1524
|
+
accounts.set(token, queue = { active: 0, waiting: [] });
|
|
1525
|
+
const accountQueues = accounts;
|
|
1526
|
+
const current = queue;
|
|
1527
|
+
return new Promise((resolve, reject) => {
|
|
1528
|
+
const abort = () => {
|
|
1529
|
+
const index = current.waiting.indexOf(waiter);
|
|
1530
|
+
if (index !== -1)
|
|
1531
|
+
current.waiting.splice(index, 1);
|
|
1532
|
+
reject(signal.reason);
|
|
1533
|
+
};
|
|
1534
|
+
const waiter = { grant: () => {
|
|
1535
|
+
signal?.removeEventListener("abort", abort);
|
|
1536
|
+
current.active++;
|
|
1537
|
+
let released = false;
|
|
1538
|
+
resolve(() => {
|
|
1539
|
+
if (released)
|
|
1540
|
+
return;
|
|
1541
|
+
released = true;
|
|
1542
|
+
current.active--;
|
|
1543
|
+
const next = current.waiting.shift();
|
|
1544
|
+
if (next)
|
|
1545
|
+
next.grant();
|
|
1546
|
+
else if (current.active === 0) {
|
|
1547
|
+
accountQueues.delete(token);
|
|
1548
|
+
if (accountQueues.size === 0)
|
|
1549
|
+
endpoints.delete(endpoint);
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1552
|
+
} };
|
|
1553
|
+
if (current.active < MAX_IN_FLIGHT)
|
|
1554
|
+
waiter.grant();
|
|
1555
|
+
else {
|
|
1556
|
+
current.waiting.push(waiter);
|
|
1557
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1358
1562
|
// packages/core/dist/runtime/prompt-cache.js
|
|
1359
1563
|
var MAX_CACHE_BREAKPOINTS = 4;
|
|
1360
1564
|
var MESSAGE_CACHE_BREAKPOINTS = MAX_CACHE_BREAKPOINTS - 1;
|
|
@@ -1369,7 +1573,7 @@ function providerSupportsPromptCache(provider) {
|
|
|
1369
1573
|
return readExtraCacheProviders().has(provider);
|
|
1370
1574
|
}
|
|
1371
1575
|
function readExtraCacheProviders() {
|
|
1372
|
-
const raw = process.env["
|
|
1576
|
+
const raw = process.env["ZERO_PROMPT_CACHE_EXTRA_PROVIDERS"];
|
|
1373
1577
|
if (!raw)
|
|
1374
1578
|
return /* @__PURE__ */ new Set();
|
|
1375
1579
|
return new Set(raw.split(",").map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0));
|
|
@@ -1438,7 +1642,7 @@ function isWireBlockArray(blocks) {
|
|
|
1438
1642
|
}
|
|
1439
1643
|
var azureRegionCache = /* @__PURE__ */ new Map();
|
|
1440
1644
|
async function probeAzureRegion(baseUrl, apiKey, fetchImpl = fetch) {
|
|
1441
|
-
const override = process.env["
|
|
1645
|
+
const override = process.env["ZERO_REGION_OVERRIDE"];
|
|
1442
1646
|
if (override && override.trim().length > 0) {
|
|
1443
1647
|
return override.trim();
|
|
1444
1648
|
}
|
|
@@ -1500,7 +1704,7 @@ function prettyRegion(code) {
|
|
|
1500
1704
|
};
|
|
1501
1705
|
return map[code.toLowerCase()] ?? code;
|
|
1502
1706
|
}
|
|
1503
|
-
var PROVIDER_BANNER_KEY = /* @__PURE__ */ Symbol.for("
|
|
1707
|
+
var PROVIDER_BANNER_KEY = /* @__PURE__ */ Symbol.for("0.core.loggedProviderStartup");
|
|
1504
1708
|
var loggedProviderStartup = (() => {
|
|
1505
1709
|
const g = globalThis;
|
|
1506
1710
|
if (!g[PROVIDER_BANNER_KEY])
|
|
@@ -1508,7 +1712,7 @@ var loggedProviderStartup = (() => {
|
|
|
1508
1712
|
return g[PROVIDER_BANNER_KEY];
|
|
1509
1713
|
})();
|
|
1510
1714
|
function appendNativeTrace(record) {
|
|
1511
|
-
const file = process.env["
|
|
1715
|
+
const file = process.env["ZERO_TRACE_NATIVE_RESPONSES"];
|
|
1512
1716
|
if (!file)
|
|
1513
1717
|
return;
|
|
1514
1718
|
try {
|
|
@@ -1518,7 +1722,7 @@ function appendNativeTrace(record) {
|
|
|
1518
1722
|
}
|
|
1519
1723
|
}
|
|
1520
1724
|
function shouldLogProviderStartup() {
|
|
1521
|
-
return process.env["
|
|
1725
|
+
return process.env["ZERO_SUPPRESS_PROVIDER_STARTUP_LOG"] !== "1";
|
|
1522
1726
|
}
|
|
1523
1727
|
function isRetryableHttpStatus(status) {
|
|
1524
1728
|
return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
|
|
@@ -1528,7 +1732,7 @@ var TRANSIENT_STREAM_ERROR_PATTERNS = [
|
|
|
1528
1732
|
"response stream failed"
|
|
1529
1733
|
];
|
|
1530
1734
|
function llmStreamMaxAttempts() {
|
|
1531
|
-
const raw = process.env["
|
|
1735
|
+
const raw = process.env["ZERO_LLM_STREAM_MAX_ATTEMPTS"];
|
|
1532
1736
|
if (raw == null || raw.trim() === "")
|
|
1533
1737
|
return 3;
|
|
1534
1738
|
const n = Number.parseInt(raw, 10);
|
|
@@ -1575,28 +1779,28 @@ function isRetryableTransportCode(code) {
|
|
|
1575
1779
|
].includes(code);
|
|
1576
1780
|
}
|
|
1577
1781
|
function llmMaxRetries() {
|
|
1578
|
-
const raw = process.env["
|
|
1782
|
+
const raw = process.env["ZERO_LLM_MAX_RETRIES"];
|
|
1579
1783
|
if (raw == null || raw.trim() === "")
|
|
1580
1784
|
return 6;
|
|
1581
1785
|
const n = Number.parseInt(raw, 10);
|
|
1582
1786
|
return Number.isFinite(n) && n >= 0 ? n : 6;
|
|
1583
1787
|
}
|
|
1584
1788
|
function llmMaxRetryWaitMs() {
|
|
1585
|
-
const raw = process.env["
|
|
1789
|
+
const raw = process.env["ZERO_LLM_MAX_RETRY_WAIT_MS"];
|
|
1586
1790
|
if (raw == null || raw.trim() === "")
|
|
1587
1791
|
return 6e4;
|
|
1588
1792
|
const n = Number.parseInt(raw, 10);
|
|
1589
1793
|
return Number.isFinite(n) && n > 0 ? n : 6e4;
|
|
1590
1794
|
}
|
|
1591
1795
|
function llm429MaxRetries() {
|
|
1592
|
-
const raw = process.env["
|
|
1796
|
+
const raw = process.env["ZERO_LLM_429_MAX_RETRIES"] ?? process.env["ZERO_LLM_MAX_RETRIES"];
|
|
1593
1797
|
if (raw == null || raw.trim() === "")
|
|
1594
1798
|
return 12;
|
|
1595
1799
|
const n = Number.parseInt(raw, 10);
|
|
1596
1800
|
return Number.isFinite(n) && n >= 0 ? n : 12;
|
|
1597
1801
|
}
|
|
1598
1802
|
function llm429MaxRetryWaitMs() {
|
|
1599
|
-
const raw = process.env["
|
|
1803
|
+
const raw = process.env["ZERO_LLM_429_MAX_RETRY_WAIT_MS"] ?? process.env["ZERO_LLM_MAX_RETRY_WAIT_MS"];
|
|
1600
1804
|
if (raw == null || raw.trim() === "")
|
|
1601
1805
|
return 3e5;
|
|
1602
1806
|
const n = Number.parseInt(raw, 10);
|
|
@@ -1707,14 +1911,14 @@ function parseUsageLimitReached(body) {
|
|
|
1707
1911
|
return details;
|
|
1708
1912
|
}
|
|
1709
1913
|
function llmStreamIdleTimeoutMs() {
|
|
1710
|
-
const raw = process.env["
|
|
1914
|
+
const raw = process.env["ZERO_LLM_STREAM_IDLE_TIMEOUT_MS"];
|
|
1711
1915
|
if (raw == null || raw.trim() === "")
|
|
1712
1916
|
return 12e4;
|
|
1713
1917
|
const n = Number.parseInt(raw, 10);
|
|
1714
1918
|
return Number.isFinite(n) && n > 0 ? n : 12e4;
|
|
1715
1919
|
}
|
|
1716
1920
|
function llmStreamEventIdleTimeoutMs() {
|
|
1717
|
-
const raw = process.env["
|
|
1921
|
+
const raw = process.env["ZERO_LLM_STREAM_EVENT_IDLE_TIMEOUT_MS"];
|
|
1718
1922
|
if (raw == null || raw.trim() === "")
|
|
1719
1923
|
return 24e4;
|
|
1720
1924
|
const n = Number.parseInt(raw, 10);
|
|
@@ -1864,7 +2068,7 @@ var AZURE_FOUNDRY_DEPLOYMENT_IDS = {
|
|
|
1864
2068
|
"gpt-5.6-terra": true
|
|
1865
2069
|
};
|
|
1866
2070
|
function parseLlmFallbackChain(env2 = process.env) {
|
|
1867
|
-
const raw = env2["
|
|
2071
|
+
const raw = env2["ZERO_LLM_FALLBACK"];
|
|
1868
2072
|
if (!raw || raw.trim().length === 0)
|
|
1869
2073
|
return [];
|
|
1870
2074
|
const entries = [];
|
|
@@ -1890,17 +2094,17 @@ function parseLlmFallbackChain(env2 = process.env) {
|
|
|
1890
2094
|
continue;
|
|
1891
2095
|
const colonIdx = trimmed.indexOf(":");
|
|
1892
2096
|
if (colonIdx < 1 || colonIdx === trimmed.length - 1) {
|
|
1893
|
-
diag.warn("fallback_chain_malformed_entry", `
|
|
2097
|
+
diag.warn("fallback_chain_malformed_entry", `ZERO_LLM_FALLBACK: malformed entry "${trimmed}" (expected provider:model)`, { entry: trimmed, expected: "provider:model" });
|
|
1894
2098
|
continue;
|
|
1895
2099
|
}
|
|
1896
2100
|
const provider = trimmed.slice(0, colonIdx);
|
|
1897
2101
|
const model = trimmed.slice(colonIdx + 1).trim();
|
|
1898
2102
|
if (!VALID_PROVIDERS[provider]) {
|
|
1899
|
-
diag.warn("fallback_chain_unknown_provider", `
|
|
2103
|
+
diag.warn("fallback_chain_unknown_provider", `ZERO_LLM_FALLBACK: unknown provider "${provider}" in "${trimmed}"`, { entry: trimmed, provider });
|
|
1900
2104
|
continue;
|
|
1901
2105
|
}
|
|
1902
2106
|
if (!model) {
|
|
1903
|
-
diag.warn("fallback_chain_empty_model", `
|
|
2107
|
+
diag.warn("fallback_chain_empty_model", `ZERO_LLM_FALLBACK: empty model in "${trimmed}"`, { entry: trimmed, provider });
|
|
1904
2108
|
continue;
|
|
1905
2109
|
}
|
|
1906
2110
|
entries.push({ provider, model });
|
|
@@ -1943,7 +2147,7 @@ function resolveFailoverProvider(provider, model, env2 = process.env, apiKey) {
|
|
|
1943
2147
|
return { apiKey: key, baseUrl: env2.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com", wireApi: "chat_completions" };
|
|
1944
2148
|
}
|
|
1945
2149
|
case "chatgpt-codex": {
|
|
1946
|
-
if (!env2["
|
|
2150
|
+
if (!env2["ZERO_CHATGPT_ACCESS_TOKEN"] && !env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"] && !readChatGptCodexAuthFile(env2))
|
|
1947
2151
|
return void 0;
|
|
1948
2152
|
return { apiKey: "", baseUrl: CODEX_API_ENDPOINT, wireApi: "responses" };
|
|
1949
2153
|
}
|
|
@@ -1978,13 +2182,13 @@ function resolveFailoverProvider(provider, model, env2 = process.env, apiKey) {
|
|
|
1978
2182
|
return { apiKey: key, baseUrl: env2.OPENCODE_BASE_URL ?? OPENCODE_DEFAULT_BASE_URL, wireApi: opencodeWireApiForModel(model) };
|
|
1979
2183
|
}
|
|
1980
2184
|
case "copilot": {
|
|
1981
|
-
const key = apiKey ?? env2["
|
|
2185
|
+
const key = apiKey ?? env2["ZERO_COPILOT_GITHUB_TOKEN"];
|
|
1982
2186
|
if (!key)
|
|
1983
2187
|
return void 0;
|
|
1984
2188
|
return { apiKey: key, baseUrl: env2.COPILOT_BASE_URL ?? COPILOT_API_BASE, wireApi: "chat_completions" };
|
|
1985
2189
|
}
|
|
1986
2190
|
case "google": {
|
|
1987
|
-
if (!env2["
|
|
2191
|
+
if (!env2["ZERO_GEMINI_ACCESS_TOKEN"] && !env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"])
|
|
1988
2192
|
return void 0;
|
|
1989
2193
|
return { apiKey: "", baseUrl: CODE_ASSIST_ENDPOINT, wireApi: "google_generate_content" };
|
|
1990
2194
|
}
|
|
@@ -2010,7 +2214,7 @@ function resolveFailoverProvider(provider, model, env2 = process.env, apiKey) {
|
|
|
2010
2214
|
}
|
|
2011
2215
|
var fallbackChainCache;
|
|
2012
2216
|
function getFallbackChain(env2) {
|
|
2013
|
-
const raw = env2["
|
|
2217
|
+
const raw = env2["ZERO_LLM_FALLBACK"];
|
|
2014
2218
|
if (!fallbackChainCache || fallbackChainCache.raw !== raw) {
|
|
2015
2219
|
fallbackChainCache = { raw, entries: parseLlmFallbackChain(env2) };
|
|
2016
2220
|
}
|
|
@@ -2020,7 +2224,7 @@ var ZAI_DEFAULT_BASE_URL = "https://api.z.ai/api/anthropic";
|
|
|
2020
2224
|
var ZAI_DEFAULT_MODEL = "glm-5.3";
|
|
2021
2225
|
var ZAI_DEFAULT_THINKING_BUDGET = 2048;
|
|
2022
2226
|
function zaiThinkingBudget() {
|
|
2023
|
-
const raw = process.env["
|
|
2227
|
+
const raw = process.env["ZERO_ZAI_THINKING_BUDGET"];
|
|
2024
2228
|
if (raw == null || raw.trim().length === 0)
|
|
2025
2229
|
return ZAI_DEFAULT_THINKING_BUDGET;
|
|
2026
2230
|
const n = Number.parseInt(raw, 10);
|
|
@@ -2035,18 +2239,18 @@ var CODEX_OAUTH_ISSUER = "https://auth.openai.com";
|
|
|
2035
2239
|
var CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
2036
2240
|
var CODEX_DEFAULT_MODEL = "gpt-5.5";
|
|
2037
2241
|
var LOOP_SERVER_COMPACTION_TOKENS = 15e4;
|
|
2038
|
-
var PROCESS_SESSION_ID = `
|
|
2242
|
+
var PROCESS_SESSION_ID = `0-${Math.random().toString(36).slice(2, 10)}-${Date.now().toString(36)}`;
|
|
2039
2243
|
var chatGptCodexAuthStates = /* @__PURE__ */ new Map();
|
|
2040
2244
|
function codexAuthStateKey(state) {
|
|
2041
2245
|
return JSON.stringify([state.authFilePath, state.accountId, state.refreshToken || state.accessToken]);
|
|
2042
2246
|
}
|
|
2043
2247
|
function readChatGptCodexEnv(env2 = process.env) {
|
|
2044
|
-
const access = env2["
|
|
2045
|
-
const refresh = env2["
|
|
2248
|
+
const access = env2["ZERO_CHATGPT_ACCESS_TOKEN"];
|
|
2249
|
+
const refresh = env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"];
|
|
2046
2250
|
if ((!access || access.length === 0) && (!refresh || refresh.length === 0)) {
|
|
2047
2251
|
return void 0;
|
|
2048
2252
|
}
|
|
2049
|
-
const accountId = env2["
|
|
2253
|
+
const accountId = env2["ZERO_CHATGPT_ACCOUNT_ID"];
|
|
2050
2254
|
return {
|
|
2051
2255
|
accessToken: access && access.length > 0 ? access : void 0,
|
|
2052
2256
|
refreshToken: refresh && refresh.length > 0 ? refresh : void 0,
|
|
@@ -2054,7 +2258,7 @@ function readChatGptCodexEnv(env2 = process.env) {
|
|
|
2054
2258
|
};
|
|
2055
2259
|
}
|
|
2056
2260
|
function resolveChatGptCodexAuthPath(env2 = process.env) {
|
|
2057
|
-
return env2["
|
|
2261
|
+
return env2["ZERO_CHATGPT_AUTH_FILE"] ?? join2(env2.HOME ?? homedir(), ".codex", "auth.json");
|
|
2058
2262
|
}
|
|
2059
2263
|
function persistChatGptCodexAuthFile(authPath, tokens, usedRefreshToken) {
|
|
2060
2264
|
try {
|
|
@@ -2083,7 +2287,7 @@ function persistChatGptCodexAuthFile(authPath, tokens, usedRefreshToken) {
|
|
|
2083
2287
|
`, { mode: 384 });
|
|
2084
2288
|
renameSync(tmp, authPath);
|
|
2085
2289
|
} catch (err) {
|
|
2086
|
-
process.stderr.write(`[
|
|
2290
|
+
process.stderr.write(`[0] warning: could not persist rotated Codex refresh token to ${authPath}: ${err instanceof Error ? err.message : String(err)}
|
|
2087
2291
|
`);
|
|
2088
2292
|
}
|
|
2089
2293
|
}
|
|
@@ -2119,6 +2323,7 @@ function accessTokenExpiryMs(accessToken) {
|
|
|
2119
2323
|
}
|
|
2120
2324
|
async function refreshChatGptCodexAccessToken(refreshToken) {
|
|
2121
2325
|
const res = await fetch(`${CODEX_OAUTH_ISSUER}/oauth/token`, {
|
|
2326
|
+
signal: AbortSignal.timeout(3e4),
|
|
2122
2327
|
method: "POST",
|
|
2123
2328
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
2124
2329
|
body: new URLSearchParams({
|
|
@@ -2170,12 +2375,15 @@ function extractChatGptAccountId(tokens) {
|
|
|
2170
2375
|
}
|
|
2171
2376
|
return void 0;
|
|
2172
2377
|
}
|
|
2378
|
+
async function getChatGptCodexAccessToken(env2 = process.env) {
|
|
2379
|
+
return refreshChatGptCodexAuthState(resolveChatGptCodexAuthState(env2));
|
|
2380
|
+
}
|
|
2173
2381
|
function resolveChatGptCodexAuthState(env2) {
|
|
2174
2382
|
const fromEnvOnly = readChatGptCodexEnv(env2);
|
|
2175
2383
|
const fromFile = fromEnvOnly ? void 0 : readChatGptCodexAuthFile(env2);
|
|
2176
2384
|
const tokens = fromEnvOnly ?? fromFile;
|
|
2177
2385
|
if (!tokens) {
|
|
2178
|
-
throw new Error("ChatGPT Codex auth: neither
|
|
2386
|
+
throw new Error("ChatGPT Codex auth: neither ZERO_CHATGPT_ACCESS_TOKEN nor ZERO_CHATGPT_OAUTH_REFRESH_TOKEN is set. Run `codex login` and either forward the access token via worker-controller (preferred for multi-sandbox dispatch \u2014 avoids the OAuth refresh-token rotation race) or keep a valid ~/.codex/auth.json on this host.");
|
|
2179
2387
|
}
|
|
2180
2388
|
const identity = {
|
|
2181
2389
|
refreshToken: tokens.refreshToken ?? "",
|
|
@@ -2235,8 +2443,8 @@ function geminiAuthStateKey(refreshToken, accessToken) {
|
|
|
2235
2443
|
return JSON.stringify([refreshToken, refreshToken ? void 0 : accessToken]);
|
|
2236
2444
|
}
|
|
2237
2445
|
function readGeminiCodeAssistEnv(env2 = process.env) {
|
|
2238
|
-
const access = env2["
|
|
2239
|
-
const refresh = env2["
|
|
2446
|
+
const access = env2["ZERO_GEMINI_ACCESS_TOKEN"];
|
|
2447
|
+
const refresh = env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"];
|
|
2240
2448
|
if ((!access || access.length === 0) && (!refresh || refresh.length === 0))
|
|
2241
2449
|
return void 0;
|
|
2242
2450
|
return {
|
|
@@ -2247,7 +2455,7 @@ function readGeminiCodeAssistEnv(env2 = process.env) {
|
|
|
2247
2455
|
function resolveGeminiCodeAssistAuthState(env2) {
|
|
2248
2456
|
const tokens = readGeminiCodeAssistEnv(env2);
|
|
2249
2457
|
if (!tokens) {
|
|
2250
|
-
throw new Error("Google Gemini Code Assist auth: neither
|
|
2458
|
+
throw new Error("Google Gemini Code Assist auth: neither ZERO_GEMINI_ACCESS_TOKEN nor ZERO_GEMINI_OAUTH_REFRESH_TOKEN is set. Sign in with your Google account (0 connect) or forward a fresh access token.");
|
|
2251
2459
|
}
|
|
2252
2460
|
const key = geminiAuthStateKey(tokens.refreshToken ?? "", tokens.accessToken);
|
|
2253
2461
|
const existing = geminiCodeAssistAuthStates.get(key);
|
|
@@ -2360,7 +2568,7 @@ async function resolveGeminiCodeAssistProject(state, env2, sleep = (ms) => new P
|
|
|
2360
2568
|
return state.inflightProjectResolve;
|
|
2361
2569
|
state.inflightProjectResolve = (async () => {
|
|
2362
2570
|
try {
|
|
2363
|
-
const override = firstNonEmptyEnv(env2, "GOOGLE_CLOUD_PROJECT", "
|
|
2571
|
+
const override = firstNonEmptyEnv(env2, "GOOGLE_CLOUD_PROJECT", "ZERO_GEMINI_PROJECT");
|
|
2364
2572
|
const accessToken = await refreshGeminiCodeAssistAuthState(state);
|
|
2365
2573
|
let load;
|
|
2366
2574
|
try {
|
|
@@ -2372,7 +2580,7 @@ async function resolveGeminiCodeAssistProject(state, env2, sleep = (ms) => new P
|
|
|
2372
2580
|
if (err.securityPolicyViolated) {
|
|
2373
2581
|
if (override)
|
|
2374
2582
|
return override;
|
|
2375
|
-
throw new Error("Google Gemini Code Assist: this account is behind a VPC Service Controls perimeter \u2014 set GOOGLE_CLOUD_PROJECT (or
|
|
2583
|
+
throw new Error("Google Gemini Code Assist: this account is behind a VPC Service Controls perimeter \u2014 set GOOGLE_CLOUD_PROJECT (or ZERO_GEMINI_PROJECT).");
|
|
2376
2584
|
}
|
|
2377
2585
|
throw err;
|
|
2378
2586
|
}
|
|
@@ -2484,13 +2692,13 @@ function providerForModel(model, env2) {
|
|
|
2484
2692
|
return env2.OPENCODE_API_KEY ? "opencode" : void 0;
|
|
2485
2693
|
}
|
|
2486
2694
|
if (m.startsWith("copilot/")) {
|
|
2487
|
-
return env2["
|
|
2695
|
+
return env2["ZERO_COPILOT_GITHUB_TOKEN"] ? "copilot" : void 0;
|
|
2488
2696
|
}
|
|
2489
2697
|
if (m.startsWith("gemini") || m.startsWith("google/")) {
|
|
2490
|
-
return env2["
|
|
2698
|
+
return env2["ZERO_GEMINI_ACCESS_TOKEN"] || env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"] ? "google" : void 0;
|
|
2491
2699
|
}
|
|
2492
2700
|
if (/^gpt-|^o[1-4](?:[-_]|$)/.test(m)) {
|
|
2493
|
-
if (env2["
|
|
2701
|
+
if (env2["ZERO_CHATGPT_ACCESS_TOKEN"] || env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"])
|
|
2494
2702
|
return "chatgpt-codex";
|
|
2495
2703
|
if (env2.OPENAI_API_KEY)
|
|
2496
2704
|
return "openai";
|
|
@@ -2526,21 +2734,21 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2526
2734
|
if (configProvider !== void 0 && !Object.hasOwn(DEFAULT_PROVIDER_MODELS, configProvider)) {
|
|
2527
2735
|
throw new Error(`RuntimeConfig.provider is unsupported: ${configProvider}`);
|
|
2528
2736
|
}
|
|
2529
|
-
const selectedProviderRaw = configProvider ?? env2["
|
|
2530
|
-
const forcedProviderRaw = env2["
|
|
2737
|
+
const selectedProviderRaw = configProvider ?? env2["ZERO_SELECTED_PROVIDER"]?.trim();
|
|
2738
|
+
const forcedProviderRaw = env2["ZERO_FORCE_PROVIDER"]?.trim() || void 0;
|
|
2531
2739
|
if (selectedProviderRaw && forcedProviderRaw && selectedProviderRaw !== forcedProviderRaw) {
|
|
2532
|
-
throw new Error(`${configProvider !== void 0 ? "RuntimeConfig.provider" : "
|
|
2740
|
+
throw new Error(`${configProvider !== void 0 ? "RuntimeConfig.provider" : "ZERO_SELECTED_PROVIDER"} conflicts with ZERO_FORCE_PROVIDER`);
|
|
2533
2741
|
}
|
|
2534
|
-
const primaryModel = env2["
|
|
2742
|
+
const primaryModel = env2["ZERO_MODEL"]?.trim();
|
|
2535
2743
|
const selectedProviderApplies = configProvider !== void 0 || !preferredModel || !primaryModel || preferredModel === primaryModel;
|
|
2536
2744
|
const pinnedProviderRaw = forcedProviderRaw ?? (selectedProviderApplies ? selectedProviderRaw : void 0);
|
|
2537
2745
|
if (pinnedProviderRaw) {
|
|
2538
|
-
const source = pinnedProviderRaw === forcedProviderRaw ? "
|
|
2746
|
+
const source = pinnedProviderRaw === forcedProviderRaw ? "ZERO_FORCE_PROVIDER" : configProvider !== void 0 ? "RuntimeConfig.provider" : "ZERO_SELECTED_PROVIDER";
|
|
2539
2747
|
if (!Object.hasOwn(DEFAULT_PROVIDER_MODELS, pinnedProviderRaw)) {
|
|
2540
2748
|
throw new Error(`${source} is unsupported: ${pinnedProviderRaw}`);
|
|
2541
2749
|
}
|
|
2542
2750
|
const provider = pinnedProviderRaw;
|
|
2543
|
-
const model = preferredModel ?? env2["
|
|
2751
|
+
const model = preferredModel ?? env2["ZERO_MODEL"] ?? (configProvider !== void 0 || provider === "hosted" ? DEFAULT_PROVIDER_MODELS[provider] : void 0);
|
|
2544
2752
|
if (model === void 0 || model === "" && provider !== "hosted") {
|
|
2545
2753
|
throw new Error(`${source} requires an explicit model`);
|
|
2546
2754
|
}
|
|
@@ -2650,7 +2858,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2650
2858
|
case "copilot":
|
|
2651
2859
|
return {
|
|
2652
2860
|
provider: "copilot",
|
|
2653
|
-
apiKey: env2["
|
|
2861
|
+
apiKey: env2["ZERO_COPILOT_GITHUB_TOKEN"],
|
|
2654
2862
|
baseUrl: env2.COPILOT_BASE_URL ?? COPILOT_API_BASE,
|
|
2655
2863
|
defaultModel: preferredModel ?? COPILOT_DEFAULT_MODEL,
|
|
2656
2864
|
wireApi: "chat_completions"
|
|
@@ -2668,7 +2876,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2668
2876
|
provider: "chatgpt-codex",
|
|
2669
2877
|
apiKey: "",
|
|
2670
2878
|
baseUrl: CODEX_API_ENDPOINT,
|
|
2671
|
-
defaultModel: env2["
|
|
2879
|
+
defaultModel: env2["ZERO_MODEL"] ?? CODEX_DEFAULT_MODEL,
|
|
2672
2880
|
wireApi: "responses"
|
|
2673
2881
|
};
|
|
2674
2882
|
case "anthropic":
|
|
@@ -2698,8 +2906,23 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2698
2906
|
default:
|
|
2699
2907
|
break;
|
|
2700
2908
|
}
|
|
2701
|
-
|
|
2702
|
-
|
|
2909
|
+
try {
|
|
2910
|
+
const hostedCreds = loadCloudCredentials({
|
|
2911
|
+
env: env2,
|
|
2912
|
+
warn: () => {
|
|
2913
|
+
}
|
|
2914
|
+
});
|
|
2915
|
+
return {
|
|
2916
|
+
provider: "hosted",
|
|
2917
|
+
apiKey: hostedCreds.token,
|
|
2918
|
+
baseUrl: `${hostedCreds.host}/api/inference/v1`,
|
|
2919
|
+
defaultModel: "",
|
|
2920
|
+
wireApi: "chat_completions"
|
|
2921
|
+
};
|
|
2922
|
+
} catch {
|
|
2923
|
+
}
|
|
2924
|
+
const chatGptAccess = env2["ZERO_CHATGPT_ACCESS_TOKEN"];
|
|
2925
|
+
const chatGptRefresh = env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"];
|
|
2703
2926
|
const chatGptAuthFile = !chatGptAccess && !chatGptRefresh ? readChatGptCodexAuthFile(env2) : void 0;
|
|
2704
2927
|
if (chatGptAccess && chatGptAccess.length > 0 || chatGptRefresh && chatGptRefresh.length > 0 || !!chatGptAuthFile) {
|
|
2705
2928
|
return {
|
|
@@ -2712,7 +2935,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2712
2935
|
// baseUrl is informational only — the runtime hardcodes
|
|
2713
2936
|
// CODEX_API_ENDPOINT for this provider.
|
|
2714
2937
|
baseUrl: CODEX_API_ENDPOINT,
|
|
2715
|
-
defaultModel: env2["
|
|
2938
|
+
defaultModel: env2["ZERO_MODEL"] ?? CODEX_DEFAULT_MODEL,
|
|
2716
2939
|
wireApi: "responses"
|
|
2717
2940
|
};
|
|
2718
2941
|
}
|
|
@@ -2808,7 +3031,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2808
3031
|
wireApi: opencodeWireApiForModel(preferredModel)
|
|
2809
3032
|
};
|
|
2810
3033
|
}
|
|
2811
|
-
const copilotToken = env2["
|
|
3034
|
+
const copilotToken = env2["ZERO_COPILOT_GITHUB_TOKEN"];
|
|
2812
3035
|
if (copilotToken) {
|
|
2813
3036
|
return {
|
|
2814
3037
|
provider: "copilot",
|
|
@@ -2818,14 +3041,14 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2818
3041
|
wireApi: "chat_completions"
|
|
2819
3042
|
};
|
|
2820
3043
|
}
|
|
2821
|
-
const geminiAccess = env2["
|
|
2822
|
-
const geminiRefresh = env2["
|
|
3044
|
+
const geminiAccess = env2["ZERO_GEMINI_ACCESS_TOKEN"];
|
|
3045
|
+
const geminiRefresh = env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"];
|
|
2823
3046
|
if (geminiAccess && geminiAccess.length > 0 || geminiRefresh && geminiRefresh.length > 0) {
|
|
2824
3047
|
return {
|
|
2825
3048
|
provider: "google",
|
|
2826
3049
|
apiKey: "",
|
|
2827
3050
|
baseUrl: CODE_ASSIST_ENDPOINT,
|
|
2828
|
-
defaultModel: env2["
|
|
3051
|
+
defaultModel: env2["ZERO_MODEL"] ?? GEMINI_DEFAULT_MODEL,
|
|
2829
3052
|
wireApi: "google_generate_content"
|
|
2830
3053
|
};
|
|
2831
3054
|
}
|
|
@@ -2839,21 +3062,6 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2839
3062
|
wireApi: "chat_completions"
|
|
2840
3063
|
};
|
|
2841
3064
|
}
|
|
2842
|
-
try {
|
|
2843
|
-
const hostedCreds = loadCloudCredentials({
|
|
2844
|
-
env: env2,
|
|
2845
|
-
warn: () => {
|
|
2846
|
-
}
|
|
2847
|
-
});
|
|
2848
|
-
return {
|
|
2849
|
-
provider: "hosted",
|
|
2850
|
-
apiKey: hostedCreds.token,
|
|
2851
|
-
baseUrl: `${hostedCreds.host}/api/inference/v1`,
|
|
2852
|
-
defaultModel: "",
|
|
2853
|
-
wireApi: "chat_completions"
|
|
2854
|
-
};
|
|
2855
|
-
} catch {
|
|
2856
|
-
}
|
|
2857
3065
|
return {
|
|
2858
3066
|
provider: "anthropic",
|
|
2859
3067
|
apiKey: "",
|
|
@@ -2880,7 +3088,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
2880
3088
|
reasoningEffort;
|
|
2881
3089
|
azureConfig;
|
|
2882
3090
|
serverCompactionTokens;
|
|
2883
|
-
/** Ordered fallback chain (
|
|
3091
|
+
/** Ordered fallback chain (ZERO_LLM_FALLBACK). Empty = no failover. */
|
|
2884
3092
|
fallbackChain;
|
|
2885
3093
|
/** Index into fallbackChain — which entry to try next. */
|
|
2886
3094
|
fallbackIndex;
|
|
@@ -2956,7 +3164,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
2956
3164
|
credentials: resolveFailoverProvider(entry.provider, entry.model, this.env)
|
|
2957
3165
|
}));
|
|
2958
3166
|
this.fallbackIndex = 0;
|
|
2959
|
-
const detected = detectProvider(config.apiKey, config.model ?? this.env["
|
|
3167
|
+
const detected = detectProvider(config.apiKey, config.model ?? this.env["ZERO_MODEL"], this.env, config.provider);
|
|
2960
3168
|
this.provider = detected.provider;
|
|
2961
3169
|
this.apiKey = detected.apiKey;
|
|
2962
3170
|
this.baseUrl = detected.baseUrl;
|
|
@@ -2973,9 +3181,9 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
2973
3181
|
this.geminiAuthState = resolveGeminiCodeAssistAuthState(this.env);
|
|
2974
3182
|
}
|
|
2975
3183
|
}
|
|
2976
|
-
this.reasoningEffort = this.env["
|
|
3184
|
+
this.reasoningEffort = this.env["ZERO_REASONING_EFFORT"] ?? detected.reasoningEffort;
|
|
2977
3185
|
this.serverCompactionTokens = config.serverCompactionTokens !== void 0 ? Math.max(1e3, config.serverCompactionTokens) : void 0;
|
|
2978
|
-
const requestedModel = config.model ?? this.env["
|
|
3186
|
+
const requestedModel = config.model ?? this.env["ZERO_MODEL"];
|
|
2979
3187
|
if (requestedModel === "free" && this.provider === "openrouter") {
|
|
2980
3188
|
this.model = FREE_OPENROUTER_MODEL;
|
|
2981
3189
|
} else {
|
|
@@ -2988,11 +3196,50 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
2988
3196
|
this.model = copilotModelId(this.model);
|
|
2989
3197
|
}
|
|
2990
3198
|
this.applyModelWireApi();
|
|
2991
|
-
if (this.apiKey && !this.env["
|
|
3199
|
+
if (this.apiKey && !this.env["ZERO_SKIP_PROVIDER_BANNER"]) {
|
|
2992
3200
|
void logProviderStartup(this.provider, this.providerLabel, this.baseUrl, this.model, this.wireApi, this.apiKey).catch(() => {
|
|
2993
3201
|
});
|
|
2994
3202
|
}
|
|
2995
3203
|
}
|
|
3204
|
+
/** Discover models using this runtime's captured account, including after a separate login changes. */
|
|
3205
|
+
async codexModelCatalog(signal) {
|
|
3206
|
+
const state = this.codexAuthState;
|
|
3207
|
+
if (this.provider !== "chatgpt-codex" || !state)
|
|
3208
|
+
throw new Error("No active Codex subscription");
|
|
3209
|
+
const { loadCodexModelCatalog } = await import("./codex-models-TYYV4JNN.js");
|
|
3210
|
+
return loadCodexModelCatalog({ signal, resolveCredentials: () => refreshChatGptCodexAuthState(state) });
|
|
3211
|
+
}
|
|
3212
|
+
/** Check account admission and resolve the service model without inference.
|
|
3213
|
+
* Each explicit check refreshes admission; failed discovery is never cached.
|
|
3214
|
+
*/
|
|
3215
|
+
async prepare() {
|
|
3216
|
+
while (this.provider === "hosted") {
|
|
3217
|
+
const config = this.config;
|
|
3218
|
+
const client = new CloudClient({
|
|
3219
|
+
host: this.baseUrl.replace(/\/api\/inference\/v1$/, ""),
|
|
3220
|
+
token: this.apiKey
|
|
3221
|
+
});
|
|
3222
|
+
try {
|
|
3223
|
+
const account = await client.getInferenceAccount();
|
|
3224
|
+
if (this.config !== config)
|
|
3225
|
+
continue;
|
|
3226
|
+
if (!account) {
|
|
3227
|
+
throw new CloudError("0cloud account availability could not be read. Check again or review your account in /connect.", void 0, "/api/inference/account", "unsupported_account_data");
|
|
3228
|
+
}
|
|
3229
|
+
if (!account.admission.eligible) {
|
|
3230
|
+
const reason = account.admission.reason ?? account.reason ?? "account_restricted";
|
|
3231
|
+
throw new CloudError(account.state === "unavailable" ? `0cloud account availability could not be checked (${reason}). Check again or review your account in /connect.` : `0cloud account access is restricted (${reason}). Review your account in /connect or contact your organization owner.`, void 0, "/api/inference/account", reason);
|
|
3232
|
+
}
|
|
3233
|
+
await this.ensureHostedModel();
|
|
3234
|
+
if (this.config === config)
|
|
3235
|
+
return;
|
|
3236
|
+
} catch (error) {
|
|
3237
|
+
if (this.config !== config)
|
|
3238
|
+
continue;
|
|
3239
|
+
throw error;
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
2996
3243
|
/**
|
|
2997
3244
|
* Mutate the live selection in place so the NEXT turn (the engine reads
|
|
2998
3245
|
* `config.runtime` per turn) and the NEXT `forkForSubagent` pick up the new
|
|
@@ -3001,11 +3248,12 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3001
3248
|
*/
|
|
3002
3249
|
reconfigure(sel) {
|
|
3003
3250
|
const providerChanged = sel.provider !== void 0 && sel.provider !== this.provider;
|
|
3004
|
-
if (providerChanged) {
|
|
3251
|
+
if (providerChanged || this.provider === "hosted" && (sel.env !== void 0 || sel.provider === "hosted")) {
|
|
3005
3252
|
const merged = {
|
|
3006
3253
|
...this.config,
|
|
3254
|
+
...providerChanged && sel.provider === "hosted" && sel.model === void 0 ? { model: "" } : {},
|
|
3007
3255
|
apiKey: void 0,
|
|
3008
|
-
provider: sel.provider,
|
|
3256
|
+
provider: sel.provider ?? this.provider,
|
|
3009
3257
|
...sel.model !== void 0 ? { model: sel.model } : {},
|
|
3010
3258
|
...sel.agentModels !== void 0 ? { agentModels: sel.agentModels } : {},
|
|
3011
3259
|
...sel.singleModel !== void 0 ? { singleModel: sel.singleModel } : {},
|
|
@@ -3035,6 +3283,8 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3035
3283
|
this.wireApi = openAICompatibleWireApi(this.env, "AZURE_OPENAI_WIRE_API", this.azureConfig.wireApi);
|
|
3036
3284
|
this.applyModelWireApi();
|
|
3037
3285
|
this.reasoningEffort = void 0;
|
|
3286
|
+
this.hostedCatalogPromise = null;
|
|
3287
|
+
this.hostedMaxOutputTokens = void 0;
|
|
3038
3288
|
}
|
|
3039
3289
|
}
|
|
3040
3290
|
}
|
|
@@ -3119,25 +3369,50 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3119
3369
|
}
|
|
3120
3370
|
/** The server catalog is authoritative even when a model was selected explicitly. */
|
|
3121
3371
|
async ensureHostedModel() {
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3372
|
+
while (this.provider === "hosted") {
|
|
3373
|
+
let pending = this.hostedCatalogPromise;
|
|
3374
|
+
if (!pending) {
|
|
3375
|
+
const requestedModel = this.model;
|
|
3126
3376
|
const client = new CloudClient({
|
|
3127
3377
|
host: this.baseUrl.replace(/\/api\/inference\/v1$/, ""),
|
|
3128
3378
|
token: this.apiKey
|
|
3129
3379
|
});
|
|
3130
|
-
const
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3380
|
+
const request = client.getInferenceModels().then((catalog) => {
|
|
3381
|
+
if (this.hostedCatalogPromise !== request)
|
|
3382
|
+
return;
|
|
3383
|
+
const selected = requestedModel ? catalog.data.find((model) => model.id === requestedModel) : catalog.data[0];
|
|
3384
|
+
if (!selected) {
|
|
3385
|
+
throw new Error(requestedModel ? `Hosted model "${requestedModel}" is unavailable. Run \`0 models\` for available models.` : "No hosted models are available. Run `0 models` to check service availability.");
|
|
3386
|
+
}
|
|
3387
|
+
this.model = selected.id;
|
|
3388
|
+
this.wireApi = selected.wire_api;
|
|
3389
|
+
this.hostedMaxOutputTokens = selected.max_output_tokens;
|
|
3390
|
+
});
|
|
3391
|
+
this.hostedCatalogPromise = pending = request;
|
|
3392
|
+
}
|
|
3393
|
+
try {
|
|
3394
|
+
await pending;
|
|
3395
|
+
} catch (error) {
|
|
3396
|
+
if (this.hostedCatalogPromise !== pending)
|
|
3397
|
+
continue;
|
|
3398
|
+
this.hostedCatalogPromise = null;
|
|
3399
|
+
throw error;
|
|
3400
|
+
}
|
|
3401
|
+
if (this.hostedCatalogPromise === pending)
|
|
3402
|
+
return;
|
|
3139
3403
|
}
|
|
3140
|
-
|
|
3404
|
+
}
|
|
3405
|
+
/** All audits and nested workers on this hosted credential share admission. */
|
|
3406
|
+
async acquireHostedSlot(signal) {
|
|
3407
|
+
while (this.provider === "hosted") {
|
|
3408
|
+
const config = this.config;
|
|
3409
|
+
const release = await acquireHostedRequestSlot(this.baseUrl, this.apiKey, signal);
|
|
3410
|
+
if (this.config === config)
|
|
3411
|
+
return release;
|
|
3412
|
+
release();
|
|
3413
|
+
await this.ensureHostedModel();
|
|
3414
|
+
}
|
|
3415
|
+
return void 0;
|
|
3141
3416
|
}
|
|
3142
3417
|
/**
|
|
3143
3418
|
* A hard dollar ceiling needs a provider-enforced bound on the next response.
|
|
@@ -3215,8 +3490,8 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3215
3490
|
if (this.provider === "chatgpt-codex") {
|
|
3216
3491
|
return {
|
|
3217
3492
|
"Content-Type": "application/json",
|
|
3218
|
-
originator: "
|
|
3219
|
-
"User-Agent": `
|
|
3493
|
+
originator: "0",
|
|
3494
|
+
"User-Agent": `0/${VERSION}`
|
|
3220
3495
|
};
|
|
3221
3496
|
}
|
|
3222
3497
|
if (this.isGeminiCodeAssist) {
|
|
@@ -3248,8 +3523,8 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3248
3523
|
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
3249
3524
|
}
|
|
3250
3525
|
if (this.provider === "openrouter") {
|
|
3251
|
-
headers["HTTP-Referer"] = "https://
|
|
3252
|
-
headers["X-Title"] = "
|
|
3526
|
+
headers["HTTP-Referer"] = "https://0.security";
|
|
3527
|
+
headers["X-Title"] = "0 Security Scanner";
|
|
3253
3528
|
}
|
|
3254
3529
|
return headers;
|
|
3255
3530
|
}
|
|
@@ -3273,7 +3548,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3273
3548
|
* happens.
|
|
3274
3549
|
*
|
|
3275
3550
|
* session_id is process-stable (PROCESS_SESSION_ID, randomised
|
|
3276
|
-
* once at module load). A
|
|
3551
|
+
* once at module load). A @0/cli invocation = one scan = one
|
|
3277
3552
|
* session, so the process-lifetime constant is the right
|
|
3278
3553
|
* granularity. If we ever want per-scan ids inside a long-lived
|
|
3279
3554
|
* controller process, add a setter on the runtime; for now this
|
|
@@ -3427,7 +3702,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3427
3702
|
/**
|
|
3428
3703
|
* Per-turn prompt-cache accounting line, so a run can be shown to actually
|
|
3429
3704
|
* be hitting cache rather than assumed to be. Off unless
|
|
3430
|
-
* `
|
|
3705
|
+
* `ZERO_DEBUG_PROMPT_CACHE` is set — this fires once per agent turn, and an
|
|
3431
3706
|
* unconditional line would interleave with the TUI on every scan.
|
|
3432
3707
|
*
|
|
3433
3708
|
* The same numbers reach the cloud without this flag: `cachedInputTokens`
|
|
@@ -3435,7 +3710,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3435
3710
|
* is the durable, queryable proof. This is the local fast path.
|
|
3436
3711
|
*/
|
|
3437
3712
|
logCacheUsage(usage) {
|
|
3438
|
-
if (!usage || !process.env["
|
|
3713
|
+
if (!usage || !process.env["ZERO_DEBUG_PROMPT_CACHE"])
|
|
3439
3714
|
return;
|
|
3440
3715
|
const read = usage.cachedInputTokens ?? 0;
|
|
3441
3716
|
const write = usage.cacheWriteTokens ?? 0;
|
|
@@ -3479,11 +3754,11 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3479
3754
|
case "google":
|
|
3480
3755
|
return "Google Gemini (Code Assist)";
|
|
3481
3756
|
case "hosted":
|
|
3482
|
-
return "
|
|
3757
|
+
return "0.security Cloud";
|
|
3483
3758
|
}
|
|
3484
3759
|
}
|
|
3485
3760
|
noKeyError() {
|
|
3486
|
-
return "No provider credential found. Set one of:\n env
|
|
3761
|
+
return "No provider credential found. Set one of:\n env ZERO_CHATGPT_OAUTH_REFRESH_TOKEN=... 0 <command> (ChatGPT Codex subscription auth)\n export OPENROUTER_API_KEY=sk-or-... (OpenRouter \u2014 many models, one key)\n export DEEPSEEK_API_KEY=... (DeepSeek \u2014 direct Flash 0731 inference)\n export ANTHROPIC_API_KEY=sk-ant-... (Anthropic \u2014 direct Claude access)\n export AZURE_OPENAI_API_KEY=... (Azure OpenAI \u2014 reuse your Codex Azure provider)\n export OPENAI_API_KEY=sk-... (OpenAI \u2014 direct GPT access)\n export Z_AI_API_KEY=... (Z.ai GLM \u2014 flat-rate Coding Plan, Anthropic-compatible)\n export KIMI_API_KEY=... (Moonshot Kimi K3 \u2014 flat-rate coding, Anthropic-compatible)\n export QWEN_API_KEY=... (Alibaba Qwen \u2014 Token Plan sub, OpenAI-compatible)\n export XAI_API_KEY=... (xAI Grok \u2014 OpenAI-compatible)\n export OPENCODE_API_KEY=... (OpenCode Zen \u2014 multi-wire gateway)\n export ZERO_COPILOT_GITHUB_TOKEN=... (GitHub Copilot \u2014 device-code OAuth token)\n Run `0 login` (0 hosted inference)";
|
|
3487
3762
|
}
|
|
3488
3763
|
getConfigurationDiagnostics() {
|
|
3489
3764
|
if (!this.apiKey && this.provider !== "chatgpt-codex") {
|
|
@@ -3503,7 +3778,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3503
3778
|
};
|
|
3504
3779
|
}
|
|
3505
3780
|
const hasConfiguredBaseUrl = !!(this.env.AZURE_OPENAI_BASE_URL || this.env.OPENAI_BASE_URL || this.azureConfig.baseUrl);
|
|
3506
|
-
const hasConfiguredModel = !!(this.config.model || this.env["
|
|
3781
|
+
const hasConfiguredModel = !!(this.config.model || this.env["ZERO_MODEL"] || this.env.AZURE_OPENAI_MODEL || this.azureConfig.model);
|
|
3507
3782
|
const missing = [];
|
|
3508
3783
|
if (!hasConfiguredBaseUrl) {
|
|
3509
3784
|
missing.push("AZURE_OPENAI_BASE_URL (or [model_providers.azure].base_url in ~/.codex/config.toml)");
|
|
@@ -3519,7 +3794,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3519
3794
|
reason: "invalid_config",
|
|
3520
3795
|
fatalError: `Azure OpenAI runtime is selected, but the configuration is incomplete.
|
|
3521
3796
|
Missing: ${missing.join("; ")}
|
|
3522
|
-
|
|
3797
|
+
0 will not guess Azure defaults because that can silently route to the wrong endpoint or deployment.`
|
|
3523
3798
|
};
|
|
3524
3799
|
}
|
|
3525
3800
|
return {
|
|
@@ -3537,15 +3812,15 @@ Missing: ${missing.join("; ")}
|
|
|
3537
3812
|
*
|
|
3538
3813
|
* Two 429 classes are handled differently:
|
|
3539
3814
|
* - per-minute rate limit → retry with the wider 429 budget
|
|
3540
|
-
* (
|
|
3815
|
+
* (ZERO_LLM_429_MAX_RETRIES attempts / ZERO_LLM_429_MAX_RETRY_WAIT_MS
|
|
3541
3816
|
* cumulative, defaults 12 / 5min) since the limiter resets every ~60s;
|
|
3542
3817
|
* `Retry-After` / `retry-after-ms` headers are honored up to a 120s cap.
|
|
3543
3818
|
* - plan-quota exhaustion (`usage_limit_reached`, resets in hours/days) →
|
|
3544
|
-
* skips retries and immediately advances `
|
|
3819
|
+
* skips retries and immediately advances `ZERO_LLM_FALLBACK`; if no
|
|
3545
3820
|
* configured fallback has credentials, it throws QuotaExhaustedError.
|
|
3546
3821
|
*
|
|
3547
3822
|
* Other retryable statuses (transient 5xx) keep the generic budget:
|
|
3548
|
-
*
|
|
3823
|
+
* ZERO_LLM_MAX_RETRIES (attempts) and ZERO_LLM_MAX_RETRY_WAIT_MS
|
|
3549
3824
|
* (cumulative backoff). On exhaustion it returns the last still-failing
|
|
3550
3825
|
* Response with its body intact, so the caller's existing `!res.ok` branch
|
|
3551
3826
|
* surfaces the clear "API error <status>" message — a rate-limit never
|
|
@@ -3556,7 +3831,7 @@ Missing: ${missing.join("; ")}
|
|
|
3556
3831
|
* is fixed across attempts.
|
|
3557
3832
|
*/
|
|
3558
3833
|
/**
|
|
3559
|
-
* Try the next fallback provider in the chain (
|
|
3834
|
+
* Try the next fallback provider in the chain (ZERO_LLM_FALLBACK).
|
|
3560
3835
|
* Updates `this.provider`, `this.model`, `this.apiKey`, `this.baseUrl`,
|
|
3561
3836
|
* `this.wireApi` to match the next valid provider. Returns `true` when a
|
|
3562
3837
|
* valid next provider was found and switched to, `false` when the chain is
|
|
@@ -3568,7 +3843,7 @@ Missing: ${missing.join("; ")}
|
|
|
3568
3843
|
this.fallbackIndex++;
|
|
3569
3844
|
const cfg = entry.credentials;
|
|
3570
3845
|
if (!cfg) {
|
|
3571
|
-
diag.warn("failover_provider_skipped", `
|
|
3846
|
+
diag.warn("failover_provider_skipped", `ZERO_LLM_FALLBACK: skipping ${entry.provider} (auth env missing)`, { provider: entry.provider, model: entry.model, cause: "auth-env-missing" });
|
|
3572
3847
|
continue;
|
|
3573
3848
|
}
|
|
3574
3849
|
this.provider = entry.provider;
|
|
@@ -3614,7 +3889,7 @@ Missing: ${missing.join("; ")}
|
|
|
3614
3889
|
} catch (error) {
|
|
3615
3890
|
abort?.throwIfCancelled();
|
|
3616
3891
|
if (this.provider === "hosted") {
|
|
3617
|
-
throw new Error("
|
|
3892
|
+
throw new Error("0 hosted request outcome is unknown. Automatic replay is disabled; check your inference usage before retrying.", { cause: error });
|
|
3618
3893
|
}
|
|
3619
3894
|
const cause = error instanceof Error ? error.cause : void 0;
|
|
3620
3895
|
const causeCode = cause && typeof cause === "object" && "code" in cause && typeof cause.code === "string" ? cause.code : "unknown";
|
|
@@ -3642,12 +3917,12 @@ Missing: ${missing.join("; ")}
|
|
|
3642
3917
|
if (res.ok || !isRetryableHttpStatus(res.status)) {
|
|
3643
3918
|
return res;
|
|
3644
3919
|
}
|
|
3645
|
-
if (this.provider === "hosted" && res.status === 429 && res.headers.get("x-
|
|
3920
|
+
if (this.provider === "hosted" && res.status === 429 && res.headers.get("x-0-retry-safe") !== "1") {
|
|
3646
3921
|
return res;
|
|
3647
3922
|
}
|
|
3648
3923
|
if (this.provider === "hosted" && res.status >= 500) {
|
|
3649
3924
|
await res.body?.cancel();
|
|
3650
|
-
throw new Error(`
|
|
3925
|
+
throw new Error(`0 hosted request returned HTTP ${res.status}; its outcome may be unknown. Automatic replay is disabled; check your inference usage before retrying.`);
|
|
3651
3926
|
}
|
|
3652
3927
|
abort?.throwIfCancelled();
|
|
3653
3928
|
const is429 = res.status === 429;
|
|
@@ -3751,11 +4026,15 @@ Missing: ${missing.join("; ")}
|
|
|
3751
4026
|
};
|
|
3752
4027
|
}
|
|
3753
4028
|
const systemPrompt = context?.systemPrompt ?? "";
|
|
4029
|
+
let releaseHostedSlot = this.provider === "hosted" ? await this.acquireHostedSlot() : void 0;
|
|
3754
4030
|
const controller = new AbortController();
|
|
3755
4031
|
const timer = setTimeout(() => controller.abort(), this.config.timeout || 12e4);
|
|
3756
4032
|
try {
|
|
3757
4033
|
let res;
|
|
3758
4034
|
do {
|
|
4035
|
+
if (this.provider === "hosted" && !releaseHostedSlot) {
|
|
4036
|
+
releaseHostedSlot = await this.acquireHostedSlot(controller.signal);
|
|
4037
|
+
}
|
|
3759
4038
|
if (this.isOpenAICompat && this.wireApi === "chat_completions") {
|
|
3760
4039
|
const messages = [];
|
|
3761
4040
|
if (systemPrompt) {
|
|
@@ -3883,6 +4162,8 @@ Missing: ${missing.join("; ")}
|
|
|
3883
4162
|
durationMs: Date.now() - start,
|
|
3884
4163
|
error: timedOut ? `${this.providerLabel} API request timed out` : `${this.providerLabel} API error: ${msg}`
|
|
3885
4164
|
};
|
|
4165
|
+
} finally {
|
|
4166
|
+
releaseHostedSlot?.();
|
|
3886
4167
|
}
|
|
3887
4168
|
}
|
|
3888
4169
|
// ── Native Runtime interface (structured messages + tool_use) ──
|
|
@@ -3936,12 +4217,25 @@ Missing: ${missing.join("; ")}
|
|
|
3936
4217
|
}
|
|
3937
4218
|
if (signal?.aborted)
|
|
3938
4219
|
return this.cancelledResult(start);
|
|
4220
|
+
let releaseHostedSlot;
|
|
4221
|
+
if (this.provider === "hosted") {
|
|
4222
|
+
try {
|
|
4223
|
+
releaseHostedSlot = await this.acquireHostedSlot(signal);
|
|
4224
|
+
} catch (error) {
|
|
4225
|
+
if (signal?.aborted)
|
|
4226
|
+
return this.cancelledResult(start);
|
|
4227
|
+
throw error;
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
3939
4230
|
const controller = new AbortController();
|
|
3940
4231
|
const timer = setTimeout(() => controller.abort(), this.config.timeout || 12e4);
|
|
3941
4232
|
const call = composeCallAbort(controller.signal, signal);
|
|
3942
4233
|
try {
|
|
3943
4234
|
let res;
|
|
3944
4235
|
do {
|
|
4236
|
+
if (this.provider === "hosted" && !releaseHostedSlot) {
|
|
4237
|
+
releaseHostedSlot = await this.acquireHostedSlot(call.signal);
|
|
4238
|
+
}
|
|
3945
4239
|
if (this.isOpenAICompat && this.wireApi === "chat_completions") {
|
|
3946
4240
|
const chatMessages = [];
|
|
3947
4241
|
chatMessages.push({ role: "system", content: system });
|
|
@@ -4440,6 +4734,7 @@ Missing: ${missing.join("; ")}
|
|
|
4440
4734
|
error: timedOut ? `${this.providerLabel} API request timed out` : `${this.providerLabel} API error: ${msg}`
|
|
4441
4735
|
};
|
|
4442
4736
|
} finally {
|
|
4737
|
+
releaseHostedSlot?.();
|
|
4443
4738
|
call.dispose();
|
|
4444
4739
|
}
|
|
4445
4740
|
}
|
|
@@ -4507,9 +4802,14 @@ Missing: ${missing.join("; ")}
|
|
|
4507
4802
|
};
|
|
4508
4803
|
const decoder = new TextDecoder();
|
|
4509
4804
|
let buffer = "";
|
|
4805
|
+
let trailingCR = false;
|
|
4806
|
+
let receivedBytes = 0;
|
|
4807
|
+
let malformedEvents = 0;
|
|
4808
|
+
const eventTypes = /* @__PURE__ */ new Set();
|
|
4809
|
+
const identifier = (value) => typeof value === "string" && /^[A-Za-z0-9_.:-]{1,96}$/.test(value) ? value : null;
|
|
4510
4810
|
let completedResponse = null;
|
|
4511
|
-
let
|
|
4512
|
-
let
|
|
4811
|
+
let streamFailure;
|
|
4812
|
+
let responseUsage;
|
|
4513
4813
|
const streamedOutputItems = [];
|
|
4514
4814
|
let thinkingText = "";
|
|
4515
4815
|
let lastThinkingEmit = 0;
|
|
@@ -4532,7 +4832,7 @@ Missing: ${missing.join("; ")}
|
|
|
4532
4832
|
lastThinkingLength = thinkingText.length;
|
|
4533
4833
|
callbacks.onThinking(thinkingText);
|
|
4534
4834
|
};
|
|
4535
|
-
while (true) {
|
|
4835
|
+
responses: while (true) {
|
|
4536
4836
|
let chunk;
|
|
4537
4837
|
try {
|
|
4538
4838
|
chunk = await readBounded();
|
|
@@ -4565,9 +4865,18 @@ Missing: ${missing.join("; ")}
|
|
|
4565
4865
|
throw err;
|
|
4566
4866
|
}
|
|
4567
4867
|
const { done, value } = chunk;
|
|
4568
|
-
if (done)
|
|
4868
|
+
if (done) {
|
|
4869
|
+
buffer += decoder.decode();
|
|
4569
4870
|
break;
|
|
4570
|
-
|
|
4871
|
+
}
|
|
4872
|
+
receivedBytes += value.byteLength;
|
|
4873
|
+
let decoded = decoder.decode(value, { stream: true });
|
|
4874
|
+
if (decoded) {
|
|
4875
|
+
if (trailingCR && decoded.startsWith("\n"))
|
|
4876
|
+
decoded = decoded.slice(1);
|
|
4877
|
+
trailingCR = decoded.endsWith("\r");
|
|
4878
|
+
buffer += decoded.replace(/\r\n?/g, "\n");
|
|
4879
|
+
}
|
|
4571
4880
|
let boundary = buffer.indexOf("\n\n");
|
|
4572
4881
|
while (boundary >= 0) {
|
|
4573
4882
|
const rawChunk = buffer.slice(0, boundary);
|
|
@@ -4580,24 +4889,42 @@ Missing: ${missing.join("; ")}
|
|
|
4580
4889
|
let event;
|
|
4581
4890
|
try {
|
|
4582
4891
|
event = JSON.parse(payload);
|
|
4892
|
+
if (!event || typeof event !== "object" || Array.isArray(event)) {
|
|
4893
|
+
malformedEvents++;
|
|
4894
|
+
continue;
|
|
4895
|
+
}
|
|
4583
4896
|
} catch {
|
|
4897
|
+
malformedEvents++;
|
|
4584
4898
|
continue;
|
|
4585
4899
|
}
|
|
4586
4900
|
const type = String(event.type ?? "");
|
|
4587
|
-
if (
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4901
|
+
if (eventTypes.size < 32)
|
|
4902
|
+
eventTypes.add(identifier(type) ?? "unrecognized");
|
|
4903
|
+
const terminal = type === "response.completed" || this.provider === "openrouter" && type === "response.done";
|
|
4904
|
+
if (terminal || type === "response.failed" || type === "response.incomplete" || type === "error") {
|
|
4905
|
+
const response = event.response && typeof event.response === "object" && !Array.isArray(event.response) ? event.response : void 0;
|
|
4906
|
+
const usage = response?.usage;
|
|
4907
|
+
if (usage && typeof usage.input_tokens === "number" && Number.isFinite(usage.input_tokens) && usage.input_tokens >= 0 && typeof usage.output_tokens === "number" && Number.isFinite(usage.output_tokens) && usage.output_tokens >= 0) {
|
|
4908
|
+
responseUsage = {
|
|
4909
|
+
inputTokens: usage.input_tokens,
|
|
4910
|
+
outputTokens: usage.output_tokens,
|
|
4911
|
+
...readResponsesCachedTokens(usage)
|
|
4595
4912
|
};
|
|
4596
|
-
callbacks?.onUsage?.(
|
|
4913
|
+
callbacks?.onUsage?.(responseUsage);
|
|
4597
4914
|
}
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4915
|
+
if (!terminal || !response || response.error != null || (type === "response.done" || response.status !== void 0) && response.status !== "completed") {
|
|
4916
|
+
const error = response?.error ?? event.error;
|
|
4917
|
+
const incomplete = response?.incomplete_details;
|
|
4918
|
+
streamFailure = {
|
|
4919
|
+
event: type,
|
|
4920
|
+
status: identifier(response?.status),
|
|
4921
|
+
code: identifier(error?.code ?? event.code),
|
|
4922
|
+
errorType: identifier(error?.type),
|
|
4923
|
+
reason: identifier(incomplete?.reason)
|
|
4924
|
+
};
|
|
4925
|
+
break responses;
|
|
4926
|
+
}
|
|
4927
|
+
completedResponse = response;
|
|
4601
4928
|
continue;
|
|
4602
4929
|
}
|
|
4603
4930
|
if (type === "response.output_text.delta" || this.provider === "openrouter" && type === "response.content_part.delta") {
|
|
@@ -4631,33 +4958,33 @@ Missing: ${missing.join("; ")}
|
|
|
4631
4958
|
}
|
|
4632
4959
|
continue;
|
|
4633
4960
|
}
|
|
4634
|
-
if (type === "response.completed" || type === "response.incomplete" || this.provider === "openrouter" && type === "response.done") {
|
|
4635
|
-
const response = event.response;
|
|
4636
|
-
if (response) {
|
|
4637
|
-
if (this.provider === "openrouter" && (openRouterStreamFailed || response.error != null || (type === "response.done" || response.status !== void 0) && response.status !== "completed")) {
|
|
4638
|
-
openRouterStreamFailed = true;
|
|
4639
|
-
continue;
|
|
4640
|
-
}
|
|
4641
|
-
completedResponse = response;
|
|
4642
|
-
const usage2 = response.usage;
|
|
4643
|
-
if (usage2 && this.provider !== "openrouter") {
|
|
4644
|
-
callbacks?.onUsage?.({
|
|
4645
|
-
inputTokens: Number(usage2.input_tokens ?? 0),
|
|
4646
|
-
outputTokens: Number(usage2.output_tokens ?? 0)
|
|
4647
|
-
});
|
|
4648
|
-
}
|
|
4649
|
-
}
|
|
4650
|
-
}
|
|
4651
4961
|
}
|
|
4652
4962
|
}
|
|
4653
4963
|
emitThinking(true);
|
|
4654
|
-
if (!completedResponse ||
|
|
4964
|
+
if (!completedResponse || streamFailure) {
|
|
4965
|
+
if (streamFailure) {
|
|
4966
|
+
void reader.cancel().catch(() => {
|
|
4967
|
+
});
|
|
4968
|
+
}
|
|
4969
|
+
appendNativeTrace({
|
|
4970
|
+
kind: "native-response-stream-error",
|
|
4971
|
+
provider: this.providerLabel,
|
|
4972
|
+
wireApi: this.wireApi,
|
|
4973
|
+
httpStatus: res.status,
|
|
4974
|
+
eventStreamContentType: /^text\/event-stream(?:\s*;|$)/i.test(res.headers.get("content-type") ?? ""),
|
|
4975
|
+
terminalFailure: streamFailure ?? null,
|
|
4976
|
+
eventTypes: [...eventTypes],
|
|
4977
|
+
receivedBytes,
|
|
4978
|
+
malformedEvents,
|
|
4979
|
+
trailingCharacters: buffer.length,
|
|
4980
|
+
usage: responseUsage ?? null
|
|
4981
|
+
});
|
|
4655
4982
|
return {
|
|
4656
4983
|
content: thinkingText ? [{ type: "text", text: thinkingText }] : [{ type: "text", text: "" }],
|
|
4657
4984
|
stopReason: "error",
|
|
4658
4985
|
durationMs: Date.now() - start,
|
|
4659
|
-
...
|
|
4660
|
-
error: `${this.providerLabel} API error: ${
|
|
4986
|
+
...responseUsage ? { usage: responseUsage } : {},
|
|
4987
|
+
error: `${this.providerLabel} API error: ${streamFailure ? `Responses terminal failure ${JSON.stringify(streamFailure)}` : `stream completed without final response (HTTP ${res.status}; events=${[...eventTypes].join(",") || "none"}; malformed=${malformedEvents}; trailing=${buffer.length})`}`
|
|
4661
4988
|
};
|
|
4662
4989
|
}
|
|
4663
4990
|
appendNativeTrace({
|
|
@@ -4710,21 +5037,10 @@ Missing: ${missing.join("; ")}
|
|
|
4710
5037
|
thinkingText = reasoningSummaries.join("\n");
|
|
4711
5038
|
emitThinking(true);
|
|
4712
5039
|
}
|
|
4713
|
-
const usageRecord = completedResponse.usage;
|
|
4714
|
-
const usage = usageRecord ? {
|
|
4715
|
-
inputTokens: Number(usageRecord.input_tokens ?? 0),
|
|
4716
|
-
outputTokens: Number(usageRecord.output_tokens ?? 0),
|
|
4717
|
-
// Responses `input_tokens` already INCLUDES the cached span (unlike
|
|
4718
|
-
// Anthropic, which subtracts it), so no normalisation is needed —
|
|
4719
|
-
// this is purely so cache behaviour becomes observable. Without it
|
|
4720
|
-
// the Codex cache hit rate is unmeasurable: `prompt-cache.ts`
|
|
4721
|
-
// instruments the Anthropic path only.
|
|
4722
|
-
...readResponsesCachedTokens(usageRecord)
|
|
4723
|
-
} : void 0;
|
|
4724
5040
|
return {
|
|
4725
5041
|
content,
|
|
4726
5042
|
stopReason: content.some((item) => item.type === "tool_use") ? "tool_use" : "end_turn",
|
|
4727
|
-
usage,
|
|
5043
|
+
usage: responseUsage,
|
|
4728
5044
|
durationMs: Date.now() - start,
|
|
4729
5045
|
// `outputItems` is the complete, correctly-ordered response array —
|
|
4730
5046
|
// reasoning items with their `encrypted_content` still attached, each
|
|
@@ -4780,5 +5096,6 @@ export {
|
|
|
4780
5096
|
OperatorAbortError,
|
|
4781
5097
|
parseUsageLimitReached,
|
|
4782
5098
|
LOOP_SERVER_COMPACTION_TOKENS,
|
|
5099
|
+
getChatGptCodexAccessToken,
|
|
4783
5100
|
LlmApiRuntime
|
|
4784
5101
|
};
|