mirror of
https://github.com/fancive/obsidian-parallel-reader.git
synced 2026-07-22 06:53:43 +00:00
refactor: extract provider HTTP helpers to provider-request.ts
Moves RequestUrlFunction type, endpointUrl, responseJson, requestJsonBody, shouldRetryWithoutStructuredOutput, and requestJsonBodyWithStructuredFallback to a dedicated provider-request.ts module (103 lines). providers.ts reduced from 377 to 277 lines. Change-Id: I013d6a0b9565770c00e48eb416541fcf08d1c5e8
This commit is contained in:
parent
ed93e25cc9
commit
60e9ad6bb2
3 changed files with 114 additions and 111 deletions
20
main.js
20
main.js
File diff suppressed because one or more lines are too long
103
src/provider-request.ts
Normal file
103
src/provider-request.ts
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
'use strict';
|
||||
|
||||
import { translate } from './i18n';
|
||||
import type { PluginSettings } from './types';
|
||||
|
||||
export type RequestUrlFunction = (params: {
|
||||
url: string;
|
||||
method: string;
|
||||
headers: Record<string, string>;
|
||||
body: string;
|
||||
throw?: boolean;
|
||||
}) => Promise<{ status: number; json: unknown; text: string }>;
|
||||
|
||||
export function endpointUrl(baseUrl: string, suffixes: string[]) {
|
||||
const base = baseUrl.replace(/\/+$/, '');
|
||||
for (const suffix of suffixes) {
|
||||
if (base.endsWith(suffix)) return base;
|
||||
}
|
||||
return base + suffixes[0];
|
||||
}
|
||||
|
||||
export function responseJson(
|
||||
resp: { json: unknown; text: string },
|
||||
label: string,
|
||||
settings?: PluginSettings | null,
|
||||
): Record<string, unknown> {
|
||||
if (resp.json && typeof resp.json === 'object') return resp.json as Record<string, unknown>;
|
||||
try {
|
||||
return JSON.parse(resp.text || '{}') as Record<string, unknown>;
|
||||
} catch (_) {
|
||||
throw new Error(
|
||||
translate(settings || null, 'errorProviderNonJson', {
|
||||
label,
|
||||
excerpt: (resp.text || '').slice(0, 500),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function requestJsonBody(
|
||||
requestUrlImpl: RequestUrlFunction,
|
||||
label: string,
|
||||
url: string,
|
||||
headers: Record<string, string>,
|
||||
body: unknown,
|
||||
settings?: PluginSettings | null,
|
||||
): Promise<Record<string, unknown>> {
|
||||
let resp: { status: number; json: unknown; text: string };
|
||||
try {
|
||||
resp = await requestUrlImpl({
|
||||
url,
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
throw: false,
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
throw new Error(
|
||||
translate(settings || null, 'errorProviderRequestFailed', {
|
||||
label,
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (resp.status >= 400) {
|
||||
throw new Error(
|
||||
translate(settings || null, 'errorProviderApiStatus', {
|
||||
label,
|
||||
status: resp.status,
|
||||
excerpt: (resp.text || '').slice(0, 500),
|
||||
}),
|
||||
);
|
||||
}
|
||||
return responseJson(resp, label, settings);
|
||||
}
|
||||
|
||||
export function shouldRetryWithoutStructuredOutput(error: unknown): boolean {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
if (!/(?:API (?:400|404|422):|API returned HTTP (?:400|404|422)|API 返回 HTTP (?:400|404|422))/.test(message))
|
||||
return false;
|
||||
return /response_format|json_schema|responseJsonSchema|responseMimeType|tools?|tool_choice|unsupported|unrecognized|unknown|schema/i.test(
|
||||
message,
|
||||
);
|
||||
}
|
||||
|
||||
export async function requestJsonBodyWithStructuredFallback(
|
||||
requestUrlImpl: RequestUrlFunction,
|
||||
label: string,
|
||||
url: string,
|
||||
headers: Record<string, string>,
|
||||
structuredBody: unknown,
|
||||
fallbackBody: unknown,
|
||||
settings?: PluginSettings | null,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
return await requestJsonBody(requestUrlImpl, label, url, headers, structuredBody, settings);
|
||||
} catch (e: unknown) {
|
||||
if (!fallbackBody || !shouldRetryWithoutStructuredOutput(e)) throw e;
|
||||
console.warn(`[parallel-reader] ${label} structured output rejected; retrying without structured output`, e);
|
||||
return requestJsonBody(requestUrlImpl, label + ' fallback', url, headers, fallbackBody, settings);
|
||||
}
|
||||
}
|
||||
102
src/providers.ts
102
src/providers.ts
|
|
@ -14,6 +14,7 @@ import {
|
|||
textFromOpenAiChatResponse,
|
||||
textFromOpenAiResponsesResponse,
|
||||
} from './provider-parsers';
|
||||
import { endpointUrl, type RequestUrlFunction, requestJsonBodyWithStructuredFallback } from './provider-request';
|
||||
import { parseCardsJson } from './schema';
|
||||
import {
|
||||
API_FORMATS,
|
||||
|
|
@ -35,16 +36,6 @@ export {
|
|||
tokenLimitFieldForOpenAiChat,
|
||||
} from './provider-bodies';
|
||||
|
||||
/* ---------- Typed request/response shapes ---------- */
|
||||
|
||||
type RequestUrlFunction = (params: {
|
||||
url: string;
|
||||
method: string;
|
||||
headers: Record<string, string>;
|
||||
body: string;
|
||||
throw?: boolean;
|
||||
}) => Promise<{ status: number; json: unknown; text: string }>;
|
||||
|
||||
function requiredDeltaExtractor(format: string) {
|
||||
const extractor = deltaExtractorForFormat(format);
|
||||
if (!extractor) throw new Error(`Streaming not supported for format: ${format}`);
|
||||
|
|
@ -53,14 +44,6 @@ function requiredDeltaExtractor(format: string) {
|
|||
|
||||
/* ---------- Helpers ---------- */
|
||||
|
||||
function endpointUrl(baseUrl: string, suffixes: string[]) {
|
||||
const base = baseUrl.replace(/\/+$/, '');
|
||||
for (const suffix of suffixes) {
|
||||
if (base.endsWith(suffix)) return base;
|
||||
}
|
||||
return base + suffixes[0];
|
||||
}
|
||||
|
||||
export function parseApiHeaders(raw: string, settings?: PluginSettings | null): Record<string, string> {
|
||||
const text = (raw || '').trim();
|
||||
if (!text) return {};
|
||||
|
|
@ -125,89 +108,6 @@ function buildApiHeaders(settings: PluginSettings, extra?: Record<string, string
|
|||
};
|
||||
}
|
||||
|
||||
function responseJson(
|
||||
resp: { json: unknown; text: string },
|
||||
label: string,
|
||||
settings?: PluginSettings | null,
|
||||
): Record<string, unknown> {
|
||||
if (resp.json && typeof resp.json === 'object') return resp.json as Record<string, unknown>;
|
||||
try {
|
||||
return JSON.parse(resp.text || '{}') as Record<string, unknown>;
|
||||
} catch (_) {
|
||||
throw new Error(
|
||||
translate(settings || null, 'errorProviderNonJson', {
|
||||
label,
|
||||
excerpt: (resp.text || '').slice(0, 500),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function requestJsonBody(
|
||||
requestUrlImpl: RequestUrlFunction,
|
||||
label: string,
|
||||
url: string,
|
||||
headers: Record<string, string>,
|
||||
body: unknown,
|
||||
settings?: PluginSettings | null,
|
||||
): Promise<Record<string, unknown>> {
|
||||
let resp: { status: number; json: unknown; text: string };
|
||||
try {
|
||||
resp = await requestUrlImpl({
|
||||
url,
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
throw: false,
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
throw new Error(
|
||||
translate(settings || null, 'errorProviderRequestFailed', {
|
||||
label,
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (resp.status >= 400) {
|
||||
throw new Error(
|
||||
translate(settings || null, 'errorProviderApiStatus', {
|
||||
label,
|
||||
status: resp.status,
|
||||
excerpt: (resp.text || '').slice(0, 500),
|
||||
}),
|
||||
);
|
||||
}
|
||||
return responseJson(resp, label, settings);
|
||||
}
|
||||
|
||||
function shouldRetryWithoutStructuredOutput(error: unknown): boolean {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
if (!/(?:API (?:400|404|422):|API returned HTTP (?:400|404|422)|API 返回 HTTP (?:400|404|422))/.test(message))
|
||||
return false;
|
||||
return /response_format|json_schema|responseJsonSchema|responseMimeType|tools?|tool_choice|unsupported|unrecognized|unknown|schema/i.test(
|
||||
message,
|
||||
);
|
||||
}
|
||||
|
||||
async function requestJsonBodyWithStructuredFallback(
|
||||
requestUrlImpl: RequestUrlFunction,
|
||||
label: string,
|
||||
url: string,
|
||||
headers: Record<string, string>,
|
||||
structuredBody: unknown,
|
||||
fallbackBody: unknown,
|
||||
settings?: PluginSettings | null,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
return await requestJsonBody(requestUrlImpl, label, url, headers, structuredBody, settings);
|
||||
} catch (e: unknown) {
|
||||
if (!fallbackBody || !shouldRetryWithoutStructuredOutput(e)) throw e;
|
||||
console.warn(`[parallel-reader] ${label} structured output rejected; retrying without structured output`, e);
|
||||
return requestJsonBody(requestUrlImpl, label + ' fallback', url, headers, fallbackBody, settings);
|
||||
}
|
||||
}
|
||||
|
||||
async function summarizeViaAnthropicMessages(
|
||||
requestUrlImpl: RequestUrlFunction,
|
||||
system: string,
|
||||
|
|
|
|||
Loading…
Reference in a new issue