@gaunt-sloth/agent 0.1.8 → 2.0.0-alpha.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/README.md +14 -5
- package/cli-acp.js +35 -0
- package/cli.js +35 -0
- package/dist/builtInToolsConfig.d.ts +26 -0
- package/dist/builtInToolsConfig.js +148 -0
- package/dist/builtInToolsConfig.js.map +1 -0
- package/dist/core/GthDeepAgent.d.ts +76 -0
- package/dist/core/GthDeepAgent.js +368 -0
- package/dist/core/GthDeepAgent.js.map +1 -0
- package/dist/core/debugCapture.d.ts +51 -0
- package/dist/core/debugCapture.js +2 -0
- package/dist/core/debugCapture.js.map +1 -0
- package/dist/core/deepAgentPermissions.d.ts +55 -0
- package/dist/core/deepAgentPermissions.js +120 -0
- package/dist/core/deepAgentPermissions.js.map +1 -0
- package/dist/core/gthAcpServer.d.ts +27 -0
- package/dist/core/gthAcpServer.js +71 -0
- package/dist/core/gthAcpServer.js.map +1 -0
- package/dist/core/gthDeepAgentFactory.d.ts +8 -0
- package/dist/core/gthDeepAgentFactory.js +9 -0
- package/dist/core/gthDeepAgentFactory.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/OAuthClientProviderImpl.d.ts +37 -0
- package/dist/mcp/OAuthClientProviderImpl.js +223 -0
- package/dist/mcp/OAuthClientProviderImpl.js.map +1 -0
- package/dist/middleware/binaryContentInjectionMiddleware.d.ts +22 -0
- package/dist/middleware/binaryContentInjectionMiddleware.js +125 -0
- package/dist/middleware/binaryContentInjectionMiddleware.js.map +1 -0
- package/dist/middleware/registry.d.ts +34 -0
- package/dist/middleware/registry.js +124 -0
- package/dist/middleware/registry.js.map +1 -0
- package/dist/middleware/types.d.ts +89 -0
- package/dist/middleware/types.js +9 -0
- package/dist/middleware/types.js.map +1 -0
- package/dist/modules/a2a/A2AClientWrapper.d.ts +22 -0
- package/dist/modules/a2a/A2AClientWrapper.js +63 -0
- package/dist/modules/a2a/A2AClientWrapper.js.map +1 -0
- package/dist/modules/acpModule.d.ts +37 -0
- package/dist/modules/acpModule.js +67 -0
- package/dist/modules/acpModule.js.map +1 -0
- package/dist/modules/apiAgUiModule.d.ts +2 -0
- package/dist/modules/apiAgUiModule.js +417 -0
- package/dist/modules/apiAgUiModule.js.map +1 -0
- package/dist/modules/interactiveSessionModule.d.ts +9 -0
- package/dist/modules/interactiveSessionModule.js +99 -0
- package/dist/modules/interactiveSessionModule.js.map +1 -0
- package/dist/resolvers.d.ts +18 -0
- package/dist/resolvers.js +140 -0
- package/dist/resolvers.js.map +1 -0
- package/dist/tools/A2AAgentTool.d.ts +24 -0
- package/dist/tools/A2AAgentTool.js +37 -0
- package/dist/tools/A2AAgentTool.js.map +1 -0
- package/dist/tools/GthCustomToolkit.d.ts +41 -0
- package/dist/tools/GthCustomToolkit.js +289 -0
- package/dist/tools/GthCustomToolkit.js.map +1 -0
- package/dist/tools/GthDevToolkit.d.ts +24 -0
- package/dist/tools/GthDevToolkit.js +189 -0
- package/dist/tools/GthDevToolkit.js.map +1 -0
- package/dist/tools/GthFileSystemToolkit.d.ts +36 -0
- package/dist/tools/GthFileSystemToolkit.js +775 -0
- package/dist/tools/GthFileSystemToolkit.js.map +1 -0
- package/dist/tools/binaryUtils.d.ts +13 -0
- package/dist/tools/binaryUtils.js +55 -0
- package/dist/tools/binaryUtils.js.map +1 -0
- package/dist/tools/gthStatusUpdateTool.d.ts +2 -0
- package/dist/tools/gthStatusUpdateTool.js +15 -0
- package/dist/tools/gthStatusUpdateTool.js.map +1 -0
- package/dist/tools/gthWebFetchTool.d.ts +2 -0
- package/dist/tools/gthWebFetchTool.js +47 -0
- package/dist/tools/gthWebFetchTool.js.map +1 -0
- package/dist/tools/showA2UISurfaceTool.d.ts +9 -0
- package/dist/tools/showA2UISurfaceTool.js +25 -0
- package/dist/tools/showA2UISurfaceTool.js.map +1 -0
- package/dist/utils/mcpUtils.d.ts +13 -0
- package/dist/utils/mcpUtils.js +225 -0
- package/dist/utils/mcpUtils.js.map +1 -0
- package/package.json +49 -10
- package/index.js +0 -6
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* Thin wrapper over `deepagents-acp`'s `DeepAgentsServer` that fixes how its filesystem backend is
|
|
4
|
+
* rooted, since 0.1.12 gets both the location and the path semantics wrong for an IDE-hosted agent.
|
|
5
|
+
*
|
|
6
|
+
* 1. **Per-session root.** deepagents-acp roots its backend once at `workspaceRoot ?? process.cwd()`
|
|
7
|
+
* and IGNORES the `cwd` carried by every ACP `session/new` request. An ACP host (Zed, JetBrains)
|
|
8
|
+
* spawns one long-lived agent subprocess and passes the real project root per session, so we
|
|
9
|
+
* re-root the backend to `session/new.cwd`.
|
|
10
|
+
* 2. **Virtual-fs semantics.** deepagents-acp builds its backend WITHOUT `virtualMode`, so `/`
|
|
11
|
+
* resolves to the OS root and `ls /` lists the whole machine — but deepagents' filesystem prompt
|
|
12
|
+
* tells the model `/` is the workspace root (the convention the local runner uses via
|
|
13
|
+
* `FilesystemBackend({ virtualMode: true })`). We switch the backend into virtual-fs mode so
|
|
14
|
+
* `'/'`-rooted paths are workspace-relative.
|
|
15
|
+
*
|
|
16
|
+
* The server's connection handler dispatches `this.handleNewSession(params, conn)` dynamically, so
|
|
17
|
+
* we patch that instance method before `start()`. Both fixes reach into deepagents-acp internals and
|
|
18
|
+
* are guarded so a shape change degrades to a no-op rather than throwing; if a future deepagents-acp
|
|
19
|
+
* honors session cwd / virtual mode itself, this becomes a harmless re-assert.
|
|
20
|
+
*/
|
|
21
|
+
import { DeepAgentsServer, type DeepAgentsServerOptions } from 'deepagents-acp';
|
|
22
|
+
/**
|
|
23
|
+
* Construct and start a deepagents-acp server that roots its filesystem backend at each ACP
|
|
24
|
+
* session's `cwd` and runs it in virtual-fs mode (so `/` is the workspace, not the OS root).
|
|
25
|
+
* Resolves once the stdio transport is listening.
|
|
26
|
+
*/
|
|
27
|
+
export declare function startGthAcpServer(options: DeepAgentsServerOptions): Promise<DeepAgentsServer>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* Thin wrapper over `deepagents-acp`'s `DeepAgentsServer` that fixes how its filesystem backend is
|
|
4
|
+
* rooted, since 0.1.12 gets both the location and the path semantics wrong for an IDE-hosted agent.
|
|
5
|
+
*
|
|
6
|
+
* 1. **Per-session root.** deepagents-acp roots its backend once at `workspaceRoot ?? process.cwd()`
|
|
7
|
+
* and IGNORES the `cwd` carried by every ACP `session/new` request. An ACP host (Zed, JetBrains)
|
|
8
|
+
* spawns one long-lived agent subprocess and passes the real project root per session, so we
|
|
9
|
+
* re-root the backend to `session/new.cwd`.
|
|
10
|
+
* 2. **Virtual-fs semantics.** deepagents-acp builds its backend WITHOUT `virtualMode`, so `/`
|
|
11
|
+
* resolves to the OS root and `ls /` lists the whole machine — but deepagents' filesystem prompt
|
|
12
|
+
* tells the model `/` is the workspace root (the convention the local runner uses via
|
|
13
|
+
* `FilesystemBackend({ virtualMode: true })`). We switch the backend into virtual-fs mode so
|
|
14
|
+
* `'/'`-rooted paths are workspace-relative.
|
|
15
|
+
*
|
|
16
|
+
* The server's connection handler dispatches `this.handleNewSession(params, conn)` dynamically, so
|
|
17
|
+
* we patch that instance method before `start()`. Both fixes reach into deepagents-acp internals and
|
|
18
|
+
* are guarded so a shape change degrades to a no-op rather than throwing; if a future deepagents-acp
|
|
19
|
+
* honors session cwd / virtual mode itself, this becomes a harmless re-assert.
|
|
20
|
+
*/
|
|
21
|
+
import { DeepAgentsServer } from 'deepagents-acp';
|
|
22
|
+
import { resolve } from 'node:path';
|
|
23
|
+
import { debugLog } from '@gaunt-sloth/core/utils/debugUtils.js';
|
|
24
|
+
/**
|
|
25
|
+
* Put an ACP filesystem backend into deepagents' virtual-fs mode so `'/'`-rooted paths are
|
|
26
|
+
* workspace-relative (clamped under `cwd`), matching deepagents' prompt and the local runner.
|
|
27
|
+
*
|
|
28
|
+
* `virtualMode` covers the methods that route through `resolvePath` (ls) and the inline checks
|
|
29
|
+
* (glob/grep). The ACP read/write proxy uses its own `resolveAbsPath`, which ignores `virtualMode`,
|
|
30
|
+
* so override that too so proxied reads/writes hit the workspace, not the OS root.
|
|
31
|
+
*/
|
|
32
|
+
function configureVirtualFs(backend) {
|
|
33
|
+
backend.virtualMode = true;
|
|
34
|
+
backend.resolveAbsPath = (filePath) => resolve(backend.cwd ?? process.cwd(), String(filePath).replace(/^\/+/, ''));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Construct and start a deepagents-acp server that roots its filesystem backend at each ACP
|
|
38
|
+
* session's `cwd` and runs it in virtual-fs mode (so `/` is the workspace, not the OS root).
|
|
39
|
+
* Resolves once the stdio transport is listening.
|
|
40
|
+
*/
|
|
41
|
+
export async function startGthAcpServer(options) {
|
|
42
|
+
const server = new DeepAgentsServer(options);
|
|
43
|
+
const internals = server;
|
|
44
|
+
const original = internals.handleNewSession;
|
|
45
|
+
if (typeof original === 'function') {
|
|
46
|
+
const bound = original.bind(server);
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
|
+
internals.handleNewSession = async (params, conn) => {
|
|
49
|
+
const result = await bound(params, conn);
|
|
50
|
+
const backends = internals.acpBackends;
|
|
51
|
+
const cwd = typeof params?.cwd === 'string' && params.cwd.length > 0 ? resolve(params.cwd) : undefined;
|
|
52
|
+
if (backends && backends.size > 0) {
|
|
53
|
+
for (const backend of backends.values()) {
|
|
54
|
+
if (!backend || typeof backend !== 'object')
|
|
55
|
+
continue;
|
|
56
|
+
if (cwd)
|
|
57
|
+
backend.cwd = cwd;
|
|
58
|
+
configureVirtualFs(backend);
|
|
59
|
+
}
|
|
60
|
+
debugLog(`ACP session: virtual fs, root ${cwd ?? '[startup workspace]'}`);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
debugLog('deepagents-acp handleNewSession not found to patch; session fs rooting disabled');
|
|
67
|
+
}
|
|
68
|
+
await server.start();
|
|
69
|
+
return server;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=gthAcpServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gthAcpServer.js","sourceRoot":"","sources":["../../src/core/gthAcpServer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,gBAAgB,EAAgC,MAAM,gBAAgB,CAAC;AAChF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,uCAAuC,CAAC;AAiBjE;;;;;;;GAOG;AACH,SAAS,kBAAkB,CAAC,OAA0B;IACpD,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAC3B,OAAO,CAAC,cAAc,GAAG,CAAC,QAAgB,EAAU,EAAE,CACpD,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;AAChF,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAgC;IAEhC,MAAM,MAAM,GAAG,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,MAAuC,CAAC;IAE1D,MAAM,QAAQ,GAAG,SAAS,CAAC,gBAAgB,CAAC;IAC5C,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpC,8DAA8D;QAC9D,SAAS,CAAC,gBAAgB,GAAG,KAAK,EAAE,MAAW,EAAE,IAAS,EAAE,EAAE;YAC5D,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACzC,MAAM,QAAQ,GAAG,SAAS,CAAC,WAAW,CAAC;YACvC,MAAM,GAAG,GACP,OAAO,MAAM,EAAE,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC7F,IAAI,QAAQ,IAAI,QAAQ,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBAClC,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;oBACxC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;wBAAE,SAAS;oBACtD,IAAI,GAAG;wBAAE,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC;oBAC3B,kBAAkB,CAAC,OAAO,CAAC,CAAC;gBAC9B,CAAC;gBACD,QAAQ,CAAC,iCAAiC,GAAG,IAAI,qBAAqB,EAAE,CAAC,CAAC;YAC5E,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,iFAAiF,CAAC,CAAC;IAC9F,CAAC;IAED,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACrB,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { GthAgentFactory } from '@gaunt-sloth/core/core/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A {@link GthAgentFactory} that produces a {@link GthDeepAgent}. Pass this to
|
|
4
|
+
* `GthAgentRunner` (its optional 3rd ctor arg) so the same runner drives a
|
|
5
|
+
* `createDeepAgent` graph instead of the lean default — without `core` ever
|
|
6
|
+
* importing `deepagents`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const gthDeepAgentFactory: GthAgentFactory;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { GthDeepAgent } from '#src/core/GthDeepAgent.js';
|
|
2
|
+
/**
|
|
3
|
+
* A {@link GthAgentFactory} that produces a {@link GthDeepAgent}. Pass this to
|
|
4
|
+
* `GthAgentRunner` (its optional 3rd ctor arg) so the same runner drives a
|
|
5
|
+
* `createDeepAgent` graph instead of the lean default — without `core` ever
|
|
6
|
+
* importing `deepagents`.
|
|
7
|
+
*/
|
|
8
|
+
export const gthDeepAgentFactory = (statusUpdate, resolvers) => new GthDeepAgent(statusUpdate, resolvers);
|
|
9
|
+
//# sourceMappingURL=gthDeepAgentFactory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gthDeepAgentFactory.js","sourceRoot":"","sources":["../../src/core/gthDeepAgentFactory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAoB,CAAC,YAAY,EAAE,SAAS,EAAE,EAAE,CAC9E,IAAI,YAAY,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from '#src/modules/apiAgUiModule.js';
|
|
2
|
+
export * from '#src/modules/acpModule.js';
|
|
3
|
+
export * from '#src/modules/interactiveSessionModule.js';
|
|
4
|
+
export * from '#src/modules/a2a/A2AClientWrapper.js';
|
|
5
|
+
export * from '#src/tools/A2AAgentTool.js';
|
|
6
|
+
export * from '#src/mcp/OAuthClientProviderImpl.js';
|
|
7
|
+
export * from '#src/utils/mcpUtils.js';
|
|
8
|
+
export { createResolvers } from '#src/resolvers.js';
|
|
9
|
+
export * from '#src/builtInToolsConfig.js';
|
|
10
|
+
export { resolveMiddleware } from '#src/middleware/registry.js';
|
|
11
|
+
export { GthDeepAgent, type GthDeepAgentParams } from '#src/core/GthDeepAgent.js';
|
|
12
|
+
export * from '#src/core/deepAgentPermissions.js';
|
|
13
|
+
export { gthDeepAgentFactory } from '#src/core/gthDeepAgentFactory.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from '#src/modules/apiAgUiModule.js';
|
|
2
|
+
export * from '#src/modules/acpModule.js';
|
|
3
|
+
export * from '#src/modules/interactiveSessionModule.js';
|
|
4
|
+
export * from '#src/modules/a2a/A2AClientWrapper.js';
|
|
5
|
+
export * from '#src/tools/A2AAgentTool.js';
|
|
6
|
+
export * from '#src/mcp/OAuthClientProviderImpl.js';
|
|
7
|
+
export * from '#src/utils/mcpUtils.js';
|
|
8
|
+
export { createResolvers } from '#src/resolvers.js';
|
|
9
|
+
// Tools + middleware (formerly @gaunt-sloth/tools)
|
|
10
|
+
export * from '#src/builtInToolsConfig.js';
|
|
11
|
+
export { resolveMiddleware } from '#src/middleware/registry.js';
|
|
12
|
+
// Deep agent (createDeepAgent / deepagents) + the GthConfig→permission mapping
|
|
13
|
+
export { GthDeepAgent } from '#src/core/GthDeepAgent.js';
|
|
14
|
+
export * from '#src/core/deepAgentPermissions.js';
|
|
15
|
+
export { gthDeepAgentFactory } from '#src/core/gthDeepAgentFactory.js';
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC;AAC9C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0CAA0C,CAAC;AACzD,cAAc,sCAAsC,CAAC;AACrD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qCAAqC,CAAC;AACpD,cAAc,wBAAwB,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,mDAAmD;AACnD,cAAc,4BAA4B,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAEhE,+EAA+E;AAC/E,OAAO,EAAE,YAAY,EAA2B,MAAM,2BAA2B,CAAC;AAClF,cAAc,mCAAmC,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
2
|
+
import type { OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
3
|
+
import { StreamableHTTPConnection } from '@langchain/mcp-adapters';
|
|
4
|
+
import http from 'http';
|
|
5
|
+
interface OAuthClientProviderConfig {
|
|
6
|
+
redirectUrl: string;
|
|
7
|
+
serverUrl: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Please note most of these "unused" methods are part of {@link OAuthClientProvider}
|
|
11
|
+
*/
|
|
12
|
+
export declare class OAuthClientProviderImpl implements OAuthClientProvider {
|
|
13
|
+
private config;
|
|
14
|
+
private innerState;
|
|
15
|
+
private storagePath;
|
|
16
|
+
private storageCache?;
|
|
17
|
+
constructor(config: OAuthClientProviderConfig);
|
|
18
|
+
private loadStorageData;
|
|
19
|
+
private saveStorageData;
|
|
20
|
+
state(): string | Promise<string>;
|
|
21
|
+
get redirectUrl(): string;
|
|
22
|
+
get clientMetadata(): OAuthClientMetadata;
|
|
23
|
+
saveClientInformation(clientInformation: OAuthClientInformationFull): Promise<void>;
|
|
24
|
+
clientInformation(): Promise<OAuthClientInformationFull | undefined>;
|
|
25
|
+
saveTokens(tokens: OAuthTokens): Promise<void>;
|
|
26
|
+
tokens(): OAuthTokens | undefined;
|
|
27
|
+
saveCodeVerifier(codeVerifier: string): Promise<void>;
|
|
28
|
+
codeVerifier(): Promise<string>;
|
|
29
|
+
redirectToAuthorization(authUrl: URL): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export declare function createAuthProviderAndAuthenticate(mcpServer: StreamableHTTPConnection): Promise<OAuthClientProviderImpl>;
|
|
32
|
+
export declare function createOAuthRedirectServer(path: string, portParam?: number): Promise<{
|
|
33
|
+
port: number;
|
|
34
|
+
server: http.Server;
|
|
35
|
+
codePromise: Promise<string>;
|
|
36
|
+
}>;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { auth } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import * as crypto from 'node:crypto';
|
|
4
|
+
import { platform } from 'node:os';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { displayInfo } from '@gaunt-sloth/core/utils/consoleUtils.js';
|
|
7
|
+
import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'node:fs';
|
|
8
|
+
import { getOAuthStoragePath } from '@gaunt-sloth/core/utils/globalConfigUtils.js';
|
|
9
|
+
/**
|
|
10
|
+
* Please note most of these "unused" methods are part of {@link OAuthClientProvider}
|
|
11
|
+
*/
|
|
12
|
+
export class OAuthClientProviderImpl {
|
|
13
|
+
config;
|
|
14
|
+
innerState;
|
|
15
|
+
storagePath;
|
|
16
|
+
storageCache;
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.innerState = crypto.randomUUID();
|
|
20
|
+
if (!this.config.redirectUrl) {
|
|
21
|
+
throw new Error('No redirect URL provided');
|
|
22
|
+
}
|
|
23
|
+
if (!this.config.serverUrl) {
|
|
24
|
+
throw new Error('No server URL provided');
|
|
25
|
+
}
|
|
26
|
+
this.storagePath = getOAuthStoragePath(this.config.serverUrl);
|
|
27
|
+
}
|
|
28
|
+
loadStorageData() {
|
|
29
|
+
// Return cached data if available
|
|
30
|
+
if (this.storageCache !== undefined) {
|
|
31
|
+
return this.storageCache;
|
|
32
|
+
}
|
|
33
|
+
// Read from file if cache is empty
|
|
34
|
+
if (existsSync(this.storagePath)) {
|
|
35
|
+
try {
|
|
36
|
+
const data = readFileSync(this.storagePath, 'utf-8');
|
|
37
|
+
const parsedData = JSON.parse(data);
|
|
38
|
+
// Wipe data if the in-memory cache is empty and file info is missing tokens
|
|
39
|
+
if (parsedData.clientInformation && !parsedData.tokens) {
|
|
40
|
+
try {
|
|
41
|
+
unlinkSync(this.storagePath);
|
|
42
|
+
displayInfo('Deleted OAuth storage file (missing tokens)');
|
|
43
|
+
this.storageCache = {};
|
|
44
|
+
return this.storageCache;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
displayInfo('Failed to delete OAuth storage file: ' + error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Cache the loaded data
|
|
51
|
+
this.storageCache = parsedData;
|
|
52
|
+
return parsedData;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
displayInfo('Failed to load OAuth storage data: ' + error);
|
|
56
|
+
this.storageCache = {};
|
|
57
|
+
return this.storageCache;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// No file exists, cache empty object
|
|
61
|
+
this.storageCache = {};
|
|
62
|
+
return this.storageCache;
|
|
63
|
+
}
|
|
64
|
+
saveStorageData(data) {
|
|
65
|
+
try {
|
|
66
|
+
writeFileSync(this.storagePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
67
|
+
// Update the cache when saving
|
|
68
|
+
this.storageCache = data;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error('Failed to save OAuth storage data:', error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
state() {
|
|
75
|
+
return this.innerState;
|
|
76
|
+
}
|
|
77
|
+
// noinspection JSUnusedGlobalSymbols
|
|
78
|
+
get redirectUrl() {
|
|
79
|
+
return this.config.redirectUrl;
|
|
80
|
+
}
|
|
81
|
+
// noinspection JSUnusedGlobalSymbols
|
|
82
|
+
get clientMetadata() {
|
|
83
|
+
return {
|
|
84
|
+
redirect_uris: [this.config.redirectUrl],
|
|
85
|
+
client_name: 'Gaunt Sloth Assistant',
|
|
86
|
+
client_uri: 'https://gaunt-sloth-assistant.github.io/',
|
|
87
|
+
software_id: '1dd38b83-946b-4631-8855-66ee467bfd68',
|
|
88
|
+
scope: 'mcp:read mcp:write',
|
|
89
|
+
token_endpoint_auth_method: 'none',
|
|
90
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
91
|
+
response_types: ['code'],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// noinspection JSUnusedGlobalSymbols
|
|
95
|
+
saveClientInformation(clientInformation) {
|
|
96
|
+
const data = this.loadStorageData();
|
|
97
|
+
data.clientInformation = clientInformation;
|
|
98
|
+
this.saveStorageData(data);
|
|
99
|
+
return Promise.resolve();
|
|
100
|
+
}
|
|
101
|
+
// noinspection JSUnusedGlobalSymbols
|
|
102
|
+
async clientInformation() {
|
|
103
|
+
const data = this.loadStorageData();
|
|
104
|
+
return Promise.resolve(data.clientInformation);
|
|
105
|
+
}
|
|
106
|
+
// noinspection JSUnusedGlobalSymbols
|
|
107
|
+
async saveTokens(tokens) {
|
|
108
|
+
const data = this.loadStorageData();
|
|
109
|
+
data.tokens = tokens;
|
|
110
|
+
this.saveStorageData(data);
|
|
111
|
+
}
|
|
112
|
+
// noinspection JSUnusedGlobalSymbols
|
|
113
|
+
tokens() {
|
|
114
|
+
const data = this.loadStorageData();
|
|
115
|
+
return data.tokens;
|
|
116
|
+
}
|
|
117
|
+
// noinspection JSUnusedGlobalSymbols
|
|
118
|
+
saveCodeVerifier(codeVerifier) {
|
|
119
|
+
const data = this.loadStorageData();
|
|
120
|
+
data.codeVerifier = codeVerifier;
|
|
121
|
+
this.saveStorageData(data);
|
|
122
|
+
return Promise.resolve();
|
|
123
|
+
}
|
|
124
|
+
// noinspection JSUnusedGlobalSymbols
|
|
125
|
+
codeVerifier() {
|
|
126
|
+
const data = this.loadStorageData();
|
|
127
|
+
if (!data.codeVerifier) {
|
|
128
|
+
throw new Error('No code verifier stored');
|
|
129
|
+
}
|
|
130
|
+
return Promise.resolve(data.codeVerifier);
|
|
131
|
+
}
|
|
132
|
+
// noinspection JSUnusedGlobalSymbols
|
|
133
|
+
async redirectToAuthorization(authUrl) {
|
|
134
|
+
displayInfo('Auth url: ' + authUrl.toString());
|
|
135
|
+
try {
|
|
136
|
+
const url = authUrl.toString();
|
|
137
|
+
displayInfo('Trying to open browser');
|
|
138
|
+
// Handle different platforms
|
|
139
|
+
const platformName = platform();
|
|
140
|
+
if (platformName === 'win32') {
|
|
141
|
+
// Windows - 'start' is a cmd.exe built-in, so we must invoke it via cmd
|
|
142
|
+
spawnSync('cmd', ['/c', 'start', '', url], { stdio: 'ignore' });
|
|
143
|
+
}
|
|
144
|
+
else if (platformName === 'darwin') {
|
|
145
|
+
// macOS
|
|
146
|
+
spawnSync('open', [url], { stdio: 'ignore' });
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// Linux and others
|
|
150
|
+
spawnSync('xdg-open', [url], { stdio: 'ignore' });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
displayInfo(`Failed to open browser: ${error}`);
|
|
155
|
+
displayInfo(`Please open ${authUrl.toString()} in your browser`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export async function createAuthProviderAndAuthenticate(mcpServer) {
|
|
160
|
+
const { port, server, codePromise } = await createOAuthRedirectServer('/oauth/callback');
|
|
161
|
+
const authProvider = new OAuthClientProviderImpl({
|
|
162
|
+
redirectUrl: `http://localhost:${port}/oauth/callback`,
|
|
163
|
+
serverUrl: mcpServer.url,
|
|
164
|
+
});
|
|
165
|
+
const outcome = await auth(authProvider, { serverUrl: mcpServer.url });
|
|
166
|
+
if (outcome == 'REDIRECT') {
|
|
167
|
+
const authorizationCode = await codePromise;
|
|
168
|
+
await auth(authProvider, { serverUrl: mcpServer.url, authorizationCode });
|
|
169
|
+
}
|
|
170
|
+
else if (outcome == 'AUTHORIZED') {
|
|
171
|
+
try {
|
|
172
|
+
server.close();
|
|
173
|
+
}
|
|
174
|
+
catch { }
|
|
175
|
+
displayInfo('Authorized');
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
throw new Error(`Unexpected Auth outcome: ${outcome}`);
|
|
179
|
+
}
|
|
180
|
+
return authProvider;
|
|
181
|
+
}
|
|
182
|
+
export function createOAuthRedirectServer(path, portParam = 0) {
|
|
183
|
+
const redirectApp = express();
|
|
184
|
+
return new Promise((resolve, reject) => {
|
|
185
|
+
const codePromise = new Promise((resolveCode, rejectCode) => {
|
|
186
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
187
|
+
redirectApp.get(path, (req, res) => {
|
|
188
|
+
const code = req.query.code;
|
|
189
|
+
if (!code) {
|
|
190
|
+
res.status(400).send('Error: No auth code received');
|
|
191
|
+
rejectCode('Error: No auth code received');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
res.send(`<div style="height: 80vh;text-align: center;display: flex;justify-content: center;align-items: center;">
|
|
195
|
+
<div>
|
|
196
|
+
<h1>Auth successful!</h1>
|
|
197
|
+
You may close this window and return to the Gaunt Sloth.
|
|
198
|
+
<div>
|
|
199
|
+
<img src="data:image/bmp;base64,Qk02AwAAAAAAADYAAAAoAAAAEAAAABAAAAABABgAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAnD98ZBtDTQ8sey9YOQobRA8nZx9OdyxahzZrMwERdCtShjZqLQUVLwgZKwUWKQYXsFCRq06KciJPnEh/n0GEmjuClDl9lzl/uFGglz56dChOm0h0SBInLgQVKwgZKgUVgzZchDRfqEeLsUmavFWjvV6joEGFizFvsU+Yw2CowWCiu1qdbCRNKgEQMAofMAcebSRIgC9bt1KdqEqNnD6BokGJqEaOvVmjqEWMu12ftFeYwGCjpUOMUxc8KQIRLAQWhjVmo0qGs1CYqEmOqEmPmjyArk2UrEyQr02TsU2WwV6pvF2eo0CKlDl+OgsiJwQSijZthS9qnDuAo0GFqESMmz2BijZtpUOLnT2CuVicyWmtsVCUjDh0p0KQbiVYJAQSkzx3kzZ7iS5rv2mn3Y3Gq0+Pgi1laCFOfyxmmTx/slCVmT55eihemzyGjTd6NwgfgSxokzl9qkuY4pLR+Lvg1H3EjTRzZB1IjTV3cCNWmjmAgC1diCxpjC1ulDd8RhAtdihbljl/vmK1v2WuuWGgtVWdjDVwgS5ldCVZcydckjl5kzh3ynGwxHOooT6DSA8sQQwkpUeTyGrBlTp8eCNZkzh1rEeSmDx9ZB9JcyNYkzh2tlek5pjT8q3bqU2PPQokJgEPhTVxy2nBkDd6kDV2qEaOwGOvvWGxuViqoUSNlDd6nj6FvmKq13zKnkiJMgUZKwUWPg4poUaOkzp+kTh7nkCExG60033Jym7C03zKsVOdiC9tjjR10W7IeThrKAAOMAUYJgUULgUZdCZdqkqYu1ersk+fvFyww2q4z3fDqUmXhy5ulTp/yWi/Txo6JgEPLgYZLQUXKQUVJwkaSxExq1GTxWW3t1SpsU+gv1mvnUOLjTJ3izh3dTRkJwEQKgQWLQYaLAgcLQUYLQYZLAERezZaiTtmXyFJgjp0ijt1iTlqOAsjNQ0pKQASJwUVKQYYLgUZLAQZLAgcLgUZMAUYZyVRfCthPQoiIQAJOgcgYyBLKAIRLAUZNwsoMQcfKAQV"
|
|
200
|
+
width="16" height="16">
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>`);
|
|
204
|
+
resolveCode(code);
|
|
205
|
+
if (server) {
|
|
206
|
+
displayInfo('Cleaning auth redirect server...');
|
|
207
|
+
server.close();
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
const server = redirectApp.listen(portParam, () => {
|
|
212
|
+
const addressInfo = server.address();
|
|
213
|
+
const port = addressInfo.port;
|
|
214
|
+
displayInfo(`OAuth callback server listening at ${port}`);
|
|
215
|
+
resolve({ port, server, codePromise });
|
|
216
|
+
});
|
|
217
|
+
server.on('error', (err) => {
|
|
218
|
+
// If the server fails to start, reject the outer promise.
|
|
219
|
+
reject(err);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=OAuthClientProviderImpl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OAuthClientProviderImpl.js","sourceRoot":"","sources":["../../src/mcp/OAuthClientProviderImpl.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* Middleware to inject binary content (images, PDFs, audio) as HumanMessage.
|
|
4
|
+
*
|
|
5
|
+
* The gth_read_binary tool returns binary data as a special string format:
|
|
6
|
+
* gth_read_binary;type:${type};path:${encodedPath};data:${media_type};base64,${data}
|
|
7
|
+
* where path is URL-encoded to handle special characters like semicolons.
|
|
8
|
+
*
|
|
9
|
+
* This middleware:
|
|
10
|
+
* 1. Detects the gth_read_binary tool calls
|
|
11
|
+
* 2. Parses the special string format from ToolMessage content
|
|
12
|
+
* 3. Injects HumanMessage with binary content blocks before next model call
|
|
13
|
+
*
|
|
14
|
+
* This works around LangChain's limitation where ToolMessage doesn't properly
|
|
15
|
+
* support binary content blocks for most providers.
|
|
16
|
+
*/
|
|
17
|
+
import { type AgentMiddleware } from 'langchain';
|
|
18
|
+
import type { GthConfig } from '@gaunt-sloth/core/config.js';
|
|
19
|
+
export interface BinaryContentInjectionMiddlewareSettings {
|
|
20
|
+
name?: 'binary-content-injection';
|
|
21
|
+
}
|
|
22
|
+
export declare function createBinaryContentInjectionMiddleware(_settings: BinaryContentInjectionMiddlewareSettings, _gthConfig: GthConfig): Promise<AgentMiddleware>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* Middleware to inject binary content (images, PDFs, audio) as HumanMessage.
|
|
4
|
+
*
|
|
5
|
+
* The gth_read_binary tool returns binary data as a special string format:
|
|
6
|
+
* gth_read_binary;type:${type};path:${encodedPath};data:${media_type};base64,${data}
|
|
7
|
+
* where path is URL-encoded to handle special characters like semicolons.
|
|
8
|
+
*
|
|
9
|
+
* This middleware:
|
|
10
|
+
* 1. Detects the gth_read_binary tool calls
|
|
11
|
+
* 2. Parses the special string format from ToolMessage content
|
|
12
|
+
* 3. Injects HumanMessage with binary content blocks before next model call
|
|
13
|
+
*
|
|
14
|
+
* This works around LangChain's limitation where ToolMessage doesn't properly
|
|
15
|
+
* support binary content blocks for most providers.
|
|
16
|
+
*/
|
|
17
|
+
import { createMiddleware } from 'langchain';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { debugLog } from '@gaunt-sloth/core/utils/debugUtils.js';
|
|
20
|
+
import { ToolMessage, HumanMessage } from '@langchain/core/messages';
|
|
21
|
+
/**
|
|
22
|
+
* Parse the special binary format string returned by gth_read_binary tool.
|
|
23
|
+
* Format: gth_read_binary;type:${type};path:${encodedPath};data:${media_type};base64,${data}
|
|
24
|
+
* Path is URL-encoded to handle special characters.
|
|
25
|
+
*/
|
|
26
|
+
function parseBinaryContent(content) {
|
|
27
|
+
if (!content.startsWith('gth_read_binary;')) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const parts = content.split(';');
|
|
32
|
+
if (parts.length < 4) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const typeMatch = parts[1]?.match(/^type:(.+)$/);
|
|
36
|
+
const pathMatch = parts[2]?.match(/^path:(.+)$/);
|
|
37
|
+
const dataMatch = parts[3]?.match(/^data:(.+)$/);
|
|
38
|
+
const base64Match = content.match(/;base64,(.+)$/);
|
|
39
|
+
if (!typeMatch || !pathMatch || !dataMatch || !base64Match) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
// Decode the URL-encoded path
|
|
43
|
+
const decodedPath = decodeURIComponent(pathMatch[1]);
|
|
44
|
+
return {
|
|
45
|
+
formatType: typeMatch[1],
|
|
46
|
+
path: decodedPath,
|
|
47
|
+
media_type: dataMatch[1],
|
|
48
|
+
data: base64Match[1],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function createContentBlock(binaryData) {
|
|
56
|
+
const { formatType, media_type, data } = binaryData;
|
|
57
|
+
return {
|
|
58
|
+
type: formatType,
|
|
59
|
+
source_type: 'base64',
|
|
60
|
+
mime_type: media_type,
|
|
61
|
+
data,
|
|
62
|
+
metadata: {
|
|
63
|
+
filename: path.basename(binaryData.path),
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function getFormatLabel(formatType) {
|
|
68
|
+
const labels = {
|
|
69
|
+
image: 'image',
|
|
70
|
+
video: 'video',
|
|
71
|
+
audio: 'audio',
|
|
72
|
+
file: 'file',
|
|
73
|
+
};
|
|
74
|
+
return labels[formatType] || 'file';
|
|
75
|
+
}
|
|
76
|
+
export function createBinaryContentInjectionMiddleware(_settings, _gthConfig) {
|
|
77
|
+
debugLog('Creating binary content injection middleware');
|
|
78
|
+
return Promise.resolve(createMiddleware({
|
|
79
|
+
name: 'binary-content-injection',
|
|
80
|
+
// Before next model call, detect and inject HumanMessage with binary content
|
|
81
|
+
beforeModel: async (state) => {
|
|
82
|
+
const messages = state.messages || [];
|
|
83
|
+
// Find recent ToolMessages from gth_read_binary
|
|
84
|
+
const binaryMessages = [];
|
|
85
|
+
// Check last few messages (usually just need to check the most recent)
|
|
86
|
+
for (let i = messages.length - 1; i >= Math.max(0, messages.length - 5); i--) {
|
|
87
|
+
const msg = messages[i];
|
|
88
|
+
if (msg instanceof ToolMessage &&
|
|
89
|
+
msg.name === 'gth_read_binary' &&
|
|
90
|
+
typeof msg.content === 'string') {
|
|
91
|
+
const parsedContent = parseBinaryContent(msg.content);
|
|
92
|
+
if (parsedContent) {
|
|
93
|
+
binaryMessages.push({ message: msg, binaryData: parsedContent });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// If we found binary content, inject HumanMessage(s)
|
|
98
|
+
if (binaryMessages.length > 0) {
|
|
99
|
+
debugLog(`Injecting ${binaryMessages.length} HumanMessage(s) with binary content`);
|
|
100
|
+
const newMessages = [...messages];
|
|
101
|
+
for (const { binaryData } of binaryMessages) {
|
|
102
|
+
const formatLabel = getFormatLabel(binaryData.formatType);
|
|
103
|
+
const contentBlock = createContentBlock(binaryData);
|
|
104
|
+
const humanMessage = new HumanMessage({
|
|
105
|
+
content: [
|
|
106
|
+
{
|
|
107
|
+
type: 'text',
|
|
108
|
+
text: `Here is the ${formatLabel} content from the file:`,
|
|
109
|
+
},
|
|
110
|
+
contentBlock,
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
newMessages.push(humanMessage);
|
|
114
|
+
}
|
|
115
|
+
// Return the modified state with new messages
|
|
116
|
+
return {
|
|
117
|
+
messages: newMessages,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// No binary content, pass through
|
|
121
|
+
return undefined;
|
|
122
|
+
},
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=binaryContentInjectionMiddleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binaryContentInjectionMiddleware.js","sourceRoot":"","sources":["../../src/middleware/binaryContentInjectionMiddleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,gBAAgB,EAAwB,MAAM,WAAW,CAAC;AACnE,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,MAAM,uCAAuC,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAcrE;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,OAAe;IACzC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC5C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;QACjD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;QACjD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;QACjD,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;QAEnD,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,IAAI,CAAC,WAAW,EAAE,CAAC;YAC3D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,8BAA8B;QAC9B,MAAM,WAAW,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAErD,OAAO;YACL,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;YACxB,IAAI,EAAE,WAAW;YACjB,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;YACxB,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;SACrB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,UAA+B;IACzD,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC;IAEpD,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,QAAQ;QACrB,SAAS,EAAE,UAAU;QACrB,IAAI;QACJ,QAAQ,EAAE;YACR,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC;SACzC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,UAAkB;IACxC,MAAM,MAAM,GAA2B;QACrC,KAAK,EAAE,OAAO;QACd,KAAK,EAAE,OAAO;QACd,KAAK,EAAE,OAAO;QACd,IAAI,EAAE,MAAM;KACb,CAAC;IACF,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,sCAAsC,CACpD,SAAmD,EACnD,UAAqB;IAErB,QAAQ,CAAC,8CAA8C,CAAC,CAAC;IAEzD,OAAO,OAAO,CAAC,OAAO,CACpB,gBAAgB,CAAC;QACf,IAAI,EAAE,0BAA0B;QAEhC,6EAA6E;QAC7E,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YAC3B,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC;YAEtC,gDAAgD;YAChD,MAAM,cAAc,GAAqE,EAAE,CAAC;YAE5F,uEAAuE;YACvE,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC7E,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACxB,IACE,GAAG,YAAY,WAAW;oBAC1B,GAAG,CAAC,IAAI,KAAK,iBAAiB;oBAC9B,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAC/B,CAAC;oBACD,MAAM,aAAa,GAAG,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBACtD,IAAI,aAAa,EAAE,CAAC;wBAClB,cAAc,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,CAAC;oBACnE,CAAC;gBACH,CAAC;YACH,CAAC;YAED,qDAAqD;YACrD,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC9B,QAAQ,CAAC,aAAa,cAAc,CAAC,MAAM,sCAAsC,CAAC,CAAC;gBAEnF,MAAM,WAAW,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;gBAElC,KAAK,MAAM,EAAE,UAAU,EAAE,IAAI,cAAc,EAAE,CAAC;oBAC5C,MAAM,WAAW,GAAG,cAAc,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;oBAC1D,MAAM,YAAY,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;oBAEpD,MAAM,YAAY,GAAG,IAAI,YAAY,CAAC;wBACpC,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,eAAe,WAAW,yBAAyB;6BAC1D;4BACD,YAAY;yBACK;qBACpB,CAAC,CAAC;oBAEH,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;gBACjC,CAAC;gBAED,8CAA8C;gBAC9C,OAAO;oBACL,QAAQ,EAAE,WAAW;iBACtB,CAAC;YACJ,CAAC;YAED,kCAAkC;YAClC,OAAO,SAAS,CAAC;QACnB,CAAC;KACF,CAAC,CACH,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* Middleware registry for Gaunt Sloth Assistant.
|
|
4
|
+
*
|
|
5
|
+
* This module provides factory functions for creating predefined middleware instances
|
|
6
|
+
* and a resolver to convert middleware configurations into middleware objects.
|
|
7
|
+
*/
|
|
8
|
+
import type { GthConfig } from '@gaunt-sloth/core/config.js';
|
|
9
|
+
import type { AnthropicPromptCachingConfig, MiddlewareConfig, SummarizationConfig } from '#src/middleware/types.js';
|
|
10
|
+
import { type AgentMiddleware } from 'langchain';
|
|
11
|
+
/**
|
|
12
|
+
* Create Anthropic prompt caching middleware.
|
|
13
|
+
* This middleware adds cache control headers to reduce API costs.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createAnthropicPromptCachingMiddleware(config: AnthropicPromptCachingConfig, _: GthConfig): Promise<AgentMiddleware>;
|
|
16
|
+
/**
|
|
17
|
+
* Create summarization middleware.
|
|
18
|
+
* This middleware automatically condenses conversation history when approaching token limits.
|
|
19
|
+
*
|
|
20
|
+
* @param config - Configuration for the middleware
|
|
21
|
+
* @param gthConfig - Full Gaunt Sloth configuration
|
|
22
|
+
* @returns Middleware object
|
|
23
|
+
*/
|
|
24
|
+
export declare function createSummarizationMiddleware(config: SummarizationConfig, gthConfig: GthConfig): Promise<AgentMiddleware>;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve middleware configuration into middleware instances.
|
|
27
|
+
* Converts string identifiers and config objects into actual middleware.
|
|
28
|
+
* Automatically injects binary-content-injection middleware if binaryFormats is enabled.
|
|
29
|
+
*
|
|
30
|
+
* @param configs - Array of middleware configurations
|
|
31
|
+
* @param gthConfig - Full Gaunt Sloth configuration
|
|
32
|
+
* @returns Array of middleware instances
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveMiddleware(configs: MiddlewareConfig[] | undefined, gthConfig: GthConfig): Promise<AgentMiddleware[]>;
|