GigaChat: honor resolved URLs and headers
This commit is contained in:
parent
af83a32c9e
commit
60e23072e5
@ -34,6 +34,7 @@ describe("createGigachatStreamFn tool calling", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
clientConfigs.length = 0;
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("round-trips sanitized tool names for streamed function calls", async () => {
|
||||
@ -177,6 +178,75 @@ describe("createGigachatStreamFn tool calling", () => {
|
||||
expect(event.content).toEqual([{ type: "text", text: "final tail" }]);
|
||||
});
|
||||
|
||||
it("prefers the resolved GigaChat baseUrl over the env override", async () => {
|
||||
vi.stubEnv("GIGACHAT_BASE_URL", "https://env-host.example/api/v1");
|
||||
request.mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: createSseStream(['data: {"choices":[{"delta":{"content":"done"}}]}', "data: [DONE]"]),
|
||||
});
|
||||
|
||||
const streamFn = createGigachatStreamFn({
|
||||
baseUrl: "https://resolved-host.example/api/v1",
|
||||
authMode: "oauth",
|
||||
});
|
||||
|
||||
const stream = await streamFn(
|
||||
{ api: "gigachat", provider: "gigachat", id: "GigaChat-2-Max" } as never,
|
||||
{ messages: [], tools: [] } as never,
|
||||
{ apiKey: "token" } as never,
|
||||
);
|
||||
|
||||
const event = await stream.result();
|
||||
|
||||
expect(event.content).toEqual([{ type: "text", text: "done" }]);
|
||||
expect(clientConfigs).toHaveLength(1);
|
||||
expect(clientConfigs[0]?.baseUrl).toBe("https://resolved-host.example/api/v1");
|
||||
});
|
||||
|
||||
it("forwards resolved model headers and caller headers on the custom transport", async () => {
|
||||
request.mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: createSseStream(['data: {"choices":[{"delta":{"content":"done"}}]}', "data: [DONE]"]),
|
||||
});
|
||||
|
||||
const streamFn = createGigachatStreamFn({
|
||||
baseUrl: "https://gigachat.devices.sberbank.ru/api/v1",
|
||||
authMode: "oauth",
|
||||
});
|
||||
|
||||
const stream = await streamFn(
|
||||
{
|
||||
api: "gigachat",
|
||||
provider: "gigachat",
|
||||
id: "GigaChat-2-Max",
|
||||
headers: {
|
||||
"X-Model-Header": "model-value",
|
||||
},
|
||||
} as never,
|
||||
{ messages: [], tools: [] } as never,
|
||||
{
|
||||
apiKey: "token",
|
||||
headers: {
|
||||
"X-Caller-Header": "caller-value",
|
||||
},
|
||||
} as never,
|
||||
);
|
||||
|
||||
const event = await stream.result();
|
||||
|
||||
expect(event.content).toEqual([{ type: "text", text: "done" }]);
|
||||
expect(request).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
Authorization: "Bearer test-token",
|
||||
Accept: "text/event-stream",
|
||||
"X-Model-Header": "model-value",
|
||||
"X-Caller-Header": "caller-value",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("sanitizes historical assistant/tool result names in the outbound request", async () => {
|
||||
request.mockResolvedValueOnce({
|
||||
status: 200,
|
||||
|
||||
@ -43,6 +43,15 @@ function stripLeakedFunctionCallPrelude(text: string): string {
|
||||
return text.replace(/^\s*assistant\s+function\s+call(?:\s*([A-Za-z0-9_.:/-]+))?\s*\{\s*/i, "");
|
||||
}
|
||||
|
||||
function resolveGigachatModelHeaders(model: {
|
||||
headers?: unknown;
|
||||
}): Record<string, string> | undefined {
|
||||
if (!model.headers || typeof model.headers !== "object" || Array.isArray(model.headers)) {
|
||||
return undefined;
|
||||
}
|
||||
return model.headers as Record<string, string>;
|
||||
}
|
||||
|
||||
// ── Function name sanitization ──────────────────────────────────────────────
|
||||
// GigaChat requires function names to be alphanumeric + underscore only.
|
||||
|
||||
@ -434,9 +443,10 @@ async function withRetry<T>(
|
||||
// ── Stream function ─────────────────────────────────────────────────────────
|
||||
|
||||
export function createGigachatStreamFn(opts: GigachatStreamOptions): StreamFn {
|
||||
const configuredBaseUrl = opts.baseUrl.trim();
|
||||
const envBaseUrl = process.env.GIGACHAT_BASE_URL?.trim();
|
||||
const effectiveBaseUrl =
|
||||
envBaseUrl || opts.baseUrl || "https://gigachat.devices.sberbank.ru/api/v1";
|
||||
configuredBaseUrl || envBaseUrl || "https://gigachat.devices.sberbank.ru/api/v1";
|
||||
|
||||
const envVerifySsl = process.env.GIGACHAT_VERIFY_SSL_CERTS?.trim().toLowerCase();
|
||||
const insecureTls = opts.insecureTls ?? (envVerifySsl === "false" || envVerifySsl === "0");
|
||||
@ -650,17 +660,21 @@ export function createGigachatStreamFn(opts: GigachatStreamOptions): StreamFn {
|
||||
const requestId = randomUUID();
|
||||
log.debug(`GigaChat request ${requestId}: starting`);
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
...resolveGigachatModelHeaders(model),
|
||||
...options?.headers,
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "text/event-stream",
|
||||
"Cache-Control": "no-store",
|
||||
"X-Request-ID": requestId,
|
||||
};
|
||||
|
||||
const response = await axiosClient.request({
|
||||
method: "POST",
|
||||
url: "/chat/completions",
|
||||
data: { ...chatRequest, stream: true },
|
||||
responseType: "stream",
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "text/event-stream",
|
||||
"Cache-Control": "no-store",
|
||||
"X-Request-ID": requestId,
|
||||
},
|
||||
headers,
|
||||
signal: options?.signal,
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user