@gaunt-sloth/agent 2.0.0-alpha.9 → 2.0.0-beta.1
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 +73 -23
- package/cli-acp.js +21 -24
- package/dist/builtInToolsConfig.d.ts +17 -3
- package/dist/builtInToolsConfig.js +43 -41
- package/dist/builtInToolsConfig.js.map +1 -1
- package/dist/core/debugCapture.d.ts +7 -49
- package/dist/core/debugCapture.js +1 -1
- package/dist/core/debugCapture.js.map +1 -1
- package/dist/core/resolveAgentFactory.d.ts +10 -9
- package/dist/core/resolveAgentFactory.js +11 -12
- package/dist/core/resolveAgentFactory.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp/OAuthClientProviderImpl.js.map +1 -1
- package/dist/mcp/tlsTrust.d.ts +61 -0
- package/dist/mcp/tlsTrust.js +109 -0
- package/dist/mcp/tlsTrust.js.map +1 -0
- package/dist/middleware/binaryContentInjectionMiddleware.d.ts +8 -1
- package/dist/middleware/binaryContentInjectionMiddleware.js +11 -2
- package/dist/middleware/binaryContentInjectionMiddleware.js.map +1 -1
- package/dist/middleware/frontendImageInjectionMiddleware.d.ts +107 -0
- package/dist/middleware/frontendImageInjectionMiddleware.js +167 -0
- package/dist/middleware/frontendImageInjectionMiddleware.js.map +1 -0
- package/dist/middleware/registry.js +49 -1
- package/dist/middleware/registry.js.map +1 -1
- package/dist/middleware/types.d.ts +16 -2
- package/dist/modules/a2a/A2AClientWrapper.d.ts +56 -1
- package/dist/modules/a2a/A2AClientWrapper.js +131 -31
- package/dist/modules/a2a/A2AClientWrapper.js.map +1 -1
- package/dist/modules/acp/acpAgentApp.d.ts +57 -0
- package/dist/modules/acp/acpAgentApp.js +378 -0
- package/dist/modules/acp/acpAgentApp.js.map +1 -0
- package/dist/modules/acp/acpAgentAppV1.d.ts +52 -0
- package/dist/modules/acp/acpAgentAppV1.js +307 -0
- package/dist/modules/acp/acpAgentAppV1.js.map +1 -0
- package/dist/modules/acp/acpCommon.d.ts +167 -0
- package/dist/modules/acp/acpCommon.js +282 -0
- package/dist/modules/acp/acpCommon.js.map +1 -0
- package/dist/modules/acp/acpPermissions.d.ts +65 -0
- package/dist/modules/acp/acpPermissions.js +162 -0
- package/dist/modules/acp/acpPermissions.js.map +1 -0
- package/dist/modules/acp/acpPermissionsV1.d.ts +45 -0
- package/dist/modules/acp/acpPermissionsV1.js +110 -0
- package/dist/modules/acp/acpPermissionsV1.js.map +1 -0
- package/dist/modules/acp/acpRouter.d.ts +41 -0
- package/dist/modules/acp/acpRouter.js +48 -0
- package/dist/modules/acp/acpRouter.js.map +1 -0
- package/dist/modules/acp/acpStdio.d.ts +47 -0
- package/dist/modules/acp/acpStdio.js +71 -0
- package/dist/modules/acp/acpStdio.js.map +1 -0
- package/dist/modules/acp/acpToolCalls.d.ts +93 -0
- package/dist/modules/acp/acpToolCalls.js +193 -0
- package/dist/modules/acp/acpToolCalls.js.map +1 -0
- package/dist/modules/acp/acpUpdates.d.ts +48 -0
- package/dist/modules/acp/acpUpdates.js +142 -0
- package/dist/modules/acp/acpUpdates.js.map +1 -0
- package/dist/modules/acp/acpUpdatesV1.d.ts +61 -0
- package/dist/modules/acp/acpUpdatesV1.js +162 -0
- package/dist/modules/acp/acpUpdatesV1.js.map +1 -0
- package/dist/modules/apiAgUiModule.d.ts +87 -0
- package/dist/modules/apiAgUiModule.js +193 -28
- package/dist/modules/apiAgUiModule.js.map +1 -1
- package/dist/modules/interactiveSessionModule.js +497 -51
- package/dist/modules/interactiveSessionModule.js.map +1 -1
- package/dist/modules/slashCommands.d.ts +582 -0
- package/dist/modules/slashCommands.js +1001 -0
- package/dist/modules/slashCommands.js.map +1 -0
- package/dist/resolvers.js +119 -9
- package/dist/resolvers.js.map +1 -1
- package/dist/tools/GthCustomToolkit.js +95 -11
- package/dist/tools/GthCustomToolkit.js.map +1 -1
- package/dist/tools/GthDevToolkit.d.ts +8 -5
- package/dist/tools/GthDevToolkit.js +89 -43
- package/dist/tools/GthDevToolkit.js.map +1 -1
- package/dist/tools/GthFileSystemToolkit.d.ts +63 -0
- package/dist/tools/GthFileSystemToolkit.js +506 -105
- package/dist/tools/GthFileSystemToolkit.js.map +1 -1
- package/dist/tools/McpResourceTool.d.ts +31 -0
- package/dist/tools/McpResourceTool.js +106 -0
- package/dist/tools/McpResourceTool.js.map +1 -0
- package/dist/tools/gthChecklistTool.d.ts +30 -0
- package/dist/tools/gthChecklistTool.js +80 -0
- package/dist/tools/gthChecklistTool.js.map +1 -0
- package/dist/tools/gthGrepTool.d.ts +54 -0
- package/dist/tools/gthGrepTool.js +482 -0
- package/dist/tools/gthGrepTool.js.map +1 -0
- package/dist/tools/shell/env.js +1 -1
- package/dist/tools/shell/env.js.map +1 -1
- package/dist/tools/shell/outputBuffer.js.map +1 -1
- package/dist/tools/shell/workDir.d.ts +1 -10
- package/dist/tools/shell/workDir.js +10 -36
- package/dist/tools/shell/workDir.js.map +1 -1
- package/dist/utils/mcpAuthError.d.ts +36 -0
- package/dist/utils/mcpAuthError.js +89 -0
- package/dist/utils/mcpAuthError.js.map +1 -0
- package/package.json +13 -13
- package/dist/core/GthDeepAgent.d.ts +0 -181
- package/dist/core/GthDeepAgent.js +0 -682
- package/dist/core/GthDeepAgent.js.map +0 -1
- package/dist/core/deepAgentPermissions.d.ts +0 -115
- package/dist/core/deepAgentPermissions.js +0 -282
- package/dist/core/deepAgentPermissions.js.map +0 -1
- package/dist/core/gthAcpServer.d.ts +0 -27
- package/dist/core/gthAcpServer.js +0 -82
- package/dist/core/gthAcpServer.js.map +0 -1
- package/dist/core/gthDeepAgentFactory.d.ts +0 -8
- package/dist/core/gthDeepAgentFactory.js +0 -9
- package/dist/core/gthDeepAgentFactory.js.map +0 -1
- package/dist/modules/acpModule.d.ts +0 -40
- package/dist/modules/acpModule.js +0 -78
- package/dist/modules/acpModule.js.map +0 -1
- package/dist/tools/shell/allowlist.d.ts +0 -11
- package/dist/tools/shell/allowlist.js +0 -12
- package/dist/tools/shell/allowlist.js.map +0 -1
- package/dist/tools/shell/arity.d.ts +0 -11
- package/dist/tools/shell/arity.js +0 -12
- package/dist/tools/shell/arity.js.map +0 -1
- package/dist/tools/shell/hardline.d.ts +0 -15
- package/dist/tools/shell/hardline.js +0 -88
- package/dist/tools/shell/hardline.js.map +0 -1
- package/dist/tools/shell/normalize.d.ts +0 -10
- package/dist/tools/shell/normalize.js +0 -11
- package/dist/tools/shell/normalize.js.map +0 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* Process-global TLS trust for outbound HTTPS, driven by the `tls` config block.
|
|
4
|
+
*
|
|
5
|
+
* Motivation: an `http`-transport MCP server behind a private/corporate CA (e.g. a dev monolith
|
|
6
|
+
* with a self-signed cert) makes Node's global `fetch` reject the connection with
|
|
7
|
+
* `TypeError: fetch failed` (`SELF_SIGNED_CERT_IN_CHAIN`). The only pre-existing workaround was to
|
|
8
|
+
* prepend `NODE_EXTRA_CA_CERTS=<ca.crt>` on every invocation — that env var is read ONCE at Node
|
|
9
|
+
* startup, so it can't be set from config after launch.
|
|
10
|
+
*
|
|
11
|
+
* Node's `fetch` runs on undici and ignores `https.globalAgent`, so the CA must be installed via an
|
|
12
|
+
* undici dispatcher. `@langchain/mcp-adapters` forwards only `serverName`/`headers`/`authProvider`
|
|
13
|
+
* to the SDK transport (it drops the SDK's `requestInit`/`fetch` hooks), so a per-server CA is not
|
|
14
|
+
* reachable through the adapter. The trust is therefore installed **process-globally** via
|
|
15
|
+
* `setGlobalDispatcher`. That is acceptable for adding a CA (additive trust weakens nothing) — but
|
|
16
|
+
* note the insecure latch below is global too, and so affects LLM provider calls, not only MCP.
|
|
17
|
+
*/
|
|
18
|
+
import type { GthConfig } from '@gaunt-sloth/core/config.js';
|
|
19
|
+
type TlsConfig = GthConfig['tls'];
|
|
20
|
+
/** A cert path that could not be read, with the reason (surfaced by the installer). */
|
|
21
|
+
export interface TlsCertFailure {
|
|
22
|
+
path: string;
|
|
23
|
+
message: string;
|
|
24
|
+
}
|
|
25
|
+
/** The undici `connect` options computed from a `tls` config, plus what happened while reading. */
|
|
26
|
+
export interface BuiltDispatcher {
|
|
27
|
+
/** Node's default roots FIRST, then successfully-read user certs. `connect.ca` REPLACES the */
|
|
28
|
+
/** default store, so the roots must be included or every public-CA fetch breaks. */
|
|
29
|
+
ca: string[];
|
|
30
|
+
/** `false` disables verification entirely (process-global). Defaults to `true`. */
|
|
31
|
+
rejectUnauthorized: boolean;
|
|
32
|
+
/** How many configured certs were read successfully. */
|
|
33
|
+
loadedCount: number;
|
|
34
|
+
/** Configured certs that could not be read (fail-soft: skipped, not fatal). */
|
|
35
|
+
failures: TlsCertFailure[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Compute the undici dispatcher options from a `tls` config. Pure aside from the injected
|
|
39
|
+
* `readCert` (so unit tests stub file reads): resolves nothing about the environment itself.
|
|
40
|
+
*
|
|
41
|
+
* Returns `null` only when NOTHING is configured — no `tls` block, or an empty `tls` with no cert
|
|
42
|
+
* entries and verification left at its secure default. When certs ARE configured it always returns
|
|
43
|
+
* the object (even if every read failed), so the caller can surface those `failures` rather than
|
|
44
|
+
* silently swallowing a bad cert path. Whether to actually install is the caller's call:
|
|
45
|
+
* `loadedCount > 0 || !rejectUnauthorized`.
|
|
46
|
+
*
|
|
47
|
+
* `readCert` receives each configured path verbatim and must return the PEM contents or throw.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildDispatcherOptions(tls: TlsConfig, readCert: (path: string) => string, baseRoots?: readonly string[]): BuiltDispatcher | null;
|
|
50
|
+
/**
|
|
51
|
+
* Install the process-global undici dispatcher from `config.tls`, if any. Idempotent (installs at
|
|
52
|
+
* most once per process). Fail-soft: an unreadable cert is warned about and skipped, never fatal.
|
|
53
|
+
* Emits a loud security warning when verification is disabled.
|
|
54
|
+
*
|
|
55
|
+
* Wired into `getMcpClient` before the MCP client is created; because it is global it also covers
|
|
56
|
+
* LLM/tool fetches — which is desired for the CA case and is the danger to flag for the latch.
|
|
57
|
+
*/
|
|
58
|
+
export declare function installMcpTlsTrust(config: GthConfig): void;
|
|
59
|
+
/** Test-only: reset the install-once guard between cases. */
|
|
60
|
+
export declare function resetMcpTlsTrustForTests(): void;
|
|
61
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* Process-global TLS trust for outbound HTTPS, driven by the `tls` config block.
|
|
4
|
+
*
|
|
5
|
+
* Motivation: an `http`-transport MCP server behind a private/corporate CA (e.g. a dev monolith
|
|
6
|
+
* with a self-signed cert) makes Node's global `fetch` reject the connection with
|
|
7
|
+
* `TypeError: fetch failed` (`SELF_SIGNED_CERT_IN_CHAIN`). The only pre-existing workaround was to
|
|
8
|
+
* prepend `NODE_EXTRA_CA_CERTS=<ca.crt>` on every invocation — that env var is read ONCE at Node
|
|
9
|
+
* startup, so it can't be set from config after launch.
|
|
10
|
+
*
|
|
11
|
+
* Node's `fetch` runs on undici and ignores `https.globalAgent`, so the CA must be installed via an
|
|
12
|
+
* undici dispatcher. `@langchain/mcp-adapters` forwards only `serverName`/`headers`/`authProvider`
|
|
13
|
+
* to the SDK transport (it drops the SDK's `requestInit`/`fetch` hooks), so a per-server CA is not
|
|
14
|
+
* reachable through the adapter. The trust is therefore installed **process-globally** via
|
|
15
|
+
* `setGlobalDispatcher`. That is acceptable for adding a CA (additive trust weakens nothing) — but
|
|
16
|
+
* note the insecure latch below is global too, and so affects LLM provider calls, not only MCP.
|
|
17
|
+
*/
|
|
18
|
+
import { rootCertificates } from 'node:tls';
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
20
|
+
import { homedir } from 'node:os';
|
|
21
|
+
import { resolve } from 'node:path';
|
|
22
|
+
import { Agent, setGlobalDispatcher } from 'undici';
|
|
23
|
+
import { getProjectDir } from '@gaunt-sloth/core/utils/systemUtils.js';
|
|
24
|
+
import { displayWarning } from '@gaunt-sloth/core/utils/consoleUtils.js';
|
|
25
|
+
import { debugLog } from '@gaunt-sloth/core/utils/debugUtils.js';
|
|
26
|
+
/**
|
|
27
|
+
* Compute the undici dispatcher options from a `tls` config. Pure aside from the injected
|
|
28
|
+
* `readCert` (so unit tests stub file reads): resolves nothing about the environment itself.
|
|
29
|
+
*
|
|
30
|
+
* Returns `null` only when NOTHING is configured — no `tls` block, or an empty `tls` with no cert
|
|
31
|
+
* entries and verification left at its secure default. When certs ARE configured it always returns
|
|
32
|
+
* the object (even if every read failed), so the caller can surface those `failures` rather than
|
|
33
|
+
* silently swallowing a bad cert path. Whether to actually install is the caller's call:
|
|
34
|
+
* `loadedCount > 0 || !rejectUnauthorized`.
|
|
35
|
+
*
|
|
36
|
+
* `readCert` receives each configured path verbatim and must return the PEM contents or throw.
|
|
37
|
+
*/
|
|
38
|
+
export function buildDispatcherOptions(tls, readCert, baseRoots = rootCertificates) {
|
|
39
|
+
const rejectUnauthorized = tls?.rejectUnauthorized ?? true;
|
|
40
|
+
const certPaths = tls?.extraCaCerts ?? [];
|
|
41
|
+
// Nothing configured at all: no cert entries and verification not being disabled.
|
|
42
|
+
if (certPaths.length === 0 && rejectUnauthorized)
|
|
43
|
+
return null;
|
|
44
|
+
const userCerts = [];
|
|
45
|
+
const failures = [];
|
|
46
|
+
for (const path of certPaths) {
|
|
47
|
+
try {
|
|
48
|
+
userCerts.push(readCert(path));
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
failures.push({ path, message: error instanceof Error ? error.message : String(error) });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
// ca REPLACES the trust store — keep the default roots or public-CA fetches (incl. the LLM
|
|
56
|
+
// provider) silently break.
|
|
57
|
+
ca: [...baseRoots, ...userCerts],
|
|
58
|
+
rejectUnauthorized,
|
|
59
|
+
loadedCount: userCerts.length,
|
|
60
|
+
failures,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// Install-once guard: the trust is process-global and set-once, so re-running resolveTools (re-init)
|
|
64
|
+
// must not re-replace the dispatcher nor re-emit the security warning every time.
|
|
65
|
+
let installed = false;
|
|
66
|
+
/**
|
|
67
|
+
* Install the process-global undici dispatcher from `config.tls`, if any. Idempotent (installs at
|
|
68
|
+
* most once per process). Fail-soft: an unreadable cert is warned about and skipped, never fatal.
|
|
69
|
+
* Emits a loud security warning when verification is disabled.
|
|
70
|
+
*
|
|
71
|
+
* Wired into `getMcpClient` before the MCP client is created; because it is global it also covers
|
|
72
|
+
* LLM/tool fetches — which is desired for the CA case and is the danger to flag for the latch.
|
|
73
|
+
*/
|
|
74
|
+
export function installMcpTlsTrust(config) {
|
|
75
|
+
if (installed) {
|
|
76
|
+
debugLog('MCP TLS trust already installed this process; skipping.');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const built = buildDispatcherOptions(config.tls, resolveAndReadCert);
|
|
80
|
+
if (!built)
|
|
81
|
+
return;
|
|
82
|
+
// Surface unreadable certs even if we end up installing nothing — a bad cert path must never be
|
|
83
|
+
// swallowed silently. Warn BEFORE the "nothing actionable" bail below.
|
|
84
|
+
for (const failure of built.failures) {
|
|
85
|
+
displayWarning(`TLS: could not read CA certificate '${failure.path}' (${failure.message}); skipping it.`);
|
|
86
|
+
}
|
|
87
|
+
// Nothing left to install: every configured cert failed to read and verification is still on.
|
|
88
|
+
// (Failures were already surfaced above.) Leave Node's default dispatcher untouched.
|
|
89
|
+
if (built.loadedCount === 0 && built.rejectUnauthorized)
|
|
90
|
+
return;
|
|
91
|
+
installed = true;
|
|
92
|
+
if (!built.rejectUnauthorized) {
|
|
93
|
+
displayWarning('TLS: certificate verification is DISABLED (tls.rejectUnauthorized: false). This is INSECURE ' +
|
|
94
|
+
'and applies to ALL outbound HTTPS this process makes — MCP servers AND LLM provider calls. ' +
|
|
95
|
+
'Use only against trusted dev endpoints; prefer tls.extraCaCerts to trust a specific CA instead.');
|
|
96
|
+
}
|
|
97
|
+
setGlobalDispatcher(new Agent({ connect: { ca: built.ca, rejectUnauthorized: built.rejectUnauthorized } }));
|
|
98
|
+
debugLog(`MCP TLS trust installed: ${built.loadedCount} extra CA cert(s), rejectUnauthorized=${built.rejectUnauthorized}.`);
|
|
99
|
+
}
|
|
100
|
+
/** Resolve a configured cert path (`~` / relative-to-project / absolute) and read it as UTF-8. */
|
|
101
|
+
function resolveAndReadCert(path) {
|
|
102
|
+
const expanded = path === '~' || path.startsWith('~/') ? homedir() + path.slice(1) : path;
|
|
103
|
+
return readFileSync(resolve(getProjectDir(), expanded), 'utf8');
|
|
104
|
+
}
|
|
105
|
+
/** Test-only: reset the install-once guard between cases. */
|
|
106
|
+
export function resetMcpTlsTrustForTests() {
|
|
107
|
+
installed = false;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=tlsTrust.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tlsTrust.js","sourceRoot":"","sources":["../../src/mcp/tlsTrust.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,5 +18,12 @@ import { type AgentMiddleware } from 'langchain';
|
|
|
18
18
|
import type { GthConfig } from '@gaunt-sloth/core/config.js';
|
|
19
19
|
export interface BinaryContentInjectionMiddlewareSettings {
|
|
20
20
|
name?: 'binary-content-injection';
|
|
21
|
+
/**
|
|
22
|
+
* The gth provider string selecting the per-provider image-block shape (see
|
|
23
|
+
* {@link imageBlockFor}). Derived by the registry factory via `resolveVisionProvider(gthConfig)`;
|
|
24
|
+
* may be `''`/absent, in which case the standard base64 image block is emitted. Only affects the
|
|
25
|
+
* `image` format — file/video/audio blocks are provider-agnostic.
|
|
26
|
+
*/
|
|
27
|
+
provider?: string;
|
|
21
28
|
}
|
|
22
|
-
export declare function createBinaryContentInjectionMiddleware(
|
|
29
|
+
export declare function createBinaryContentInjectionMiddleware(settings: BinaryContentInjectionMiddlewareSettings, _gthConfig: GthConfig): Promise<AgentMiddleware>;
|
|
@@ -18,6 +18,7 @@ import { createMiddleware } from 'langchain';
|
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import { debugLog } from '@gaunt-sloth/core/utils/debugUtils.js';
|
|
20
20
|
import { ToolMessage, HumanMessage } from '@langchain/core/messages';
|
|
21
|
+
import { imageBlockFor } from '#src/middleware/frontendImageInjectionMiddleware.js';
|
|
21
22
|
/**
|
|
22
23
|
* Parse the special binary format string returned by gth_read_binary tool.
|
|
23
24
|
* Format: gth_read_binary;type:${type};path:${encodedPath};data:${media_type};base64,${data}
|
|
@@ -73,8 +74,10 @@ function getFormatLabel(formatType) {
|
|
|
73
74
|
};
|
|
74
75
|
return labels[formatType] || 'file';
|
|
75
76
|
}
|
|
76
|
-
export function createBinaryContentInjectionMiddleware(
|
|
77
|
+
export function createBinaryContentInjectionMiddleware(settings, _gthConfig) {
|
|
77
78
|
debugLog('Creating binary content injection middleware');
|
|
79
|
+
// The provider selecting the image-block shape; '' falls to the standard base64 block.
|
|
80
|
+
const provider = settings.provider ?? '';
|
|
78
81
|
return Promise.resolve(createMiddleware({
|
|
79
82
|
name: 'binary-content-injection',
|
|
80
83
|
// Before next model call, detect and inject HumanMessage with binary content
|
|
@@ -100,7 +103,13 @@ export function createBinaryContentInjectionMiddleware(_settings, _gthConfig) {
|
|
|
100
103
|
const newMessages = [...messages];
|
|
101
104
|
for (const { binaryData } of binaryMessages) {
|
|
102
105
|
const formatLabel = getFormatLabel(binaryData.formatType);
|
|
103
|
-
|
|
106
|
+
// Images go through the per-provider builder so OpenAI reasoning models (Responses API,
|
|
107
|
+
// GS2-74) get a valid `image_url` block instead of the standard `source_type` data block,
|
|
108
|
+
// which @langchain/openai mis-serialises to an invalid Responses image part (GS2-75).
|
|
109
|
+
// file/video/audio keep the standard block (they already convert correctly).
|
|
110
|
+
const contentBlock = binaryData.formatType === 'image'
|
|
111
|
+
? imageBlockFor(provider, binaryData.media_type, binaryData.data)
|
|
112
|
+
: createContentBlock(binaryData);
|
|
104
113
|
const humanMessage = new HumanMessage({
|
|
105
114
|
content: [
|
|
106
115
|
{
|
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"file":"binaryContentInjectionMiddleware.js","sourceRoot":"","sources":["../../src/middleware/binaryContentInjectionMiddleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* Opt-in middleware that turns a frontend "capture image" tool result into a vision message the
|
|
4
|
+
* model can actually see.
|
|
5
|
+
*
|
|
6
|
+
* A browser/frontend tool such as CopilotKit's `capture_image` fulfils client-side and posts its
|
|
7
|
+
* result back to gth's AG-UI server as a trailing `tool`-role message whose content is a JSON
|
|
8
|
+
* string `{"mimeType":"image/...","data":"<base64>"}`. gth hands that to the model as a plain-string
|
|
9
|
+
* ToolMessage — no vision block — so the model literally cannot see the photo. This middleware
|
|
10
|
+
* detects that ToolMessage in `beforeModel`, parses the envelope, and injects a `HumanMessage`
|
|
11
|
+
* carrying a provider-appropriate vision block before the next model call.
|
|
12
|
+
*
|
|
13
|
+
* Strictly opt-in: it fires ONLY when referenced by name in `config.middleware`
|
|
14
|
+
* (`"middleware": ["frontend-image-injection"]`), never auto-injected. Promoted from
|
|
15
|
+
* pukeko-robot-controller's proven middleware (RC-21), minus the robot-specific motion-tool coupling.
|
|
16
|
+
*/
|
|
17
|
+
import { type AgentMiddleware } from 'langchain';
|
|
18
|
+
/** Default frontend capture tool name. Overridable via the `toolName` middleware setting. */
|
|
19
|
+
export declare const DEFAULT_CAPTURE_TOOL_NAME = "capture_image";
|
|
20
|
+
export interface FrontendImageInjectionOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Provider selecting the vision-block shape. Derived by the registry factory from
|
|
23
|
+
* `gthConfig.modelProviderType` (the raw `llm.type`), falling back to `llm._llmType()`. One of
|
|
24
|
+
* gth's provider strings (`anthropic`, `openai`, `openrouter`, `deepseek`, `xai`, `groq`,
|
|
25
|
+
* `ollama`, `google-genai`, `vertexai`, `huggingface`, …); unknown values fall to the standard
|
|
26
|
+
* base64 block.
|
|
27
|
+
*/
|
|
28
|
+
provider: string;
|
|
29
|
+
/**
|
|
30
|
+
* Tool name whose `{mimeType,data}` result is converted into a vision message. Defaults to
|
|
31
|
+
* {@link DEFAULT_CAPTURE_TOOL_NAME} (`capture_image`).
|
|
32
|
+
*/
|
|
33
|
+
toolName?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A vision content block the target provider's `@langchain` converter actually decodes. Verified
|
|
37
|
+
* against the installed converters (RC-21):
|
|
38
|
+
* - **ollama** → `{ type:'image_url', image_url:'<data-URL string>' }`. ChatOllama's
|
|
39
|
+
* `convertToOllamaMessages` only handles `image_url` blocks (extractBase64FromDataUrl); the
|
|
40
|
+
* LangChain standard `source_type` block throws "Unsupported content type: image".
|
|
41
|
+
* - **OpenAI-compatible** (`openai`, `openrouter`, `deepseek`, `xai`, `groq` — served by an
|
|
42
|
+
* OpenAI-compatible Chat Completions API, whichever LangChain class fronts it) →
|
|
43
|
+
* `{ type:'image_url', image_url:{ url:'<data-URL>' } }`.
|
|
44
|
+
* This native OpenAI shape is correct on BOTH the Completions API AND the Responses API (GS2-74
|
|
45
|
+
* flips reasoning-capable openai models to Responses). A raw `source_type` standard block
|
|
46
|
+
* serialises to an *invalid* image part on the Responses path, so we emit the provider-native
|
|
47
|
+
* shape rather than lean on `@langchain/core`'s (deprecated, internal) auto-conversion.
|
|
48
|
+
* - **anthropic** → the provider-native block `{ type:'image', source:{ type:'base64',
|
|
49
|
+
* media_type, data } }`. The LangChain standard block is NOT usable here (RC-32): in
|
|
50
|
+
* `@langchain/anthropic`'s `_formatContentBlocks`, the `isDataContentBlock` branch yields its
|
|
51
|
+
* conversion and then FALLS THROUGH — no `continue` — into the chain below, where
|
|
52
|
+
* `type === 'image'` matches the very same block and yields a SECOND one whose `media_type` is
|
|
53
|
+
* read from camelCase `mimeType` (a key the snake_case standard block never has) and so
|
|
54
|
+
* defaults to the literal `image/jpeg`. Every frame is therefore sent twice, and a non-JPEG
|
|
55
|
+
* capture 400s outright on the mislabelled copy. The native block is recognised earlier by
|
|
56
|
+
* `_isAnthropicImageBlockParam` and passed through untouched, exactly once.
|
|
57
|
+
* - **google-genai / vertexai** (and any unknown/default) → the LangChain standard base64 data
|
|
58
|
+
* content block `{ type:'image', source_type:'base64', mime_type, data }`, which those native
|
|
59
|
+
* converters decode directly and which is the most broadly decodable fallback.
|
|
60
|
+
*
|
|
61
|
+
* The through-line: emit what the target provider's converter consumes natively rather than lean on
|
|
62
|
+
* a generic auto-conversion — the same lesson as GS2-75 on the OpenAI Responses path.
|
|
63
|
+
*
|
|
64
|
+
* Pure and exported so each provider branch can be unit-tested directly.
|
|
65
|
+
*/
|
|
66
|
+
export declare function imageBlockFor(provider: string, mimeType: string, data: string): {
|
|
67
|
+
type: 'image_url';
|
|
68
|
+
image_url: string;
|
|
69
|
+
source?: undefined;
|
|
70
|
+
source_type?: undefined;
|
|
71
|
+
mime_type?: undefined;
|
|
72
|
+
data?: undefined;
|
|
73
|
+
} | {
|
|
74
|
+
type: 'image_url';
|
|
75
|
+
image_url: {
|
|
76
|
+
url: string;
|
|
77
|
+
};
|
|
78
|
+
source?: undefined;
|
|
79
|
+
source_type?: undefined;
|
|
80
|
+
mime_type?: undefined;
|
|
81
|
+
data?: undefined;
|
|
82
|
+
} | {
|
|
83
|
+
image_url?: undefined;
|
|
84
|
+
type: 'image';
|
|
85
|
+
source: {
|
|
86
|
+
type: 'base64';
|
|
87
|
+
media_type: string;
|
|
88
|
+
data: string;
|
|
89
|
+
};
|
|
90
|
+
source_type?: undefined;
|
|
91
|
+
mime_type?: undefined;
|
|
92
|
+
data?: undefined;
|
|
93
|
+
} | {
|
|
94
|
+
image_url?: undefined;
|
|
95
|
+
source?: undefined;
|
|
96
|
+
type: 'image';
|
|
97
|
+
source_type: 'base64';
|
|
98
|
+
mime_type: string;
|
|
99
|
+
data: string;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Create the frontend-image-injection middleware.
|
|
103
|
+
*
|
|
104
|
+
* @param opts.provider - provider string selecting the vision-block shape (see {@link imageBlockFor}).
|
|
105
|
+
* @param opts.toolName - capture tool name (default {@link DEFAULT_CAPTURE_TOOL_NAME}).
|
|
106
|
+
*/
|
|
107
|
+
export declare function createFrontendImageInjectionMiddleware(opts: FrontendImageInjectionOptions): AgentMiddleware;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* Opt-in middleware that turns a frontend "capture image" tool result into a vision message the
|
|
4
|
+
* model can actually see.
|
|
5
|
+
*
|
|
6
|
+
* A browser/frontend tool such as CopilotKit's `capture_image` fulfils client-side and posts its
|
|
7
|
+
* result back to gth's AG-UI server as a trailing `tool`-role message whose content is a JSON
|
|
8
|
+
* string `{"mimeType":"image/...","data":"<base64>"}`. gth hands that to the model as a plain-string
|
|
9
|
+
* ToolMessage — no vision block — so the model literally cannot see the photo. This middleware
|
|
10
|
+
* detects that ToolMessage in `beforeModel`, parses the envelope, and injects a `HumanMessage`
|
|
11
|
+
* carrying a provider-appropriate vision block before the next model call.
|
|
12
|
+
*
|
|
13
|
+
* Strictly opt-in: it fires ONLY when referenced by name in `config.middleware`
|
|
14
|
+
* (`"middleware": ["frontend-image-injection"]`), never auto-injected. Promoted from
|
|
15
|
+
* pukeko-robot-controller's proven middleware (RC-21), minus the robot-specific motion-tool coupling.
|
|
16
|
+
*/
|
|
17
|
+
import { createMiddleware } from 'langchain';
|
|
18
|
+
import { HumanMessage, isToolMessage } from '@langchain/core/messages';
|
|
19
|
+
/** Default frontend capture tool name. Overridable via the `toolName` middleware setting. */
|
|
20
|
+
export const DEFAULT_CAPTURE_TOOL_NAME = 'capture_image';
|
|
21
|
+
/**
|
|
22
|
+
* A vision content block the target provider's `@langchain` converter actually decodes. Verified
|
|
23
|
+
* against the installed converters (RC-21):
|
|
24
|
+
* - **ollama** → `{ type:'image_url', image_url:'<data-URL string>' }`. ChatOllama's
|
|
25
|
+
* `convertToOllamaMessages` only handles `image_url` blocks (extractBase64FromDataUrl); the
|
|
26
|
+
* LangChain standard `source_type` block throws "Unsupported content type: image".
|
|
27
|
+
* - **OpenAI-compatible** (`openai`, `openrouter`, `deepseek`, `xai`, `groq` — served by an
|
|
28
|
+
* OpenAI-compatible Chat Completions API, whichever LangChain class fronts it) →
|
|
29
|
+
* `{ type:'image_url', image_url:{ url:'<data-URL>' } }`.
|
|
30
|
+
* This native OpenAI shape is correct on BOTH the Completions API AND the Responses API (GS2-74
|
|
31
|
+
* flips reasoning-capable openai models to Responses). A raw `source_type` standard block
|
|
32
|
+
* serialises to an *invalid* image part on the Responses path, so we emit the provider-native
|
|
33
|
+
* shape rather than lean on `@langchain/core`'s (deprecated, internal) auto-conversion.
|
|
34
|
+
* - **anthropic** → the provider-native block `{ type:'image', source:{ type:'base64',
|
|
35
|
+
* media_type, data } }`. The LangChain standard block is NOT usable here (RC-32): in
|
|
36
|
+
* `@langchain/anthropic`'s `_formatContentBlocks`, the `isDataContentBlock` branch yields its
|
|
37
|
+
* conversion and then FALLS THROUGH — no `continue` — into the chain below, where
|
|
38
|
+
* `type === 'image'` matches the very same block and yields a SECOND one whose `media_type` is
|
|
39
|
+
* read from camelCase `mimeType` (a key the snake_case standard block never has) and so
|
|
40
|
+
* defaults to the literal `image/jpeg`. Every frame is therefore sent twice, and a non-JPEG
|
|
41
|
+
* capture 400s outright on the mislabelled copy. The native block is recognised earlier by
|
|
42
|
+
* `_isAnthropicImageBlockParam` and passed through untouched, exactly once.
|
|
43
|
+
* - **google-genai / vertexai** (and any unknown/default) → the LangChain standard base64 data
|
|
44
|
+
* content block `{ type:'image', source_type:'base64', mime_type, data }`, which those native
|
|
45
|
+
* converters decode directly and which is the most broadly decodable fallback.
|
|
46
|
+
*
|
|
47
|
+
* The through-line: emit what the target provider's converter consumes natively rather than lean on
|
|
48
|
+
* a generic auto-conversion — the same lesson as GS2-75 on the OpenAI Responses path.
|
|
49
|
+
*
|
|
50
|
+
* Pure and exported so each provider branch can be unit-tested directly.
|
|
51
|
+
*/
|
|
52
|
+
export function imageBlockFor(provider, mimeType, data) {
|
|
53
|
+
const dataUrl = `data:${mimeType};base64,${data}`;
|
|
54
|
+
switch (provider) {
|
|
55
|
+
case 'ollama':
|
|
56
|
+
return { type: 'image_url', image_url: dataUrl };
|
|
57
|
+
case 'openai':
|
|
58
|
+
case 'openrouter':
|
|
59
|
+
case 'deepseek':
|
|
60
|
+
case 'xai':
|
|
61
|
+
case 'groq':
|
|
62
|
+
return { type: 'image_url', image_url: { url: dataUrl } };
|
|
63
|
+
case 'anthropic':
|
|
64
|
+
return {
|
|
65
|
+
type: 'image',
|
|
66
|
+
source: { type: 'base64', media_type: mimeType, data },
|
|
67
|
+
};
|
|
68
|
+
case 'google-genai':
|
|
69
|
+
case 'vertexai':
|
|
70
|
+
default:
|
|
71
|
+
return {
|
|
72
|
+
type: 'image',
|
|
73
|
+
source_type: 'base64',
|
|
74
|
+
mime_type: mimeType,
|
|
75
|
+
data,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Create the frontend-image-injection middleware.
|
|
81
|
+
*
|
|
82
|
+
* @param opts.provider - provider string selecting the vision-block shape (see {@link imageBlockFor}).
|
|
83
|
+
* @param opts.toolName - capture tool name (default {@link DEFAULT_CAPTURE_TOOL_NAME}).
|
|
84
|
+
*/
|
|
85
|
+
export function createFrontendImageInjectionMiddleware(opts) {
|
|
86
|
+
const toolName = opts.toolName ?? DEFAULT_CAPTURE_TOOL_NAME;
|
|
87
|
+
// thread_id → set of tool_call_ids whose image (or error note) has already been injected. It
|
|
88
|
+
// scopes injection to ONE frame per capture per graph thread: `beforeModel` runs before every
|
|
89
|
+
// model call, so a multi-step turn would otherwise stack a copy of the same frame at each step,
|
|
90
|
+
// each one appended after the newest content and mispairing the assistant message with a stale
|
|
91
|
+
// frame (the RC-21 idempotency guard). It is deliberately keyed on the GRAPH thread rather than
|
|
92
|
+
// the client session — the AG-UI server gives each fresh run its own checkpoint thread (RC-31),
|
|
93
|
+
// and that run replays the whole history into an empty graph, so it must re-inject the frame or
|
|
94
|
+
// the model would lose the photo the moment the capture turn ended.
|
|
95
|
+
//
|
|
96
|
+
// Closure-scoped (one Map per middleware instance), NOT a module global as the robot had it. The
|
|
97
|
+
// AG-UI server caches one agent per client-toolset signature (getAgentForTools), so a thread's
|
|
98
|
+
// turns share this instance and idempotency-across-turns is preserved — while unrelated agents (and
|
|
99
|
+
// tests) get independent state instead of a process-lifetime Map that accumulates every thread_id.
|
|
100
|
+
const injectedByThread = new Map();
|
|
101
|
+
return createMiddleware({
|
|
102
|
+
name: 'frontend-image-injection',
|
|
103
|
+
beforeModel: async (state, runtime) => {
|
|
104
|
+
const messages = state.messages || [];
|
|
105
|
+
// The AG-UI server sets runConfig.configurable.thread_id (= the run's threadId) and threads it
|
|
106
|
+
// to the graph, so per-session idempotency keys correctly. '__default__' is only reached off
|
|
107
|
+
// that path (e.g. a bare invoke with no thread_id), which still behaves correctly per-instance.
|
|
108
|
+
const threadId = runtime?.configurable?.thread_id ?? '__default__';
|
|
109
|
+
let injectedIds = injectedByThread.get(threadId);
|
|
110
|
+
if (!injectedIds) {
|
|
111
|
+
injectedIds = new Set();
|
|
112
|
+
injectedByThread.set(threadId, injectedIds);
|
|
113
|
+
}
|
|
114
|
+
// Scan forward so injected frames stay in chronological order; skip any tool_call_id already
|
|
115
|
+
// injected on this thread (idempotent across a retained/replayed tail).
|
|
116
|
+
const injected = [];
|
|
117
|
+
for (let i = 0; i < messages.length; i++) {
|
|
118
|
+
const msg = messages[i];
|
|
119
|
+
if (
|
|
120
|
+
// RC-21 (golden fix): use the duck-typed `isToolMessage`, NEVER `msg instanceof
|
|
121
|
+
// ToolMessage`. A consumer (galvanized/robot) importing this middleware across a `file:`-dep
|
|
122
|
+
// boundary resolves a SECOND @langchain/core copy, and a capture ToolMessage constructed by
|
|
123
|
+
// the AG-UI pipeline's core copy is not an instance of the `ToolMessage` class we import —
|
|
124
|
+
// `instanceof` silently returns false across copies and no frame is ever injected.
|
|
125
|
+
isToolMessage(msg) &&
|
|
126
|
+
typeof msg.content === 'string' &&
|
|
127
|
+
msg.name === toolName) {
|
|
128
|
+
const id = msg.tool_call_id;
|
|
129
|
+
if (!id || injectedIds.has(id))
|
|
130
|
+
continue;
|
|
131
|
+
try {
|
|
132
|
+
injected.push({ payload: JSON.parse(msg.content), id });
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Non-JSON tool result — skip injection (leave the guard clean).
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (injected.length === 0)
|
|
140
|
+
return undefined;
|
|
141
|
+
const newMessages = [...messages];
|
|
142
|
+
for (const { payload, id } of injected) {
|
|
143
|
+
if (payload.error) {
|
|
144
|
+
// Mark injected so the error note isn't re-emitted on a later turn.
|
|
145
|
+
injectedIds.add(id);
|
|
146
|
+
newMessages.push(new HumanMessage({ content: `Camera unavailable: ${payload.error}` }));
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (payload.mimeType && payload.data) {
|
|
150
|
+
// RC-21: mark injected ONLY on a successful frame emission. A capture result that arrived
|
|
151
|
+
// WITHOUT its base64 `data` (dropped upstream) injects nothing and must NOT poison the
|
|
152
|
+
// guard — a later data-bearing result for the same tool_call_id can still recover.
|
|
153
|
+
injectedIds.add(id);
|
|
154
|
+
const block = imageBlockFor(opts.provider, payload.mimeType, payload.data);
|
|
155
|
+
newMessages.push(new HumanMessage({
|
|
156
|
+
content: [{ type: 'text', text: 'Camera frame captured:' }, block],
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
// else: a capture result whose `data` is absent — inject nothing, leave the guard clean.
|
|
160
|
+
}
|
|
161
|
+
// If every candidate was data-less (nothing appended), return no state update rather than an
|
|
162
|
+
// identical copy — keeps beforeModel a true no-op and leaves the guard clean for recovery.
|
|
163
|
+
return newMessages.length > messages.length ? { messages: newMessages } : undefined;
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=frontendImageInjectionMiddleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontendImageInjectionMiddleware.js","sourceRoot":"","sources":["../../src/middleware/frontendImageInjectionMiddleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,41 @@ import { displayWarning } from '@gaunt-sloth/core/utils/consoleUtils.js';
|
|
|
9
9
|
import { debugLog } from '@gaunt-sloth/core/utils/debugUtils.js';
|
|
10
10
|
import { anthropicPromptCachingMiddleware, summarizationMiddleware, } from 'langchain';
|
|
11
11
|
import { createBinaryContentInjectionMiddleware, } from '#src/middleware/binaryContentInjectionMiddleware.js';
|
|
12
|
+
import { createFrontendImageInjectionMiddleware } from '#src/middleware/frontendImageInjectionMiddleware.js';
|
|
13
|
+
/**
|
|
14
|
+
* Derive the provider string the vision middleware maps to a per-provider block shape. Prefers the
|
|
15
|
+
* loader-stashed raw `llm.type` ({@link GthConfig.modelProviderType}) — the exact gth provider
|
|
16
|
+
* namespace (`anthropic`/`openrouter`/`deepseek`/`xai`/`groq`/`ollama`/`google-genai`/`vertexai`/…)
|
|
17
|
+
* — and falls back to the live model's `_llmType()` only when it is absent (module configs).
|
|
18
|
+
* `_llmType()` is the model class's own label rather than the gth provider namespace, so the two
|
|
19
|
+
* only mostly agree. Where they do: `openrouter`/`deepseek`/`xai`/`groq`/`ollama`/`anthropic` each
|
|
20
|
+
* report their own name, which `imageBlockFor` matches directly, and both Gemini providers report
|
|
21
|
+
* `google`, which lands on the default branch — the same standard block its `google-genai`/
|
|
22
|
+
* `vertexai` cases return.
|
|
23
|
+
*
|
|
24
|
+
* Two labels do NOT agree, and this fallback is the only path either can arrive on:
|
|
25
|
+
* - `huggingface` reports `openai`, so the fallback emits `image_url:{url}` where the configured
|
|
26
|
+
* `type` would take the default standard block. Which of the two the HF router actually accepts
|
|
27
|
+
* is unmeasured — see [[CFG-45]]. Do not add a branch by analogy.
|
|
28
|
+
* - `ChatXAIResponses` reports `xai-responses`, which matches no case and so takes the default
|
|
29
|
+
* standard block — while `imageBlockFor`'s own docstring records that a standard block
|
|
30
|
+
* serialises to an INVALID image part on the Responses path. gth itself only ever constructs
|
|
31
|
+
* `ChatXAI`, so this is reachable only from a module config that builds the Responses class
|
|
32
|
+
* itself.
|
|
33
|
+
*/
|
|
34
|
+
function resolveVisionProvider(gthConfig) {
|
|
35
|
+
if (gthConfig.modelProviderType)
|
|
36
|
+
return gthConfig.modelProviderType;
|
|
37
|
+
const llm = gthConfig.llm;
|
|
38
|
+
try {
|
|
39
|
+
if (typeof llm?._llmType === 'function')
|
|
40
|
+
return llm._llmType();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// A misbehaving _llmType must not break middleware resolution.
|
|
44
|
+
}
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
12
47
|
const predefinedMiddlewareFactories = {
|
|
13
48
|
/**
|
|
14
49
|
* Anthropic prompt caching middleware. see https://docs.langchain.com/oss/javascript/langchain/middleware#anthropic-prompt-caching
|
|
@@ -24,7 +59,20 @@ const predefinedMiddlewareFactories = {
|
|
|
24
59
|
* as HumanMessage content blocks before the next model call.
|
|
25
60
|
* This works around LangChain's limitation where ToolMessage doesn't support binary content.
|
|
26
61
|
*/
|
|
27
|
-
'binary-content-injection': (settings, gthConfig) => createBinaryContentInjectionMiddleware(
|
|
62
|
+
'binary-content-injection': (settings, gthConfig) => createBinaryContentInjectionMiddleware({
|
|
63
|
+
...settings,
|
|
64
|
+
provider: resolveVisionProvider(gthConfig),
|
|
65
|
+
}, gthConfig),
|
|
66
|
+
/**
|
|
67
|
+
* Frontend image injection middleware (RC-22). Converts a frontend capture tool's
|
|
68
|
+
* `{mimeType,data}` ToolMessage (default tool name `capture_image`) into a provider-appropriate
|
|
69
|
+
* vision HumanMessage the model can see. Strictly opt-in — resolved ONLY when named in
|
|
70
|
+
* `config.middleware` (no auto-inject branch). Optional `toolName` setting overrides the tool name.
|
|
71
|
+
*/
|
|
72
|
+
'frontend-image-injection': (settings, gthConfig) => Promise.resolve(createFrontendImageInjectionMiddleware({
|
|
73
|
+
provider: resolveVisionProvider(gthConfig),
|
|
74
|
+
toolName: typeof settings.toolName === 'string' ? settings.toolName : undefined,
|
|
75
|
+
})),
|
|
28
76
|
};
|
|
29
77
|
function isPredefinedMiddlewareName(name) {
|
|
30
78
|
return name in predefinedMiddlewareFactories;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/middleware/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,uCAAuC,CAAC;AACjE,OAAO,EACL,gCAAgC,EAChC,uBAAuB,GAExB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,sCAAsC,GAEvC,MAAM,qDAAqD,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/middleware/registry.ts"],"names":[],"mappings":"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"}
|