forked from EduCraft/curriculum-project-hub
ef96f8d33d
Co-authored-by: Hong Jiarong <me@jrhim.com> Co-committed-by: Hong Jiarong <me@jrhim.com>
70 lines
2.5 KiB
TypeScript
70 lines
2.5 KiB
TypeScript
/**
|
|
* ADR-0027: Capability readiness probe. Validates the Alibaba Cloud docmind
|
|
* credential by calling QueryDocParserStatus with a dummy id — a 400 (bad
|
|
* request) means the credential is valid (the API accepted auth but rejected
|
|
* the id); a 401/403 means the credential is bad.
|
|
*/
|
|
import { classifyNetworkFailure, type NetworkFailureCategory } from "../connections/networkFailure.js";
|
|
|
|
export interface CapabilityReadinessInput {
|
|
readonly endpoint: string;
|
|
readonly accessKeyId: string;
|
|
readonly accessKeySecret: string;
|
|
}
|
|
|
|
export type CapabilityReadinessProbe = (input: CapabilityReadinessInput) => Promise<void>;
|
|
|
|
export class CapabilityReadinessError extends Error {
|
|
constructor(
|
|
readonly code: "capability_readiness_unsupported" | "capability_readiness_unreachable" | "capability_readiness_rejected",
|
|
message: string,
|
|
readonly category: NetworkFailureCategory | "configuration" | "http",
|
|
readonly upstreamStatus?: number,
|
|
) {
|
|
super(message);
|
|
this.name = "CapabilityReadinessError";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Probe the Alibaba Cloud docmind credential. We call QueryDocParserStatus
|
|
* with a dummy id. The API will return:
|
|
* - 400 (InvalidParameter) → credential valid, just a bad id → probe passes
|
|
* - 401/403 (InvalidAccessKey/Forbidden) → credential invalid → probe fails
|
|
* - network error → unreachable
|
|
*/
|
|
export const probeDocmindCredential: CapabilityReadinessProbe = async (input) => {
|
|
const url = `https://${input.endpoint}/?Action=QueryDocParserStatus&Id=probe-test&Version=2022-07-11`;
|
|
const authHeader = makeBasicAuth(input.accessKeyId, input.accessKeySecret);
|
|
|
|
let response: Response;
|
|
try {
|
|
response = await fetch(url, {
|
|
method: "GET",
|
|
headers: { authorization: authHeader, accept: "application/json" },
|
|
redirect: "manual",
|
|
signal: AbortSignal.timeout(10_000),
|
|
});
|
|
} catch (error) {
|
|
throw new CapabilityReadinessError(
|
|
"capability_readiness_unreachable",
|
|
"docmind credential readiness check could not reach the API",
|
|
classifyNetworkFailure(error),
|
|
);
|
|
}
|
|
await response.body?.cancel();
|
|
if (response.status === 401 || response.status === 403) {
|
|
throw new CapabilityReadinessError(
|
|
"capability_readiness_rejected",
|
|
`docmind credential rejected: status ${response.status}`,
|
|
"http",
|
|
response.status,
|
|
);
|
|
}
|
|
};
|
|
|
|
function makeBasicAuth(accessKeyId: string, accessKeySecret: string): string {
|
|
const credentials = Buffer.from(`${accessKeyId}:${accessKeySecret}`).toString("base64");
|
|
return `Basic ${credentials}`;
|
|
}
|