@zleap-ai/dsh-sag 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/README.zh.md +58 -0
- package/THIRD_PARTY_NOTICES +671 -0
- package/cordis.patch.yml +5 -0
- package/docs/embedded.md +61 -0
- package/lib/brand.d.ts +13 -0
- package/lib/brand.js +15 -0
- package/lib/cli/runtime.d.ts +40 -0
- package/lib/cli/runtime.js +121 -0
- package/lib/cli.d.ts +21 -0
- package/lib/cli.js +265 -0
- package/lib/config.d.ts +60 -0
- package/lib/config.js +120 -0
- package/lib/connection/descriptor.d.ts +4 -0
- package/lib/connection/descriptor.js +66 -0
- package/lib/connection/discovery.d.ts +40 -0
- package/lib/connection/discovery.js +139 -0
- package/lib/connection/guidance.d.ts +7 -0
- package/lib/connection/guidance.js +7 -0
- package/lib/connection/manager.d.ts +67 -0
- package/lib/connection/manager.js +273 -0
- package/lib/connection/store.d.ts +42 -0
- package/lib/connection/store.js +164 -0
- package/lib/connection/types.d.ts +35 -0
- package/lib/connection/types.js +2 -0
- package/lib/dsh-sag-cli.js +32202 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +68 -0
- package/lib/local/api-client.d.ts +157 -0
- package/lib/local/api-client.js +338 -0
- package/lib/local/gateway.d.ts +43 -0
- package/lib/local/gateway.js +34 -0
- package/lib/local/mcp-probe.d.ts +27 -0
- package/lib/local/mcp-probe.js +92 -0
- package/lib/presentation.d.ts +8 -0
- package/lib/presentation.js +7 -0
- package/lib/runtime/client.d.ts +22 -0
- package/lib/runtime/client.js +117 -0
- package/lib/runtime/protocol.d.ts +113 -0
- package/lib/runtime/protocol.js +118 -0
- package/lib/runtime/supervisor.d.ts +30 -0
- package/lib/runtime/supervisor.js +107 -0
- package/lib/tools/documents.d.ts +8 -0
- package/lib/tools/documents.js +134 -0
- package/lib/tools/ingest.d.ts +5 -0
- package/lib/tools/ingest.js +35 -0
- package/lib/tools/local.d.ts +40 -0
- package/lib/tools/local.js +111 -0
- package/lib/tools/output.d.ts +96 -0
- package/lib/tools/output.js +67 -0
- package/lib/tools/read.d.ts +6 -0
- package/lib/tools/read.js +84 -0
- package/lib/tools/search.d.ts +6 -0
- package/lib/tools/search.js +107 -0
- package/lib/tools/sources.d.ts +5 -0
- package/lib/tools/sources.js +61 -0
- package/lib/tools/status.d.ts +5 -0
- package/lib/tools/status.js +28 -0
- package/lib/tools/upload.d.ts +6 -0
- package/lib/tools/upload.js +68 -0
- package/package.json +96 -0
- package/runtime/pyproject.toml +29 -0
- package/runtime/src/dsh_sag_runtime/__init__.py +3 -0
- package/runtime/src/dsh_sag_runtime/__main__.py +80 -0
- package/runtime/src/dsh_sag_runtime/engines.py +139 -0
- package/runtime/src/dsh_sag_runtime/errors.py +47 -0
- package/runtime/src/dsh_sag_runtime/evidence.py +53 -0
- package/runtime/src/dsh_sag_runtime/protocol.py +161 -0
- package/runtime/src/dsh_sag_runtime/read.py +61 -0
- package/runtime/src/dsh_sag_runtime/search.py +76 -0
- package/runtime/src/dsh_sag_runtime/server.py +136 -0
- package/runtime/uv.lock +2310 -0
- package/scripts/setup-runtime.mjs +47 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
2
|
+
import { jsonRender, localOperation, requireToolCapability, selectSource } from './local.js';
|
|
3
|
+
const DOCUMENT = {
|
|
4
|
+
type: 'object', additionalProperties: false, properties: {
|
|
5
|
+
id: { type: 'string', required: true }, sourceId: { type: 'string' },
|
|
6
|
+
filename: { type: 'string' }, status: { type: 'string', enum: ['pending', 'loading', 'extracting', 'pausing', 'paused', 'deleting', 'delete_failed', 'ready', 'failed'] },
|
|
7
|
+
progress: { type: 'integer' }, chunkCount: { type: 'integer' },
|
|
8
|
+
error: { oneOf: [{ type: 'string' }, { type: 'null' }] },
|
|
9
|
+
errorLayer: { oneOf: [{ type: 'string' }, { type: 'null' }] },
|
|
10
|
+
errorStage: { oneOf: [{ type: 'string' }, { type: 'null' }] },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
function documentValue(document) {
|
|
14
|
+
return {
|
|
15
|
+
id: document.id,
|
|
16
|
+
...(typeof document.sourceId === 'string' ? { sourceId: document.sourceId } : {}),
|
|
17
|
+
...(typeof document.filename === 'string' ? { filename: document.filename } : {}),
|
|
18
|
+
...(typeof document.status === 'string' ? { status: document.status } : {}),
|
|
19
|
+
...(typeof document.progress === 'number' ? { progress: document.progress } : {}),
|
|
20
|
+
...(typeof document.chunkCount === 'number' ? { chunkCount: document.chunkCount } : {}),
|
|
21
|
+
...(document.error === null || typeof document.error === 'string' ? { error: document.error } : {}),
|
|
22
|
+
...(document.errorLayer === null || typeof document.errorLayer === 'string' ? { errorLayer: document.errorLayer } : {}),
|
|
23
|
+
...(document.errorStage === null || typeof document.errorStage === 'string' ? { errorStage: document.errorStage } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Require user approval for irreversible SAG document deletion. */
|
|
27
|
+
export function sagDeleteApprovalGate(exec, next) {
|
|
28
|
+
if (exec.name !== 'sag_delete_document')
|
|
29
|
+
return next();
|
|
30
|
+
return Promise.resolve({ kind: 'ask', reason: '删除 SAG 文档后无法恢复' });
|
|
31
|
+
}
|
|
32
|
+
/** Build local SAG document inspection, reprocessing, and deletion tools. */
|
|
33
|
+
export function createDocumentTools(manager, config) {
|
|
34
|
+
const list = defineTool({
|
|
35
|
+
name: 'sag_list_documents',
|
|
36
|
+
description: 'List documents and current processing states in a selected local SAG knowledge base.',
|
|
37
|
+
parameters: { source_id: { type: 'string', description: 'Knowledge-base id.' } },
|
|
38
|
+
output: {
|
|
39
|
+
schema: { type: 'object', additionalProperties: false, properties: {
|
|
40
|
+
sourceId: { type: 'string', required: true }, documents: { type: 'array', required: true, items: DOCUMENT },
|
|
41
|
+
} },
|
|
42
|
+
render: (_args, value) => jsonRender(value),
|
|
43
|
+
},
|
|
44
|
+
timeoutMs: config.requestTimeoutMs,
|
|
45
|
+
isConcurrencySafe: () => true,
|
|
46
|
+
async execute(args, exec) {
|
|
47
|
+
return localOperation(manager, exec.signal, async (connection) => {
|
|
48
|
+
requireToolCapability(connection, 'sag_list_documents');
|
|
49
|
+
const sourceId = await selectSource(connection, args.source_id, exec.signal);
|
|
50
|
+
return { sourceId, documents: (await connection.gateway.listDocuments(sourceId, exec.signal)).map(documentValue) };
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
presentCall: args => ({ card: 'generic', title: 'List SAG documents', kind: 'read', rawInput: args.source_id }),
|
|
54
|
+
});
|
|
55
|
+
const get = defineTool({
|
|
56
|
+
name: 'sag_get_document',
|
|
57
|
+
description: 'Get one local SAG document and its current background processing status.',
|
|
58
|
+
parameters: {
|
|
59
|
+
document_id: { type: 'string', required: true, description: 'Document id.' },
|
|
60
|
+
source_id: { type: 'string', description: 'Knowledge-base id.' },
|
|
61
|
+
},
|
|
62
|
+
output: { schema: DOCUMENT, render: (_args, value) => jsonRender(value) },
|
|
63
|
+
timeoutMs: config.requestTimeoutMs,
|
|
64
|
+
isConcurrencySafe: () => true,
|
|
65
|
+
async execute(args, exec) {
|
|
66
|
+
return localOperation(manager, exec.signal, async (connection) => {
|
|
67
|
+
requireToolCapability(connection, 'sag_get_document');
|
|
68
|
+
const sourceId = await selectSource(connection, args.source_id, exec.signal);
|
|
69
|
+
return documentValue(await connection.gateway.getDocument(sourceId, args.document_id, exec.signal));
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
presentCall: args => ({ card: 'generic', title: 'Read SAG document status', kind: 'read', rawInput: args.document_id }),
|
|
73
|
+
});
|
|
74
|
+
const reprocess = defineTool({
|
|
75
|
+
name: 'sag_reprocess_document',
|
|
76
|
+
description: 'Queue asynchronous reprocessing for one local SAG document and return the accepted job state.',
|
|
77
|
+
parameters: {
|
|
78
|
+
document_id: { type: 'string', required: true, description: 'Document id.' },
|
|
79
|
+
source_id: { type: 'string', description: 'Knowledge-base id.' },
|
|
80
|
+
},
|
|
81
|
+
output: { schema: {
|
|
82
|
+
type: 'object', additionalProperties: false, properties: {
|
|
83
|
+
accepted: { type: 'boolean', required: true }, jobId: { type: 'string', required: true },
|
|
84
|
+
documentId: { type: 'string', required: true }, sourceId: { type: 'string', required: true },
|
|
85
|
+
status: { type: 'string', enum: ['queued', 'running', 'paused', 'succeeded', 'failed'], required: true },
|
|
86
|
+
type: { type: 'string', const: 'reprocess_document', required: true },
|
|
87
|
+
progress: { type: 'number' }, attempts: { type: 'integer' },
|
|
88
|
+
error: { oneOf: [{ type: 'string' }, { type: 'null' }] },
|
|
89
|
+
},
|
|
90
|
+
}, render: (_args, value) => jsonRender(value) },
|
|
91
|
+
timeoutMs: config.requestTimeoutMs,
|
|
92
|
+
async execute(args, exec) {
|
|
93
|
+
return localOperation(manager, exec.signal, async (connection) => {
|
|
94
|
+
requireToolCapability(connection, 'sag_reprocess_document');
|
|
95
|
+
const sourceId = await selectSource(connection, args.source_id, exec.signal);
|
|
96
|
+
const job = await connection.gateway.reprocessDocument(sourceId, args.document_id, exec.signal);
|
|
97
|
+
return {
|
|
98
|
+
accepted: true, jobId: job.id, documentId: job.documentId ?? args.document_id,
|
|
99
|
+
sourceId: job.sourceId ?? sourceId, status: job.status,
|
|
100
|
+
type: job.type,
|
|
101
|
+
...(job.progress === undefined ? {} : { progress: job.progress }),
|
|
102
|
+
...(job.attempts === undefined ? {} : { attempts: job.attempts }),
|
|
103
|
+
...(job.error === undefined ? {} : { error: job.error }),
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
presentCall: args => ({ card: 'generic', title: 'Reprocess SAG document', kind: 'execute', rawInput: args.document_id }),
|
|
108
|
+
});
|
|
109
|
+
const remove = defineTool({
|
|
110
|
+
name: 'sag_delete_document',
|
|
111
|
+
description: 'Permanently delete one document from a local SAG knowledge base after dsh user approval.',
|
|
112
|
+
parameters: {
|
|
113
|
+
document_id: { type: 'string', required: true, description: 'Document id.' },
|
|
114
|
+
source_id: { type: 'string', description: 'Knowledge-base id.' },
|
|
115
|
+
},
|
|
116
|
+
output: { schema: {
|
|
117
|
+
type: 'object', additionalProperties: false, properties: {
|
|
118
|
+
deleted: { type: 'boolean', required: true }, documentId: { type: 'string', required: true }, sourceId: { type: 'string', required: true },
|
|
119
|
+
},
|
|
120
|
+
}, render: (_args, value) => jsonRender(value) },
|
|
121
|
+
timeoutMs: config.requestTimeoutMs,
|
|
122
|
+
async execute(args, exec) {
|
|
123
|
+
return localOperation(manager, exec.signal, async (connection) => {
|
|
124
|
+
requireToolCapability(connection, 'sag_delete_document');
|
|
125
|
+
const sourceId = await selectSource(connection, args.source_id, exec.signal);
|
|
126
|
+
await connection.gateway.deleteDocument(sourceId, args.document_id, exec.signal);
|
|
127
|
+
return { deleted: true, documentId: args.document_id, sourceId };
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
presentCall: args => ({ card: 'generic', title: 'Delete SAG document', kind: 'delete', rawInput: args.document_id }),
|
|
131
|
+
});
|
|
132
|
+
return [list, get, reprocess, remove];
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=documents.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ResolvedLocalConfig } from '../config.js';
|
|
2
|
+
import { type ConnectionManager } from './local.js';
|
|
3
|
+
/** Build direct text ingestion for one local SAG knowledge base. */
|
|
4
|
+
export declare function createIngestTextTool(manager: ConnectionManager, config: ResolvedLocalConfig): import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
5
|
+
//# sourceMappingURL=ingest.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
2
|
+
import { jsonRender, localInputError, localOperation, requireToolCapability, selectSource } from './local.js';
|
|
3
|
+
const INGEST_OUTPUT = {
|
|
4
|
+
type: 'object', additionalProperties: false, properties: {
|
|
5
|
+
accepted: { type: 'boolean', required: true }, documentId: { type: 'string', required: true },
|
|
6
|
+
sourceId: { type: 'string', required: true }, status: { type: 'string', required: true },
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
/** Build direct text ingestion for one local SAG knowledge base. */
|
|
10
|
+
export function createIngestTextTool(manager, config) {
|
|
11
|
+
return defineTool({
|
|
12
|
+
name: 'sag_ingest_text',
|
|
13
|
+
description: 'Submit text to a SAG knowledge base and return the accepted document processing status.',
|
|
14
|
+
parameters: {
|
|
15
|
+
text: { type: 'string', required: true, description: 'Text to ingest.' },
|
|
16
|
+
title: { type: 'string', description: 'Optional document title.' },
|
|
17
|
+
source_id: { type: 'string', description: 'Target knowledge-base id; omitted only when a default or single source exists.' },
|
|
18
|
+
},
|
|
19
|
+
output: { schema: INGEST_OUTPUT, render: (_args, value) => jsonRender(value) },
|
|
20
|
+
timeoutMs: config.requestTimeoutMs,
|
|
21
|
+
async execute(args, exec) {
|
|
22
|
+
return localOperation(manager, exec.signal, async (connection) => {
|
|
23
|
+
requireToolCapability(connection, 'sag_ingest_text');
|
|
24
|
+
const text = args.text.trim();
|
|
25
|
+
if (!text)
|
|
26
|
+
localInputError('sag_ingest_text text must not be empty');
|
|
27
|
+
const sourceId = await selectSource(connection, args.source_id, exec.signal);
|
|
28
|
+
const document = await connection.gateway.ingestText({ sourceId, text, ...(args.title === undefined ? {} : { title: args.title }) }, exec.signal);
|
|
29
|
+
return { accepted: true, documentId: document.id, sourceId, status: document.status ?? 'pending' };
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
presentCall: args => ({ card: 'generic', title: 'Ingest text into SAG', kind: 'edit', rawInput: args.title ?? `${args.text.slice(0, 80)}${args.text.length > 80 ? '…' : ''}` }),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=ingest.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { SagConnectionManager } from '../connection/manager.js';
|
|
2
|
+
import type { SagConnectionDescriptor, SagCapabilityDescriptor } from '../connection/types.js';
|
|
3
|
+
import type { SagGateway } from '../local/gateway.js';
|
|
4
|
+
export declare const SAG_NOT_FOUND_MESSAGE = "\u6CA1\u6709\u53D1\u73B0\u6B63\u5728\u8FD0\u884C\u7684 SAG\u3002\u8BF7\u5148\u542F\u52A8 SAG \u540E\u91CD\u8BD5\uFF1B\u4E5F\u53EF\u8FD0\u884C dsh plugin --profile web exec dsh-sag setup \u91CD\u65B0\u53D1\u73B0\u3002";
|
|
5
|
+
export interface LocalConnection {
|
|
6
|
+
readonly descriptor: SagConnectionDescriptor;
|
|
7
|
+
readonly capabilities: SagCapabilityDescriptor;
|
|
8
|
+
readonly gateway: SagGateway;
|
|
9
|
+
readonly sourceCount: number;
|
|
10
|
+
}
|
|
11
|
+
export type ConnectionManager = Pick<SagConnectionManager, 'ensureConnected'>;
|
|
12
|
+
/** Current SAG v1 capability required by each endpoint-backed local Tool. */
|
|
13
|
+
export declare const SAG_TOOL_CAPABILITY: {
|
|
14
|
+
readonly sag_list_sources: "sources.list";
|
|
15
|
+
readonly sag_create_source: "sources.create";
|
|
16
|
+
readonly sag_search: "knowledge.search";
|
|
17
|
+
readonly sag_read: "knowledge.read";
|
|
18
|
+
readonly sag_list_documents: "documents.list";
|
|
19
|
+
readonly sag_get_document: "documents.get";
|
|
20
|
+
readonly sag_upload_file: "documents.upload";
|
|
21
|
+
readonly sag_ingest_text: "documents.ingest";
|
|
22
|
+
readonly sag_reprocess_document: "documents.reprocess";
|
|
23
|
+
readonly sag_delete_document: "documents.delete";
|
|
24
|
+
};
|
|
25
|
+
export type CapabilityGatedToolName = keyof typeof SAG_TOOL_CAPABILITY;
|
|
26
|
+
/** Raise a model-actionable input/configuration failure from inside an operation. */
|
|
27
|
+
export declare function localInputError(message: string): never;
|
|
28
|
+
/** Resolve one ready local connection without exposing credentials or transport details. */
|
|
29
|
+
export declare function connectLocal(manager: ConnectionManager, signal: AbortSignal): Promise<LocalConnection>;
|
|
30
|
+
/** Stop before transport when the connected SAG does not advertise an operation. */
|
|
31
|
+
export declare function requireToolCapability(connection: LocalConnection, toolName: CapabilityGatedToolName): void;
|
|
32
|
+
/** Execute one complete local tool operation behind a credential-safe failure boundary. */
|
|
33
|
+
export declare function localOperation<T>(manager: ConnectionManager, signal: AbortSignal, operation: (connection: LocalConnection) => Promise<T>): Promise<T>;
|
|
34
|
+
/** Choose a source deterministically or return model-actionable candidates. */
|
|
35
|
+
export declare function selectSource(connection: LocalConnection, explicitSourceId: string | undefined, signal: AbortSignal): Promise<string>;
|
|
36
|
+
export declare function jsonRender(value: unknown): {
|
|
37
|
+
type: "text";
|
|
38
|
+
text: string;
|
|
39
|
+
}[];
|
|
40
|
+
//# sourceMappingURL=local.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { FsError } from '@deepseek-ai/dsh-fs';
|
|
2
|
+
import { SAG_DOCTOR_COMMAND, SAG_SETUP_COMMAND } from '../connection/guidance.js';
|
|
3
|
+
import { SagApiError } from '../local/api-client.js';
|
|
4
|
+
export const SAG_NOT_FOUND_MESSAGE = `没有发现正在运行的 SAG。请先启动 SAG 后重试;也可运行 ${SAG_SETUP_COMMAND} 重新发现。`;
|
|
5
|
+
/** Current SAG v1 capability required by each endpoint-backed local Tool. */
|
|
6
|
+
export const SAG_TOOL_CAPABILITY = {
|
|
7
|
+
sag_list_sources: 'sources.list',
|
|
8
|
+
sag_create_source: 'sources.create',
|
|
9
|
+
sag_search: 'knowledge.search',
|
|
10
|
+
sag_read: 'knowledge.read',
|
|
11
|
+
sag_list_documents: 'documents.list',
|
|
12
|
+
sag_get_document: 'documents.get',
|
|
13
|
+
sag_upload_file: 'documents.upload',
|
|
14
|
+
sag_ingest_text: 'documents.ingest',
|
|
15
|
+
sag_reprocess_document: 'documents.reprocess',
|
|
16
|
+
sag_delete_document: 'documents.delete',
|
|
17
|
+
};
|
|
18
|
+
class LocalToolInputError extends Error {
|
|
19
|
+
}
|
|
20
|
+
/** Raise a model-actionable input/configuration failure from inside an operation. */
|
|
21
|
+
export function localInputError(message) {
|
|
22
|
+
throw new LocalToolInputError(message);
|
|
23
|
+
}
|
|
24
|
+
function connectionError(report) {
|
|
25
|
+
switch (report.status) {
|
|
26
|
+
case 'not-found':
|
|
27
|
+
return new Error(SAG_NOT_FOUND_MESSAGE);
|
|
28
|
+
case 'unreachable':
|
|
29
|
+
return new Error(`SAG 已发现但当前无法连接。请确认 SAG 正在运行,然后重试或运行 ${SAG_DOCTOR_COMMAND}。`);
|
|
30
|
+
case 'incompatible':
|
|
31
|
+
return new Error(`当前 SAG 版本与 dsh-sag 不兼容。请升级 SAG,然后运行 ${SAG_DOCTOR_COMMAND}。`);
|
|
32
|
+
case 'ready':
|
|
33
|
+
return new Error(`SAG 连接缺少必要信息。请运行 ${SAG_SETUP_COMMAND} 重新配置。`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Resolve one ready local connection without exposing credentials or transport details. */
|
|
37
|
+
export async function connectLocal(manager, signal) {
|
|
38
|
+
let report;
|
|
39
|
+
try {
|
|
40
|
+
report = await manager.ensureConnected(signal);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
if (signal.aborted)
|
|
44
|
+
throw signal.reason;
|
|
45
|
+
throw new Error(`无法检查 SAG 连接。请确认 SAG 正在运行,然后重试或运行 ${SAG_DOCTOR_COMMAND}。`);
|
|
46
|
+
}
|
|
47
|
+
if (report.status !== 'ready' || report.descriptor === undefined || report.gateway === undefined || report.capabilities === undefined) {
|
|
48
|
+
throw connectionError(report);
|
|
49
|
+
}
|
|
50
|
+
return { descriptor: report.descriptor, capabilities: report.capabilities, gateway: report.gateway, sourceCount: report.sourceCount };
|
|
51
|
+
}
|
|
52
|
+
/** Stop before transport when the connected SAG does not advertise an operation. */
|
|
53
|
+
export function requireToolCapability(connection, toolName) {
|
|
54
|
+
const capability = SAG_TOOL_CAPABILITY[toolName];
|
|
55
|
+
if (!connection.capabilities.capabilities.includes(capability)) {
|
|
56
|
+
localInputError(`This SAG version does not provide ${capability}. Upgrade SAG or use sag_status to inspect available capabilities.`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function redacted(value, token) {
|
|
60
|
+
return value?.split(token).join('<redacted>');
|
|
61
|
+
}
|
|
62
|
+
function publicApiError(error, token) {
|
|
63
|
+
const prefix = `SAG API ${error.status} ${error.code}: `;
|
|
64
|
+
const detail = error.message.startsWith(prefix) ? error.message.slice(prefix.length) : error.message;
|
|
65
|
+
return new SagApiError(error.status, redacted(error.code, token), redacted(detail, token), error.retryable, redacted(error.requestId, token), redacted(error.layer, token), redacted(error.stage, token));
|
|
66
|
+
}
|
|
67
|
+
/** Execute one complete local tool operation behind a credential-safe failure boundary. */
|
|
68
|
+
export async function localOperation(manager, signal, operation) {
|
|
69
|
+
const connection = await connectLocal(manager, signal);
|
|
70
|
+
try {
|
|
71
|
+
return await operation(connection);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (signal.aborted)
|
|
75
|
+
throw signal.reason;
|
|
76
|
+
if (error instanceof LocalToolInputError || error instanceof FsError)
|
|
77
|
+
throw error;
|
|
78
|
+
if (error instanceof SagApiError)
|
|
79
|
+
throw publicApiError(error, connection.descriptor.accessToken);
|
|
80
|
+
throw new Error(`SAG 连接已中断或返回了不兼容的数据。请确认 SAG 正在运行,然后重试或运行 ${SAG_DOCTOR_COMMAND}。`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Choose a source deterministically or return model-actionable candidates. */
|
|
84
|
+
export async function selectSource(connection, explicitSourceId, signal) {
|
|
85
|
+
const explicit = explicitSourceId?.trim();
|
|
86
|
+
if (explicit)
|
|
87
|
+
return explicit;
|
|
88
|
+
const configured = connection.capabilities.defaultSourceId?.trim();
|
|
89
|
+
if (configured)
|
|
90
|
+
return configured;
|
|
91
|
+
if (connection.capabilities.defaultSourceId === undefined) {
|
|
92
|
+
const descriptorDefault = connection.descriptor.defaultSourceId?.trim();
|
|
93
|
+
if (descriptorDefault)
|
|
94
|
+
return descriptorDefault;
|
|
95
|
+
}
|
|
96
|
+
if (!connection.capabilities.capabilities.includes('sources.list')) {
|
|
97
|
+
localInputError('当前 SAG 未提供 sources.list,且没有默认知识库。请为此操作明确指定 source_id。');
|
|
98
|
+
}
|
|
99
|
+
const sources = await connection.gateway.listSources(signal);
|
|
100
|
+
if (sources.length === 1)
|
|
101
|
+
return sources[0].id;
|
|
102
|
+
if (sources.length === 0) {
|
|
103
|
+
localInputError('SAG 中还没有知识库。请先用 sag_create_source 创建一个知识库。');
|
|
104
|
+
}
|
|
105
|
+
const choices = sources.map(source => `${source.id} (${source.name})`).join('、');
|
|
106
|
+
localInputError(`发现多个知识库且未设置默认项,请指定知识库 source_id。可选知识库:${choices}`);
|
|
107
|
+
}
|
|
108
|
+
export function jsonRender(value) {
|
|
109
|
+
return [{ type: 'text', text: JSON.stringify(value, null, 2) }];
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=local.js.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { ReadResult, SearchResult } from '../runtime/protocol.js';
|
|
2
|
+
export declare const SEARCH_OUTPUT_SCHEMA: {
|
|
3
|
+
readonly type: "object";
|
|
4
|
+
readonly additionalProperties: false;
|
|
5
|
+
readonly properties: {
|
|
6
|
+
readonly query: {
|
|
7
|
+
readonly type: "string";
|
|
8
|
+
readonly required: true;
|
|
9
|
+
};
|
|
10
|
+
readonly evidences: {
|
|
11
|
+
readonly type: "array";
|
|
12
|
+
readonly required: true;
|
|
13
|
+
readonly items: {
|
|
14
|
+
readonly type: "object";
|
|
15
|
+
readonly additionalProperties: false;
|
|
16
|
+
readonly properties: {
|
|
17
|
+
readonly evidenceRef: {
|
|
18
|
+
readonly type: "string";
|
|
19
|
+
readonly required: true;
|
|
20
|
+
};
|
|
21
|
+
readonly namespaceId: {
|
|
22
|
+
readonly type: "string";
|
|
23
|
+
readonly required: true;
|
|
24
|
+
};
|
|
25
|
+
readonly sourceId: {
|
|
26
|
+
readonly type: "string";
|
|
27
|
+
readonly required: true;
|
|
28
|
+
};
|
|
29
|
+
readonly title: {
|
|
30
|
+
readonly type: "string";
|
|
31
|
+
readonly required: true;
|
|
32
|
+
};
|
|
33
|
+
readonly excerpt: {
|
|
34
|
+
readonly type: "string";
|
|
35
|
+
readonly required: true;
|
|
36
|
+
};
|
|
37
|
+
readonly score: {
|
|
38
|
+
readonly type: "number";
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export declare const READ_OUTPUT_SCHEMA: {
|
|
46
|
+
readonly type: "object";
|
|
47
|
+
readonly additionalProperties: false;
|
|
48
|
+
readonly properties: {
|
|
49
|
+
readonly title: {
|
|
50
|
+
readonly type: "string";
|
|
51
|
+
readonly required: true;
|
|
52
|
+
};
|
|
53
|
+
readonly content: {
|
|
54
|
+
readonly type: "string";
|
|
55
|
+
readonly required: true;
|
|
56
|
+
};
|
|
57
|
+
readonly offset: {
|
|
58
|
+
readonly type: "integer";
|
|
59
|
+
readonly required: true;
|
|
60
|
+
};
|
|
61
|
+
readonly nextOffset: {
|
|
62
|
+
readonly type: "integer";
|
|
63
|
+
};
|
|
64
|
+
readonly totalChars: {
|
|
65
|
+
readonly type: "integer";
|
|
66
|
+
readonly required: true;
|
|
67
|
+
};
|
|
68
|
+
readonly events: {
|
|
69
|
+
readonly type: "array";
|
|
70
|
+
readonly items: {
|
|
71
|
+
readonly type: "object";
|
|
72
|
+
readonly additionalProperties: false;
|
|
73
|
+
readonly properties: {
|
|
74
|
+
readonly id: {
|
|
75
|
+
readonly type: "string";
|
|
76
|
+
};
|
|
77
|
+
readonly title: {
|
|
78
|
+
readonly type: "string";
|
|
79
|
+
};
|
|
80
|
+
readonly summary: {
|
|
81
|
+
readonly type: "string";
|
|
82
|
+
};
|
|
83
|
+
readonly category: {
|
|
84
|
+
readonly type: "string";
|
|
85
|
+
};
|
|
86
|
+
readonly rank: {
|
|
87
|
+
readonly type: "integer";
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
export declare function renderSearch(result: SearchResult, labels: ReadonlyMap<string, string>): string;
|
|
95
|
+
export declare function renderRead(result: ReadResult): string;
|
|
96
|
+
//# sourceMappingURL=output.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export const SEARCH_OUTPUT_SCHEMA = {
|
|
2
|
+
type: 'object',
|
|
3
|
+
additionalProperties: false,
|
|
4
|
+
properties: {
|
|
5
|
+
query: { type: 'string', required: true },
|
|
6
|
+
evidences: {
|
|
7
|
+
type: 'array',
|
|
8
|
+
required: true,
|
|
9
|
+
items: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
additionalProperties: false,
|
|
12
|
+
properties: {
|
|
13
|
+
evidenceRef: { type: 'string', required: true },
|
|
14
|
+
namespaceId: { type: 'string', required: true },
|
|
15
|
+
sourceId: { type: 'string', required: true },
|
|
16
|
+
title: { type: 'string', required: true },
|
|
17
|
+
excerpt: { type: 'string', required: true },
|
|
18
|
+
score: { type: 'number' },
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
export const READ_OUTPUT_SCHEMA = {
|
|
25
|
+
type: 'object',
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
properties: {
|
|
28
|
+
title: { type: 'string', required: true },
|
|
29
|
+
content: { type: 'string', required: true },
|
|
30
|
+
offset: { type: 'integer', required: true },
|
|
31
|
+
nextOffset: { type: 'integer' },
|
|
32
|
+
totalChars: { type: 'integer', required: true },
|
|
33
|
+
events: {
|
|
34
|
+
type: 'array',
|
|
35
|
+
items: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
additionalProperties: false,
|
|
38
|
+
properties: {
|
|
39
|
+
id: { type: 'string' },
|
|
40
|
+
title: { type: 'string' },
|
|
41
|
+
summary: { type: 'string' },
|
|
42
|
+
category: { type: 'string' },
|
|
43
|
+
rank: { type: 'integer' },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
export function renderSearch(result, labels) {
|
|
50
|
+
if (result.evidences.length === 0)
|
|
51
|
+
return 'No SAG evidence matched the query.';
|
|
52
|
+
return result.evidences.map((evidence, index) => {
|
|
53
|
+
const label = labels.get(evidence.namespaceId) ?? evidence.namespaceId;
|
|
54
|
+
const score = evidence.score === undefined ? '' : ` (score ${evidence.score.toFixed(3)})`;
|
|
55
|
+
return `${index + 1}. [${label}] ${evidence.title}${score}\n ${evidence.excerpt}\n evidence_ref: ${evidence.evidenceRef}`;
|
|
56
|
+
}).join('\n\n');
|
|
57
|
+
}
|
|
58
|
+
export function renderRead(result) {
|
|
59
|
+
const events = result.events?.length
|
|
60
|
+
? `\n\nRelated events:\n${result.events.map(event => `- ${event.title ?? event.id ?? 'event'}${event.summary ? `: ${event.summary}` : ''}`).join('\n')}`
|
|
61
|
+
: '';
|
|
62
|
+
const continuation = result.nextOffset === undefined
|
|
63
|
+
? ''
|
|
64
|
+
: `\n\nContinue with sag_read using the same evidence_ref and offset=${result.nextOffset}.`;
|
|
65
|
+
return `# ${result.title}\n\n${result.content}${events}${continuation}`;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SagConnectionManager } from '../connection/manager.js';
|
|
2
|
+
import type { ResolvedConfig } from '../config.js';
|
|
3
|
+
import type { SagRuntimeClient } from '../runtime/client.js';
|
|
4
|
+
/** Build the mode-specific bounded evidence reader. */
|
|
5
|
+
export declare function createReadTool(client: SagRuntimeClient | Pick<SagConnectionManager, 'ensureConnected'>, config: ResolvedConfig): import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
6
|
+
//# sourceMappingURL=read.d.ts.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
2
|
+
import { sagEvidenceRef } from '../brand.js';
|
|
3
|
+
import { decodeLocalEvidenceRef } from '../local/api-client.js';
|
|
4
|
+
import { presentReadCall } from '../presentation.js';
|
|
5
|
+
import { localInputError, localOperation, requireToolCapability } from './local.js';
|
|
6
|
+
import { READ_OUTPUT_SCHEMA, renderRead } from './output.js';
|
|
7
|
+
function readResult(value) {
|
|
8
|
+
if (value === null || typeof value !== 'object' || !('content' in value))
|
|
9
|
+
throw new Error('SAG runtime returned the wrong read result type');
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
function createEmbeddedReadTool(client, config) {
|
|
13
|
+
return defineTool({
|
|
14
|
+
name: 'sag_read', description: 'Read a bounded page from one embedded evidence_ref returned by sag_search.',
|
|
15
|
+
parameters: {
|
|
16
|
+
evidence_ref: { type: 'string', required: true, description: 'Opaque reference returned by sag_search.' },
|
|
17
|
+
offset: { type: 'integer', description: 'Unicode character offset, starting at 0.' },
|
|
18
|
+
max_chars: { type: 'integer', description: 'Maximum characters to return.' },
|
|
19
|
+
include_events: { type: 'boolean', description: 'Include bounded related event summaries.' },
|
|
20
|
+
},
|
|
21
|
+
output: { schema: READ_OUTPUT_SCHEMA, render: (_args, value) => [{ type: 'text', text: renderRead(value) }] },
|
|
22
|
+
timeoutMs: config.requestTimeoutMs, isConcurrencySafe: () => true,
|
|
23
|
+
async execute(args, exec) {
|
|
24
|
+
const evidenceRef = sagEvidenceRef(args.evidence_ref);
|
|
25
|
+
const offset = args.offset ?? 0;
|
|
26
|
+
const requestedMaxChars = args.max_chars ?? config.maxReadChars;
|
|
27
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
28
|
+
throw new Error('sag_read offset must not be negative');
|
|
29
|
+
if (!Number.isInteger(requestedMaxChars) || requestedMaxChars < 1)
|
|
30
|
+
throw new Error('sag_read max_chars must be positive');
|
|
31
|
+
return readResult(await client.request('read', {
|
|
32
|
+
evidenceRef, offset, maxChars: Math.min(requestedMaxChars, config.maxReadChars), includeEvents: args.include_events ?? false,
|
|
33
|
+
}, exec.signal));
|
|
34
|
+
},
|
|
35
|
+
presentCall: args => presentReadCall(args),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function createLocalReadTool(manager, config) {
|
|
39
|
+
return defineTool({
|
|
40
|
+
name: 'sag_read', description: 'Read a bounded Unicode page from one local evidence_ref returned by sag_search.',
|
|
41
|
+
parameters: {
|
|
42
|
+
evidence_ref: { type: 'string', required: true, description: 'Opaque reference returned by sag_search.' },
|
|
43
|
+
offset: { type: 'integer', description: 'Unicode character offset, starting at 0.' },
|
|
44
|
+
max_chars: { type: 'integer', description: 'Maximum characters, capped by plugin configuration.' },
|
|
45
|
+
},
|
|
46
|
+
output: { schema: READ_OUTPUT_SCHEMA, render: (_args, value) => [{ type: 'text', text: renderRead(value) }] },
|
|
47
|
+
timeoutMs: config.requestTimeoutMs, isConcurrencySafe: () => true,
|
|
48
|
+
execute(args, exec) {
|
|
49
|
+
return localOperation(manager, exec.signal, async (connection) => {
|
|
50
|
+
requireToolCapability(connection, 'sag_read');
|
|
51
|
+
const offset = args.offset ?? 0;
|
|
52
|
+
const requestedMaxChars = args.max_chars ?? config.maxReadChars;
|
|
53
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
54
|
+
localInputError('sag_read offset must not be negative');
|
|
55
|
+
if (!Number.isInteger(requestedMaxChars) || requestedMaxChars < 1)
|
|
56
|
+
localInputError('sag_read max_chars must be positive');
|
|
57
|
+
let evidenceRef;
|
|
58
|
+
try {
|
|
59
|
+
decodeLocalEvidenceRef(args.evidence_ref);
|
|
60
|
+
evidenceRef = args.evidence_ref;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
localInputError(error instanceof Error ? error.message : 'dsh-sag: invalid local evidence reference');
|
|
64
|
+
}
|
|
65
|
+
const chunk = await connection.gateway.read({ evidenceRef }, exec.signal);
|
|
66
|
+
const characters = Array.from(chunk.content);
|
|
67
|
+
const boundedMaxChars = Math.min(requestedMaxChars, config.maxReadChars);
|
|
68
|
+
const content = characters.slice(offset, offset + boundedMaxChars).join('');
|
|
69
|
+
const consumed = Array.from(content).length;
|
|
70
|
+
const nextOffset = offset + consumed;
|
|
71
|
+
return { title: `${chunk.sourceId}/${chunk.chunkId}`, content, offset,
|
|
72
|
+
...(nextOffset < characters.length && consumed > 0 ? { nextOffset } : {}), totalChars: characters.length };
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
presentCall: args => presentReadCall(args),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/** Build the mode-specific bounded evidence reader. */
|
|
79
|
+
export function createReadTool(client, config) {
|
|
80
|
+
return config.mode === 'embedded'
|
|
81
|
+
? createEmbeddedReadTool(client, config)
|
|
82
|
+
: createLocalReadTool(client, config);
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=read.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SagConnectionManager } from '../connection/manager.js';
|
|
2
|
+
import type { ResolvedConfig } from '../config.js';
|
|
3
|
+
import type { SagRuntimeClient } from '../runtime/client.js';
|
|
4
|
+
/** Build the mode-specific model-facing SAG search tool. */
|
|
5
|
+
export declare function createSearchTool(client: SagRuntimeClient | Pick<SagConnectionManager, 'ensureConnected'>, config: ResolvedConfig): import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
6
|
+
//# sourceMappingURL=search.d.ts.map
|