@tiangong-ai/cli 0.0.29 → 0.0.31
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/AGENTS.md +1 -1
- package/README.md +44 -13
- package/dist/edge-search.js +57 -16
- package/dist/edge-search.js.map +1 -1
- package/dist/http.js +17 -1
- package/dist/http.js.map +1 -1
- package/dist/research/orchestration.js +9 -1
- package/dist/research/orchestration.js.map +1 -1
- package/dist/research/workspace/broker.js +60 -15
- package/dist/research/workspace/broker.js.map +1 -1
- package/dist/research/workspace/external-skills.d.ts +95 -0
- package/dist/research/workspace/external-skills.js +79 -25
- package/dist/research/workspace/external-skills.js.map +1 -1
- package/dist/research/workspace/preflight.d.ts +9 -0
- package/dist/research/workspace/preflight.js +44 -2
- package/dist/research/workspace/preflight.js.map +1 -1
- package/dist/research/workspace/projects.js +16 -0
- package/dist/research/workspace/projects.js.map +1 -1
- package/dist/research/workspace/setup-catalog.d.ts +20 -2
- package/dist/research/workspace/setup-catalog.js +209 -12
- package/dist/research/workspace/setup-catalog.js.map +1 -1
- package/dist/research/workspace/setup.d.ts +7 -1
- package/dist/research/workspace/setup.js +190 -47
- package/dist/research/workspace/setup.js.map +1 -1
- package/dist/research/workspace/storage.d.ts +1 -0
- package/dist/research/workspace/storage.js +21 -2
- package/dist/research/workspace/storage.js.map +1 -1
- package/dist/research/workspace/types.d.ts +2 -0
- package/package.json +2 -1
|
@@ -8,11 +8,11 @@ import { CliError } from "../../errors.js";
|
|
|
8
8
|
import { loadCapabilityDeclarations } from "./capabilities.js";
|
|
9
9
|
import { inspectResearchContext } from "./context.js";
|
|
10
10
|
import { inspectCapabilityCredentialEnvironment, loadCapabilityCredentialMapForIds, reconcileCapabilityCredentialEnvironment, setCapabilityCredentialValue, } from "./credentials.js";
|
|
11
|
-
import { configureExternalSkillProfile,
|
|
11
|
+
import { configureExternalSkillProfile, configureTiangongDatabaseCapability, doctorExternalCapabilities, EXTERNAL_SKILL_CONTEXT_PROFILE, EXTERNAL_SKILL_MEDIA_PROFILE, EXTERNAL_SKILL_PROFILE, reconcileSetupManagedCapabilities, } from "./external-skills.js";
|
|
12
12
|
import { appendJournalEvent } from "./journal.js";
|
|
13
13
|
import { configuredResearchSecrets, isSensitiveEnvironmentName, sanitizeResearchRecord, sanitizeResearchText, } from "./sanitization.js";
|
|
14
|
-
import { inspectResearchSetupCatalog, RESEARCH_SETUP_CREDENTIALS, RESEARCH_SETUP_INSTALLER, RESEARCH_SETUP_SETTINGS, RESEARCH_SETUP_SKILLS, resolveSetupSkills, setupSkill, setupSource, setupTargetRoot, } from "./setup-catalog.js";
|
|
15
|
-
import { acquireFileLock, canonicalJson, ensureDirectory, fileSize, hashRegularTree, isObject, pathExists, readJsonFile, sha256File, sha256Text, workspacePaths, writeJsonAtomic, writeTextAtomic, } from "./storage.js";
|
|
14
|
+
import { inspectResearchSetupCatalog, RESEARCH_SETUP_CREDENTIALS, RESEARCH_SETUP_INSTALLER, RESEARCH_SETUP_SETTINGS, RESEARCH_SETUP_SKILLS, resolveSetupSkills, setupSkill, setupSource, setupTargetRoot, verifyResearchSetupRuntimeContract, } from "./setup-catalog.js";
|
|
15
|
+
import { acquireFileLock, canonicalJson, ensureDirectory, fileSize, hashRegularTree, isObject, pathExists, REGULAR_TREE_HASH_ALGORITHM, readJsonFile, sha256File, sha256Text, workspacePaths, writeJsonAtomic, writeTextAtomic, } from "./storage.js";
|
|
16
16
|
import { packageVersion } from "./constants.js";
|
|
17
17
|
import { doctorResearchWorkspace, initializeResearchWorkspace, loadWorkspaceConfig, } from "./workspace.js";
|
|
18
18
|
const BRAVE_PROFILE_SKILLS = {
|
|
@@ -969,22 +969,24 @@ export async function checkResearchSetupUpdates(workspace, environment = process
|
|
|
969
969
|
});
|
|
970
970
|
}
|
|
971
971
|
}
|
|
972
|
+
const cliVersionDrift = plan.cli.version === packageVersion()
|
|
973
|
+
? null
|
|
974
|
+
: { planned: plan.cli.version, active: packageVersion() };
|
|
975
|
+
const updateAvailable = drift.length > 0 || cliVersionDrift !== null;
|
|
972
976
|
return {
|
|
973
977
|
schemaVersion: 1,
|
|
974
978
|
workspace: root,
|
|
975
979
|
checkedAt: new Date().toISOString(),
|
|
976
|
-
updateAvailable
|
|
977
|
-
cliVersionDrift
|
|
978
|
-
? null
|
|
979
|
-
: { planned: plan.cli.version, active: packageVersion() },
|
|
980
|
+
updateAvailable,
|
|
981
|
+
cliVersionDrift,
|
|
980
982
|
drift,
|
|
981
983
|
currentInstaller: RESEARCH_SETUP_INSTALLER,
|
|
982
984
|
catalog,
|
|
983
985
|
policy: {
|
|
984
986
|
automaticUpdate: false,
|
|
985
987
|
floatingUpdate: false,
|
|
986
|
-
minimumAction:
|
|
987
|
-
? "Use the
|
|
988
|
+
minimumAction: updateAvailable
|
|
989
|
+
? "Use the active exact CLI release to create, review, and apply a replacement immutable plan; upgrade never runs a floating CLI or Skills update."
|
|
988
990
|
: "No catalog migration is required. Installed tree drift is reported separately by setup status/doctor.",
|
|
989
991
|
},
|
|
990
992
|
};
|
|
@@ -1612,9 +1614,12 @@ async function ensureSetupSourceCheckout(plan, sourceId, runner, environment) {
|
|
|
1612
1614
|
throw planCatalogDrift(`missing source ${sourceId}`);
|
|
1613
1615
|
const checkout = join(workspacePaths(plan.workspace.path).setupSources, `${source.id}-${source.immutableRef.slice(0, 12)}`);
|
|
1614
1616
|
await assertNoSymlinkedExistingPath(dirname(checkout), plan.workspace.path);
|
|
1617
|
+
let createdCheckout = false;
|
|
1615
1618
|
if (!(await pathExists(checkout))) {
|
|
1616
1619
|
await ensureDirectory(workspacePaths(plan.workspace.path).setupSources);
|
|
1617
1620
|
await runChecked(runner, "git", ["init", "--quiet", checkout], plan.workspace.path, environment, "source-checkout");
|
|
1621
|
+
createdCheckout = true;
|
|
1622
|
+
await configureDeterministicSourceCheckout(checkout, runner, plan.workspace.path, environment);
|
|
1618
1623
|
await runChecked(runner, "git", ["-C", checkout, "remote", "add", "origin", source.locator], plan.workspace.path, environment, "source-checkout");
|
|
1619
1624
|
}
|
|
1620
1625
|
const info = await lstat(checkout);
|
|
@@ -1677,6 +1682,9 @@ async function ensureSetupSourceCheckout(plan, sourceId, runner, environment) {
|
|
|
1677
1682
|
// A process may have been interrupted after git init/remote-add. Resume only
|
|
1678
1683
|
// that exact incomplete checkout; never rewrite a checkout with a valid,
|
|
1679
1684
|
// different HEAD.
|
|
1685
|
+
if (!createdCheckout) {
|
|
1686
|
+
await configureDeterministicSourceCheckout(checkout, runner, plan.workspace.path, environment);
|
|
1687
|
+
}
|
|
1680
1688
|
await runChecked(runner, "git", ["-C", checkout, "fetch", "--depth", "1", "origin", source.immutableRef], plan.workspace.path, environment, "source-checkout", 180_000);
|
|
1681
1689
|
await runChecked(runner, "git", ["-C", checkout, "checkout", "--quiet", "--detach", "FETCH_HEAD"], plan.workspace.path, environment, "source-checkout");
|
|
1682
1690
|
head = await runChecked(runner, "git", ["-C", checkout, "rev-parse", "HEAD"], plan.workspace.path, environment, "source-verification");
|
|
@@ -1701,14 +1709,30 @@ async function ensureSetupSourceCheckout(plan, sourceId, runner, environment) {
|
|
|
1701
1709
|
code: "RESEARCH_SETUP_SOURCE_HASH_MISMATCH",
|
|
1702
1710
|
step: "source-verification",
|
|
1703
1711
|
reason: `Pinned source bytes failed the reviewed tree hash for ${skill.id}.`,
|
|
1704
|
-
minimumAction: "
|
|
1712
|
+
minimumAction: "Regenerate the reviewed setup plan with the active CLI/catalog. If that exact plan still fails, inspect only its CLI-owned source cache; never bypass verification.",
|
|
1705
1713
|
retryCommand: `tiangong-ai research setup update --check --workspace ${plan.workspace.path} --json`,
|
|
1706
1714
|
exitCode: 3,
|
|
1715
|
+
diagnostics: {
|
|
1716
|
+
skillId: skill.id,
|
|
1717
|
+
sourceId: source.id,
|
|
1718
|
+
hashAlgorithm: REGULAR_TREE_HASH_ALGORITHM,
|
|
1719
|
+
expectedTreeSha256: skill.expectedTreeSha256,
|
|
1720
|
+
observedTreeSha256,
|
|
1721
|
+
},
|
|
1707
1722
|
});
|
|
1708
1723
|
}
|
|
1724
|
+
await verifyResearchSetupRuntimeContract(sourcePath, setupSkill(skill.id));
|
|
1709
1725
|
}
|
|
1710
1726
|
return checkout;
|
|
1711
1727
|
}
|
|
1728
|
+
async function configureDeterministicSourceCheckout(checkout, runner, cwd, environment) {
|
|
1729
|
+
for (const [key, value] of [
|
|
1730
|
+
["core.autocrlf", "false"],
|
|
1731
|
+
["core.eol", "lf"],
|
|
1732
|
+
]) {
|
|
1733
|
+
await runChecked(runner, "git", ["-C", checkout, "config", "--local", key, value], cwd, environment, "source-checkout");
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1712
1736
|
async function installSetupSkills(input) {
|
|
1713
1737
|
if (!input.skills.length)
|
|
1714
1738
|
return;
|
|
@@ -1738,8 +1762,30 @@ async function installSetupSkills(input) {
|
|
|
1738
1762
|
}
|
|
1739
1763
|
async function configureSelectedCapabilities(plan, _environment) {
|
|
1740
1764
|
const hasBraveProfile = plan.selection.evidenceProfile !== "none";
|
|
1741
|
-
const
|
|
1742
|
-
|
|
1765
|
+
const tiangongDatabases = [
|
|
1766
|
+
{
|
|
1767
|
+
kind: "sci",
|
|
1768
|
+
skillId: "tiangong.kb-sci-search",
|
|
1769
|
+
settingPrefix: "tiangong.sci",
|
|
1770
|
+
catalogId: "first-party.tiangong.kb-sci-search",
|
|
1771
|
+
capabilityId: "database.tiangong.sci-search",
|
|
1772
|
+
},
|
|
1773
|
+
{
|
|
1774
|
+
kind: "report",
|
|
1775
|
+
skillId: "tiangong.kb-report-search",
|
|
1776
|
+
settingPrefix: "tiangong.report",
|
|
1777
|
+
catalogId: "first-party.tiangong.kb-report-search",
|
|
1778
|
+
capabilityId: "database.tiangong.report-search",
|
|
1779
|
+
},
|
|
1780
|
+
{
|
|
1781
|
+
kind: "patent",
|
|
1782
|
+
skillId: "tiangong.kb-patent-search",
|
|
1783
|
+
settingPrefix: "tiangong.patent",
|
|
1784
|
+
catalogId: "first-party.tiangong.kb-patent-search",
|
|
1785
|
+
capabilityId: "database.tiangong.patent-search",
|
|
1786
|
+
},
|
|
1787
|
+
].filter((entry) => plan.selection.skillIds.includes(entry.skillId));
|
|
1788
|
+
const codexRoot = hasBraveProfile || tiangongDatabases.length ? plannedTargetRoot(plan, "codex") : null;
|
|
1743
1789
|
if (plan.selection.evidenceProfile !== "none") {
|
|
1744
1790
|
await configureExternalSkillProfile({
|
|
1745
1791
|
workspace: plan.workspace.path,
|
|
@@ -1747,10 +1793,11 @@ async function configureSelectedCapabilities(plan, _environment) {
|
|
|
1747
1793
|
skillRoot: codexRoot,
|
|
1748
1794
|
});
|
|
1749
1795
|
}
|
|
1750
|
-
|
|
1751
|
-
const skill = setupSkill(
|
|
1796
|
+
for (const database of tiangongDatabases) {
|
|
1797
|
+
const skill = setupSkill(database.skillId);
|
|
1752
1798
|
const source = setupSource(skill.sourceId);
|
|
1753
|
-
await
|
|
1799
|
+
await configureTiangongDatabaseCapability({
|
|
1800
|
+
kind: database.kind,
|
|
1754
1801
|
workspace: plan.workspace.path,
|
|
1755
1802
|
skillPath: join(codexRoot, skill.skillName),
|
|
1756
1803
|
source: {
|
|
@@ -1759,19 +1806,19 @@ async function configureSelectedCapabilities(plan, _environment) {
|
|
|
1759
1806
|
immutableRef: source.immutableRef,
|
|
1760
1807
|
expectedTreeSha256: skill.expectedTreeSha256,
|
|
1761
1808
|
license: "MIT",
|
|
1762
|
-
catalogId:
|
|
1809
|
+
catalogId: database.catalogId,
|
|
1763
1810
|
},
|
|
1764
|
-
endpoint: plan.settings[
|
|
1765
|
-
...(plan.settings[
|
|
1811
|
+
endpoint: plan.settings[`${database.settingPrefix}.endpoint`],
|
|
1812
|
+
...(plan.settings[`${database.settingPrefix}.region`] === undefined
|
|
1766
1813
|
? {}
|
|
1767
|
-
: { region: plan.settings[
|
|
1814
|
+
: { region: plan.settings[`${database.settingPrefix}.region`] }),
|
|
1768
1815
|
});
|
|
1769
1816
|
}
|
|
1770
1817
|
await reconcileSetupManagedCapabilities({
|
|
1771
1818
|
workspace: plan.workspace.path,
|
|
1772
1819
|
selectedCapabilityIds: [
|
|
1773
1820
|
...BRAVE_PROFILE_SKILLS[plan.selection.evidenceProfile].map(setupManagedCapabilityId),
|
|
1774
|
-
...(
|
|
1821
|
+
...tiangongDatabases.map((database) => database.capabilityId),
|
|
1775
1822
|
],
|
|
1776
1823
|
});
|
|
1777
1824
|
}
|
|
@@ -1866,6 +1913,14 @@ async function assertRequiredCredentialPreflight(plan, environment) {
|
|
|
1866
1913
|
.join(", ")}), then retry this exact step.`,
|
|
1867
1914
|
retryCommand: `tiangong-ai research setup retry --step credential-preflight --workspace ${plan.workspace.path} --json`,
|
|
1868
1915
|
exitCode: 3,
|
|
1916
|
+
diagnostics: {
|
|
1917
|
+
executionMode: "setup-preflight",
|
|
1918
|
+
credentialScope: [
|
|
1919
|
+
...new Set(failures.map((failure) => definitions.find((definition) => definition.id === failure.id).storage)),
|
|
1920
|
+
].join("+"),
|
|
1921
|
+
networkAttempted: false,
|
|
1922
|
+
missingCredentialIds: failures.map((failure) => failure.id),
|
|
1923
|
+
},
|
|
1869
1924
|
});
|
|
1870
1925
|
}
|
|
1871
1926
|
}
|
|
@@ -2864,7 +2919,7 @@ function commandFailure(step, command, result, root, environment) {
|
|
|
2864
2919
|
exitCode: 3,
|
|
2865
2920
|
});
|
|
2866
2921
|
}
|
|
2867
|
-
async function setupDoctorCheck(checks, id, category, callback) {
|
|
2922
|
+
async function setupDoctorCheck(checks, id, category, callback, failureMinimumAction = `Resolve ${id}, then rerun research setup doctor.`) {
|
|
2868
2923
|
try {
|
|
2869
2924
|
checks.push({
|
|
2870
2925
|
id,
|
|
@@ -2880,7 +2935,7 @@ async function setupDoctorCheck(checks, id, category, callback) {
|
|
|
2880
2935
|
category,
|
|
2881
2936
|
status: "fail",
|
|
2882
2937
|
detail: sanitizeResearchText(error instanceof Error ? error.message : String(error)),
|
|
2883
|
-
minimumAction:
|
|
2938
|
+
minimumAction: failureMinimumAction,
|
|
2884
2939
|
});
|
|
2885
2940
|
}
|
|
2886
2941
|
}
|
|
@@ -2934,35 +2989,12 @@ async function appendDependencyChecks(checks, selected, runner, root, environmen
|
|
|
2934
2989
|
return `${dependency.requirement} is active.`;
|
|
2935
2990
|
}
|
|
2936
2991
|
throw new Error(`No automatic dependency check is declared for ${dependency.id}. ${dependency.minimumAction}`);
|
|
2937
|
-
});
|
|
2992
|
+
}, dependency.minimumAction);
|
|
2938
2993
|
}
|
|
2939
2994
|
}
|
|
2940
2995
|
async function appendCompanionLiveChecks(checks, input) {
|
|
2941
|
-
void input.sleeper;
|
|
2942
2996
|
if (input.selected.some((skill) => skill.id === "tiangong.academic-paper-download")) {
|
|
2943
|
-
await
|
|
2944
|
-
const apiKey = input.adapterCredentials.get("semantic-scholar.api-key");
|
|
2945
|
-
const headers = new Headers({ Accept: "application/json" });
|
|
2946
|
-
if (apiKey)
|
|
2947
|
-
headers.set("x-api-key", apiKey);
|
|
2948
|
-
const response = await input.fetcher("https://api.semanticscholar.org/graph/v1/paper/search?query=reproducible%20research&limit=1&fields=paperId", {
|
|
2949
|
-
method: "GET",
|
|
2950
|
-
headers,
|
|
2951
|
-
redirect: "manual",
|
|
2952
|
-
signal: AbortSignal.timeout(30_000),
|
|
2953
|
-
});
|
|
2954
|
-
if (response.status >= 300 && response.status < 400) {
|
|
2955
|
-
throw new Error("Semantic Scholar returned a redirect; credential-bearing redirects are not followed.");
|
|
2956
|
-
}
|
|
2957
|
-
if (!response.ok) {
|
|
2958
|
-
const detail = await boundedResponseText(response, 2_000, apiKey ? [apiKey] : []);
|
|
2959
|
-
throw new Error(`Semantic Scholar live check returned HTTP ${response.status}${detail ? `: ${detail}` : ""}.`);
|
|
2960
|
-
}
|
|
2961
|
-
await response.body?.cancel().catch(() => undefined);
|
|
2962
|
-
return apiKey
|
|
2963
|
-
? "Semantic Scholar accepted the configured optional API key."
|
|
2964
|
-
: "Semantic Scholar public API is reachable without an optional API key.";
|
|
2965
|
-
});
|
|
2997
|
+
await appendSemanticScholarLiveCheck(checks, input);
|
|
2966
2998
|
}
|
|
2967
2999
|
if (input.selected.some((skill) => skill.id === "tiangong.document-granular-decompose")) {
|
|
2968
3000
|
if (!input.allowSyntheticUnstructureUpload) {
|
|
@@ -3008,6 +3040,116 @@ async function appendCompanionLiveChecks(checks, input) {
|
|
|
3008
3040
|
}
|
|
3009
3041
|
}
|
|
3010
3042
|
}
|
|
3043
|
+
async function appendSemanticScholarLiveCheck(checks, input) {
|
|
3044
|
+
const apiKey = input.adapterCredentials.get("semantic-scholar.api-key");
|
|
3045
|
+
const headers = new Headers({ Accept: "application/json" });
|
|
3046
|
+
if (apiKey)
|
|
3047
|
+
headers.set("x-api-key", apiKey);
|
|
3048
|
+
const url = "https://api.semanticscholar.org/graph/v1/paper/search?query=reproducible%20research&limit=1&fields=paperId";
|
|
3049
|
+
try {
|
|
3050
|
+
let response;
|
|
3051
|
+
let retryAfterSeconds = null;
|
|
3052
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
3053
|
+
response = await input.fetcher(url, {
|
|
3054
|
+
method: "GET",
|
|
3055
|
+
headers,
|
|
3056
|
+
redirect: "manual",
|
|
3057
|
+
signal: AbortSignal.timeout(30_000),
|
|
3058
|
+
});
|
|
3059
|
+
if (response.status !== 429 || attempt === 1)
|
|
3060
|
+
break;
|
|
3061
|
+
retryAfterSeconds = setupRetryAfterSeconds(response.headers.get("retry-after"));
|
|
3062
|
+
await response.body?.cancel().catch(() => undefined);
|
|
3063
|
+
await input.sleeper(Math.min(5, retryAfterSeconds ?? 1) * 1_000);
|
|
3064
|
+
}
|
|
3065
|
+
retryAfterSeconds ??= setupRetryAfterSeconds(response.headers.get("retry-after"));
|
|
3066
|
+
if (response.status >= 300 && response.status < 400) {
|
|
3067
|
+
await response.body?.cancel().catch(() => undefined);
|
|
3068
|
+
checks.push({
|
|
3069
|
+
id: "live.semantic-scholar",
|
|
3070
|
+
category: "live-check",
|
|
3071
|
+
status: "fail",
|
|
3072
|
+
detail: "Semantic Scholar returned a redirect; credential-bearing redirects are not followed.",
|
|
3073
|
+
minimumAction: "Verify the fixed Semantic Scholar endpoint and network policy, then rerun setup doctor; do not fall back to a standalone source wrapper.",
|
|
3074
|
+
diagnostics: {
|
|
3075
|
+
code: "PROVIDER_REDIRECT_REJECTED",
|
|
3076
|
+
executionMode: "setup-doctor",
|
|
3077
|
+
credentialScope: "adapter",
|
|
3078
|
+
networkAttempted: true,
|
|
3079
|
+
httpStatus: response.status,
|
|
3080
|
+
},
|
|
3081
|
+
});
|
|
3082
|
+
return;
|
|
3083
|
+
}
|
|
3084
|
+
if (!response.ok) {
|
|
3085
|
+
const authenticationFailure = response.status === 401 || response.status === 403;
|
|
3086
|
+
const rateLimited = response.status === 429;
|
|
3087
|
+
const detail = authenticationFailure
|
|
3088
|
+
? ""
|
|
3089
|
+
: await boundedResponseText(response, 2_000, apiKey ? [apiKey] : []);
|
|
3090
|
+
checks.push({
|
|
3091
|
+
id: "live.semantic-scholar",
|
|
3092
|
+
category: "live-check",
|
|
3093
|
+
status: "fail",
|
|
3094
|
+
detail: sanitizeResearchText(`Semantic Scholar live check returned HTTP ${response.status}${detail ? `: ${detail}` : ""}.`, apiKey ? [apiKey] : []),
|
|
3095
|
+
minimumAction: rateLimited
|
|
3096
|
+
? `Wait for the provider quota window${retryAfterSeconds === null ? "" : ` (Retry-After ${retryAfterSeconds}s)`}, then rerun setup doctor. The production workflow remains blocked and must not downgrade to standalone search.`
|
|
3097
|
+
: authenticationFailure
|
|
3098
|
+
? "Replace or remove the optional Semantic Scholar API key, verify provider entitlement, and rerun setup doctor; do not expose the key in output."
|
|
3099
|
+
: "Verify Semantic Scholar availability and the fixed API contract, then rerun setup doctor; do not downgrade the research workflow.",
|
|
3100
|
+
diagnostics: {
|
|
3101
|
+
code: authenticationFailure
|
|
3102
|
+
? "PROVIDER_AUTHENTICATION_FAILED"
|
|
3103
|
+
: rateLimited
|
|
3104
|
+
? "PROVIDER_RATE_LIMITED"
|
|
3105
|
+
: "PROVIDER_REQUEST_FAILED",
|
|
3106
|
+
executionMode: "setup-doctor",
|
|
3107
|
+
credentialScope: "adapter",
|
|
3108
|
+
networkAttempted: true,
|
|
3109
|
+
httpStatus: response.status,
|
|
3110
|
+
retryAfterSeconds,
|
|
3111
|
+
},
|
|
3112
|
+
});
|
|
3113
|
+
return;
|
|
3114
|
+
}
|
|
3115
|
+
await response.body?.cancel().catch(() => undefined);
|
|
3116
|
+
checks.push({
|
|
3117
|
+
id: "live.semantic-scholar",
|
|
3118
|
+
category: "live-check",
|
|
3119
|
+
status: "pass",
|
|
3120
|
+
detail: apiKey
|
|
3121
|
+
? "Semantic Scholar accepted the configured optional API key."
|
|
3122
|
+
: "Semantic Scholar public API is reachable without an optional API key.",
|
|
3123
|
+
minimumAction: null,
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
3126
|
+
catch (error) {
|
|
3127
|
+
checks.push({
|
|
3128
|
+
id: "live.semantic-scholar",
|
|
3129
|
+
category: "live-check",
|
|
3130
|
+
status: "fail",
|
|
3131
|
+
detail: sanitizeResearchText(error instanceof Error ? error.message : String(error), apiKey ? [apiKey] : []),
|
|
3132
|
+
minimumAction: "Restore provider connectivity, then rerun setup doctor; the production workflow must remain blocked instead of using a standalone fallback.",
|
|
3133
|
+
diagnostics: {
|
|
3134
|
+
code: "PROVIDER_TRANSPORT_FAILED",
|
|
3135
|
+
executionMode: "setup-doctor",
|
|
3136
|
+
credentialScope: "adapter",
|
|
3137
|
+
networkAttempted: true,
|
|
3138
|
+
},
|
|
3139
|
+
});
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
function setupRetryAfterSeconds(value) {
|
|
3143
|
+
if (!value)
|
|
3144
|
+
return null;
|
|
3145
|
+
const seconds = Number(value);
|
|
3146
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
3147
|
+
return Math.ceil(seconds);
|
|
3148
|
+
const timestamp = Date.parse(value);
|
|
3149
|
+
if (!Number.isFinite(timestamp))
|
|
3150
|
+
return null;
|
|
3151
|
+
return Math.max(0, Math.ceil((timestamp - Date.now()) / 1_000));
|
|
3152
|
+
}
|
|
3011
3153
|
async function boundedResponseText(response, maximumBytes, secrets) {
|
|
3012
3154
|
if (!response.body)
|
|
3013
3155
|
return "";
|
|
@@ -3085,6 +3227,7 @@ function setupError(input) {
|
|
|
3085
3227
|
reason: input.reason,
|
|
3086
3228
|
minimumAction: input.minimumAction,
|
|
3087
3229
|
retryCommand: input.retryCommand,
|
|
3230
|
+
...(input.diagnostics === undefined ? {} : { diagnostics: input.diagnostics }),
|
|
3088
3231
|
});
|
|
3089
3232
|
return new CliError(sanitizeResearchText(input.reason), {
|
|
3090
3233
|
code: input.code,
|