feat(server): add hints for context files (#13444)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Attachments (files) are now included in the conversation context,
allowing users to reference files during chat sessions.
* Added a new "blobRead" tool enabling secure, permission-checked
reading of attachment content in chat sessions.

* **Improvements**
* Enhanced chat session preparation to always include relevant context
files.
* System messages now clearly display attached files and selected
content only when available, improving context clarity for users.
* Updated tool-calling guidelines to ensure user workspace is searched
even when attachment content suffices.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2025-08-08 17:32:52 +08:00
committed by GitHub
parent 4005f40b16
commit 3cfb0a43af
9 changed files with 172 additions and 14 deletions

View File

@@ -0,0 +1,77 @@
import { Logger } from '@nestjs/common';
import { tool } from 'ai';
import { z } from 'zod';
import { AccessController } from '../../../core/permission';
import type { ContextSession } from '../context/session';
import type { CopilotChatOptions } from '../providers';
import { toolError } from './error';
const logger = new Logger('ContextBlobReadTool');
export const buildBlobContentGetter = (
ac: AccessController,
context: ContextSession | null
) => {
const getBlobContent = async (
options: CopilotChatOptions,
blobId?: string,
chunk?: number
) => {
if (!options?.user || !options?.workspace || !blobId || !context) {
return;
}
const canAccess = await ac
.user(options.user)
.workspace(options.workspace)
.allowLocal()
.can('Workspace.Read');
if (!canAccess || context.workspaceId !== options.workspace) {
logger.warn(
`User ${options.user} does not have access workspace ${options.workspace}`
);
return;
}
const content = await context?.getFileContent(blobId, chunk);
if (!content) {
return;
}
return { blobId, chunk, content };
};
return getBlobContent;
};
export const createBlobReadTool = (
getBlobContent: (
targetId?: string,
chunk?: number
) => Promise<object | undefined>
) => {
return tool({
description:
'Return the content and basic metadata of a single attachment identified by blobId; more inclined to use search tools rather than this tool.',
parameters: z.object({
blob_id: z.string().describe('The target blob in context to read'),
chunk: z
.number()
.optional()
.describe(
'The chunk number to read, if not provided, read the whole content, start from 0'
),
}),
execute: async ({ blob_id, chunk }) => {
try {
const blob = await getBlobContent(blob_id, chunk);
if (!blob) {
return;
}
return { ...blob };
} catch (err: any) {
logger.error(`Failed to read the blob ${blob_id} in context`, err);
return toolError('Blob Read Failed', err.message);
}
},
});
};