Files
AFFiNE-Mirror/packages/backend/server/src/plugins/copilot/tools/blob-read.ts
DarkSky 728e02cab7 feat: bump eslint & oxlint (#14452)
#### PR Dependency Tree


* **PR #14452** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

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

* **Bug Fixes**
* Improved null-safety, dependency tracking, upload validation, and
error logging for more reliable uploads, clipboard, calendar linking,
telemetry, PDF/theme printing, and preview/zoom behavior.
* Tightened handling of all-day calendar events (missing date now
reported).

* **Deprecations**
  * Removed deprecated RadioButton and RadioButtonGroup; use RadioGroup.

* **Chores**
* Unified and upgraded linting/config, reorganized imports, and
standardized binary handling for more consistent builds and tooling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-16 13:52:08 +08:00

81 lines
2.2 KiB
TypeScript

import { Logger } from '@nestjs/common';
import { tool } from 'ai';
import { z } from 'zod';
import { AccessController } from '../../../core/permission';
import { toolError } from './error';
import type { ContextSession, CopilotChatOptions } from './types';
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 [file, blob] = await Promise.all([
context?.getFileContent(blobId, chunk),
context?.getBlobContent(blobId, chunk),
]);
const content = file?.trim() || blob?.trim();
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.',
inputSchema: 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);
}
},
});
};