mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-11-01 11:36:29 +00:00
Some checks are pending
Backend Unit Tests / Build packages (push) Waiting to run
Backend Unit Tests / Codegraph select (push) Waiting to run
Backend Unit Tests / TypeScript type checks (push) Blocked by required conditions
Backend Unit Tests / Circular dependency checks (push) Waiting to run
Backend Unit Tests / Tests: api (shard 1/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 2/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 3/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: data-provider (push) Blocked by required conditions
Backend Unit Tests / Tests: data-schemas (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 1/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 2/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 3/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 4/4) (push) Blocked by required conditions
Codegraph E2E Votes / vote (full suite) (push) Waiting to run
Docker Dev Branch Images Build / publish (push) Waiting to run
Frontend Unit Tests / Codegraph select (push) Waiting to run
Frontend Unit Tests / Build packages (push) Waiting to run
Frontend Unit Tests / TypeScript type checks (client) (push) Blocked by required conditions
Frontend Unit Tests / Tests: @librechat/client (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 1/2) (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 2/2) (push) Blocked by required conditions
Frontend Unit Tests / Vite build verification (push) Blocked by required conditions
`create_file` reads its target before writing so it can tell a create from an overwrite, so `readSandboxFile` missing the path is the ordinary case for a create — yet it was reported at error level, through `logAxiosError`, which rendered the sandbox's own `cat: …: No such file or directory` as "An error occurred while setting up the request" plus a stack. Every ordinary file creation emitted an error-level entry claiming a transport fault about a file that was written half a second later, and an operator reading it concluded the file had been lost. Split the two failures `readSandboxFile` was conflating. A request that never completed still goes to `logAxiosError`; a request the sandbox answered with stderr is now classified on that stderr — an absent path logs at debug, and anything else (permission, a broken interpreter) keeps error level with the sandbox's own wording. The thrown error is unchanged, so the authoring flow's create/overwrite decision still reads it the same way. The classifier moves to `packages/api/src/files/code/errors.ts` and is now shared: `handlers.ts` had two near-duplicate copies of it, the narrower of which documented itself as narrower than the broader one without either deriving from the other. Also carry the cause on `[preflightCodeOutputBatch] … could not be inspected`. That warning follows each `create_file` in the same request, and the batch discarded the error entirely — so a Code API that refused the download, one routed to the wrong execution profile, and a file too large to inspect all arrived as the same sentence, and neither this warning nor the one above could be told apart from the failures around them. It goes through `getSafeErrorMetadata`, which is what keeps the upstream message and the artifact's name — both of which can echo submitted content — out of the log.
101 lines
3.5 KiB
JavaScript
101 lines
3.5 KiB
JavaScript
const mockRunCodeOutputBatchPreflight = jest.fn();
|
|
const mockPrepareCodeOutputForInspection = jest.fn();
|
|
const mockGetSafeErrorMetadata = jest.fn(() => ({ type: 'Error', status: 404 }));
|
|
|
|
jest.mock('@librechat/data-schemas', () => ({
|
|
logger: { warn: jest.fn() },
|
|
}));
|
|
|
|
jest.mock('@librechat/api', () => ({
|
|
preflightCodeOutputBatch: (...args) => mockRunCodeOutputBatchPreflight(...args),
|
|
getSafeErrorMetadata: (...args) => mockGetSafeErrorMetadata(...args),
|
|
}));
|
|
|
|
jest.mock('librechat-data-provider', () => ({
|
|
EModelEndpoint: { agents: 'agents' },
|
|
mergeFileConfig: jest.fn(() => ({ serverFileSizeLimit: 32 })),
|
|
getEndpointFileConfig: jest.fn(() => ({
|
|
fileLimit: 4,
|
|
fileSizeLimit: 8,
|
|
totalSizeLimit: 16,
|
|
})),
|
|
}));
|
|
|
|
jest.mock('./process', () => ({
|
|
prepareCodeOutputForInspection: (...args) => mockPrepareCodeOutputForInspection(...args),
|
|
}));
|
|
|
|
const { logger } = require('@librechat/data-schemas');
|
|
const { preflightCodeOutputBatch } = require('./preflight');
|
|
|
|
describe('preflightCodeOutputBatch Code API routing', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
mockPrepareCodeOutputForInspection.mockResolvedValue({
|
|
buffer: Buffer.from('safe'),
|
|
file: { name: 'output.txt', content: 'safe' },
|
|
});
|
|
mockRunCodeOutputBatchPreflight.mockImplementation(async ({ prepare }) => {
|
|
await prepare({
|
|
file: { id: 'file-1', name: 'output.txt' },
|
|
sessionId: 'session-1',
|
|
maxBytes: 8,
|
|
inspectContent: true,
|
|
});
|
|
return [];
|
|
});
|
|
});
|
|
|
|
it('forwards the trusted per-agent Code API route to inspection downloads', async () => {
|
|
const req = { config: {}, user: { id: 'user-1' } };
|
|
|
|
await preflightCodeOutputBatch({
|
|
req,
|
|
artifact: { files: [{ id: 'file-1', name: 'output.txt' }] },
|
|
codeExecutionContext: {
|
|
baseUrl: 'https://code-stateful.example.com',
|
|
executionProfile: 'stateful',
|
|
executionRouteKey: 'stateful:route',
|
|
},
|
|
});
|
|
|
|
expect(mockPrepareCodeOutputForInspection).toHaveBeenCalledWith({
|
|
req,
|
|
id: 'file-1',
|
|
name: 'output.txt',
|
|
session_id: 'session-1',
|
|
maxBytes: 8,
|
|
inspectContent: true,
|
|
codeApiBaseUrl: 'https://code-stateful.example.com',
|
|
executionProfile: 'stateful',
|
|
executionRouteKey: 'stateful:route',
|
|
});
|
|
});
|
|
|
|
/* The batch degrades an uninspectable artifact to the download fallback and
|
|
* the turn still succeeds, so this warning is the only trace it leaves.
|
|
* Without the cause, a refused download, a misrouted execution profile and
|
|
* an oversized file are one indistinguishable sentence. */
|
|
it('reports why an artifact could not be inspected, through the safe-metadata filter', async () => {
|
|
const cause = Object.assign(new Error('Request failed with status code 404'), {
|
|
response: { status: 404 },
|
|
});
|
|
mockRunCodeOutputBatchPreflight.mockImplementation(async ({ onInspectionUnavailable }) => {
|
|
onInspectionUnavailable(0, cause);
|
|
return [];
|
|
});
|
|
|
|
await preflightCodeOutputBatch({
|
|
req: { config: {}, user: { id: 'user-1' } },
|
|
artifact: { files: [{ id: 'file-1', name: 'PRIVATE-name.txt' }] },
|
|
});
|
|
|
|
expect(mockGetSafeErrorMetadata).toHaveBeenCalledWith(cause);
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
'[preflightCodeOutputBatch] Generated artifact 1 could not be inspected',
|
|
{ type: 'Error', status: 404 },
|
|
);
|
|
/* The artifact's name is submitted content and must never ride the log. */
|
|
expect(JSON.stringify(logger.warn.mock.calls)).not.toContain('PRIVATE-');
|
|
});
|
|
});
|