@gaunt-sloth/agent 2.0.0-alpha.22 → 2.0.0-alpha.24

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.
Files changed (53) hide show
  1. package/README.md +73 -24
  2. package/dist/core/GthDeepAgent.js +49 -2
  3. package/dist/core/GthDeepAgent.js.map +1 -1
  4. package/dist/core/debugCapture.d.ts +1 -1
  5. package/dist/core/debugCapture.js.map +1 -1
  6. package/dist/core/subagentProfiles.d.ts +50 -0
  7. package/dist/core/subagentProfiles.js +81 -0
  8. package/dist/core/subagentProfiles.js.map +1 -0
  9. package/dist/middleware/binaryContentInjectionMiddleware.d.ts +8 -1
  10. package/dist/middleware/binaryContentInjectionMiddleware.js +11 -2
  11. package/dist/middleware/binaryContentInjectionMiddleware.js.map +1 -1
  12. package/dist/middleware/frontendImageInjectionMiddleware.d.ts +80 -0
  13. package/dist/middleware/frontendImageInjectionMiddleware.js +146 -0
  14. package/dist/middleware/frontendImageInjectionMiddleware.js.map +1 -0
  15. package/dist/middleware/registry.js +36 -1
  16. package/dist/middleware/registry.js.map +1 -1
  17. package/dist/middleware/types.d.ts +16 -2
  18. package/dist/modules/a2a/A2AClientWrapper.d.ts +1 -1
  19. package/dist/modules/a2a/A2AClientWrapper.js +19 -5
  20. package/dist/modules/a2a/A2AClientWrapper.js.map +1 -1
  21. package/dist/modules/apiAgUiModule.d.ts +68 -0
  22. package/dist/modules/apiAgUiModule.js +95 -4
  23. package/dist/modules/apiAgUiModule.js.map +1 -1
  24. package/dist/modules/interactiveSessionModule.js +110 -34
  25. package/dist/modules/interactiveSessionModule.js.map +1 -1
  26. package/dist/modules/slashCommands.d.ts +328 -0
  27. package/dist/modules/slashCommands.js +598 -0
  28. package/dist/modules/slashCommands.js.map +1 -0
  29. package/dist/resolvers.js +15 -0
  30. package/dist/resolvers.js.map +1 -1
  31. package/dist/tools/GthCustomToolkit.js +72 -3
  32. package/dist/tools/GthCustomToolkit.js.map +1 -1
  33. package/dist/tools/GthDevToolkit.d.ts +5 -2
  34. package/dist/tools/GthDevToolkit.js +28 -7
  35. package/dist/tools/GthDevToolkit.js.map +1 -1
  36. package/dist/tools/GthFileSystemToolkit.d.ts +16 -0
  37. package/dist/tools/GthFileSystemToolkit.js +218 -110
  38. package/dist/tools/GthFileSystemToolkit.js.map +1 -1
  39. package/dist/tools/McpResourceTool.d.ts +31 -0
  40. package/dist/tools/McpResourceTool.js +106 -0
  41. package/dist/tools/McpResourceTool.js.map +1 -0
  42. package/dist/tools/shell/hardline.js +3 -3
  43. package/dist/tools/shell/hardline.js.map +1 -1
  44. package/package.json +2 -2
  45. package/dist/tools/shell/allowlist.d.ts +0 -11
  46. package/dist/tools/shell/allowlist.js +0 -12
  47. package/dist/tools/shell/allowlist.js.map +0 -1
  48. package/dist/tools/shell/arity.d.ts +0 -11
  49. package/dist/tools/shell/arity.js +0 -12
  50. package/dist/tools/shell/arity.js.map +0 -1
  51. package/dist/tools/shell/normalize.d.ts +0 -10
  52. package/dist/tools/shell/normalize.js +0 -11
  53. package/dist/tools/shell/normalize.js.map +0 -1
@@ -0,0 +1,146 @@
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` — all extend
28
+ * `ChatOpenAI` / report `_llmType()==='openai'`) → `{ type:'image_url', image_url:{ url:'<data-URL>' } }`.
29
+ * This native OpenAI shape is correct on BOTH the Completions API AND the Responses API (GS2-74
30
+ * flips reasoning-capable openai models to Responses). A raw `source_type` standard block
31
+ * serialises to an *invalid* image part on the Responses path, so we emit the provider-native
32
+ * shape rather than lean on `@langchain/core`'s (deprecated, internal) auto-conversion.
33
+ * - **anthropic / google-genai / vertexai** (and any unknown/default) → the LangChain standard
34
+ * base64 data content block `{ type:'image', source_type:'base64', mime_type, data }`, which
35
+ * those native converters decode directly and which is the most broadly decodable fallback.
36
+ *
37
+ * Pure and exported so each provider branch can be unit-tested directly.
38
+ */
39
+ export function imageBlockFor(provider, mimeType, data) {
40
+ const dataUrl = `data:${mimeType};base64,${data}`;
41
+ switch (provider) {
42
+ case 'ollama':
43
+ return { type: 'image_url', image_url: dataUrl };
44
+ case 'openai':
45
+ case 'openrouter':
46
+ case 'deepseek':
47
+ case 'xai':
48
+ case 'groq':
49
+ return { type: 'image_url', image_url: { url: dataUrl } };
50
+ case 'anthropic':
51
+ case 'google-genai':
52
+ case 'vertexai':
53
+ default:
54
+ return {
55
+ type: 'image',
56
+ source_type: 'base64',
57
+ mime_type: mimeType,
58
+ data,
59
+ };
60
+ }
61
+ }
62
+ /**
63
+ * Create the frontend-image-injection middleware.
64
+ *
65
+ * @param opts.provider - provider string selecting the vision-block shape (see {@link imageBlockFor}).
66
+ * @param opts.toolName - capture tool name (default {@link DEFAULT_CAPTURE_TOOL_NAME}).
67
+ */
68
+ export function createFrontendImageInjectionMiddleware(opts) {
69
+ const toolName = opts.toolName ?? DEFAULT_CAPTURE_TOOL_NAME;
70
+ // thread_id → set of tool_call_ids whose image (or error note) has already been injected. Without
71
+ // this, a summarizer/replayed history that retains the capture ToolMessage would re-inject its
72
+ // frame on the next turn, appended after the newest turn's content and mispairing the assistant
73
+ // message with a stale frame (the RC-21 idempotency guard).
74
+ //
75
+ // Closure-scoped (one Map per middleware instance), NOT a module global as the robot had it. The
76
+ // AG-UI server caches one agent per client-toolset signature (getAgentForTools), so a thread's
77
+ // turns share this instance and idempotency-across-turns is preserved — while unrelated agents (and
78
+ // tests) get independent state instead of a process-lifetime Map that accumulates every thread_id.
79
+ const injectedByThread = new Map();
80
+ return createMiddleware({
81
+ name: 'frontend-image-injection',
82
+ beforeModel: async (state, runtime) => {
83
+ const messages = state.messages || [];
84
+ // The AG-UI server sets runConfig.configurable.thread_id (= the run's threadId) and threads it
85
+ // to the graph, so per-session idempotency keys correctly. '__default__' is only reached off
86
+ // that path (e.g. a bare invoke with no thread_id), which still behaves correctly per-instance.
87
+ const threadId = runtime?.configurable?.thread_id ?? '__default__';
88
+ let injectedIds = injectedByThread.get(threadId);
89
+ if (!injectedIds) {
90
+ injectedIds = new Set();
91
+ injectedByThread.set(threadId, injectedIds);
92
+ }
93
+ // Scan forward so injected frames stay in chronological order; skip any tool_call_id already
94
+ // injected on this thread (idempotent across a retained/replayed tail).
95
+ const injected = [];
96
+ for (let i = 0; i < messages.length; i++) {
97
+ const msg = messages[i];
98
+ if (
99
+ // RC-21 (golden fix): use the duck-typed `isToolMessage`, NEVER `msg instanceof
100
+ // ToolMessage`. A consumer (galvanized/robot) importing this middleware across a `file:`-dep
101
+ // boundary resolves a SECOND @langchain/core copy, and a capture ToolMessage constructed by
102
+ // the AG-UI pipeline's core copy is not an instance of the `ToolMessage` class we import —
103
+ // `instanceof` silently returns false across copies and no frame is ever injected.
104
+ isToolMessage(msg) &&
105
+ typeof msg.content === 'string' &&
106
+ msg.name === toolName) {
107
+ const id = msg.tool_call_id;
108
+ if (!id || injectedIds.has(id))
109
+ continue;
110
+ try {
111
+ injected.push({ payload: JSON.parse(msg.content), id });
112
+ }
113
+ catch {
114
+ // Non-JSON tool result — skip injection (leave the guard clean).
115
+ }
116
+ }
117
+ }
118
+ if (injected.length === 0)
119
+ return undefined;
120
+ const newMessages = [...messages];
121
+ for (const { payload, id } of injected) {
122
+ if (payload.error) {
123
+ // Mark injected so the error note isn't re-emitted on a later turn.
124
+ injectedIds.add(id);
125
+ newMessages.push(new HumanMessage({ content: `Camera unavailable: ${payload.error}` }));
126
+ continue;
127
+ }
128
+ if (payload.mimeType && payload.data) {
129
+ // RC-21: mark injected ONLY on a successful frame emission. A capture result that arrived
130
+ // WITHOUT its base64 `data` (dropped upstream) injects nothing and must NOT poison the
131
+ // guard — a later data-bearing result for the same tool_call_id can still recover.
132
+ injectedIds.add(id);
133
+ const block = imageBlockFor(opts.provider, payload.mimeType, payload.data);
134
+ newMessages.push(new HumanMessage({
135
+ content: [{ type: 'text', text: 'Camera frame captured:' }, block],
136
+ }));
137
+ }
138
+ // else: a capture result whose `data` is absent — inject nothing, leave the guard clean.
139
+ }
140
+ // If every candidate was data-less (nothing appended), return no state update rather than an
141
+ // identical copy — keeps beforeModel a true no-op and leaves the guard clean for recovery.
142
+ return newMessages.length > messages.length ? { messages: newMessages } : undefined;
143
+ },
144
+ });
145
+ }
146
+ //# 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,28 @@ 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). The
18
+ * OpenAI-compatible shims (openrouter/deepseek/xai/groq) all report `_llmType() === 'openai'`, which
19
+ * maps to the same `image_url:{url}` block, so the fallback stays shape-correct.
20
+ */
21
+ function resolveVisionProvider(gthConfig) {
22
+ if (gthConfig.modelProviderType)
23
+ return gthConfig.modelProviderType;
24
+ const llm = gthConfig.llm;
25
+ try {
26
+ if (typeof llm?._llmType === 'function')
27
+ return llm._llmType();
28
+ }
29
+ catch {
30
+ // A misbehaving _llmType must not break middleware resolution.
31
+ }
32
+ return '';
33
+ }
12
34
  const predefinedMiddlewareFactories = {
13
35
  /**
14
36
  * Anthropic prompt caching middleware. see https://docs.langchain.com/oss/javascript/langchain/middleware#anthropic-prompt-caching
@@ -24,7 +46,20 @@ const predefinedMiddlewareFactories = {
24
46
  * as HumanMessage content blocks before the next model call.
25
47
  * This works around LangChain's limitation where ToolMessage doesn't support binary content.
26
48
  */
27
- 'binary-content-injection': (settings, gthConfig) => createBinaryContentInjectionMiddleware(settings, gthConfig),
49
+ 'binary-content-injection': (settings, gthConfig) => createBinaryContentInjectionMiddleware({
50
+ ...settings,
51
+ provider: resolveVisionProvider(gthConfig),
52
+ }, gthConfig),
53
+ /**
54
+ * Frontend image injection middleware (RC-22). Converts a frontend capture tool's
55
+ * `{mimeType,data}` ToolMessage (default tool name `capture_image`) into a provider-appropriate
56
+ * vision HumanMessage the model can see. Strictly opt-in — resolved ONLY when named in
57
+ * `config.middleware` (no auto-inject branch). Optional `toolName` setting overrides the tool name.
58
+ */
59
+ 'frontend-image-injection': (settings, gthConfig) => Promise.resolve(createFrontendImageInjectionMiddleware({
60
+ provider: resolveVisionProvider(gthConfig),
61
+ toolName: typeof settings.toolName === 'string' ? settings.toolName : undefined,
62
+ })),
28
63
  };
29
64
  function isPredefinedMiddlewareName(name) {
30
65
  return name in predefinedMiddlewareFactories;
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/middleware/registry.ts"],"names":[],"mappings":"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"}
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;AAC7D,OAAO,EAAE,sCAAsC,EAAE,MAAM,qDAAqD,CAAC;AAO7G;;;;;;;GAOG;AACH,SAAS,qBAAqB,CAAC,SAAoB;IACjD,IAAI,SAAS,CAAC,iBAAiB;QAAE,OAAO,SAAS,CAAC,iBAAiB,CAAC;IACpE,MAAM,GAAG,GAAG,SAAS,CAAC,GAA8C,CAAC;IACrE,IAAI,CAAC;QACH,IAAI,OAAO,GAAG,EAAE,QAAQ,KAAK,UAAU;YAAE,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,+DAA+D;IACjE,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,6BAA6B,GAAG;IACpC;;OAEG;IACH,0BAA0B,EAAE,CAC1B,QAAiC,EACjC,SAAoB,EACM,EAAE,CAC5B,sCAAsC,CAAC,QAAwC,EAAE,SAAS,CAAC;IAC7F;;OAEG;IACH,aAAa,EAAE,CACb,QAAiC,EACjC,SAAoB,EACM,EAAE,CAC5B,6BAA6B,CAAC,QAA+B,EAAE,SAAS,CAAC;IAC3E;;;;;OAKG;IACH,0BAA0B,EAAE,CAC1B,QAAiC,EACjC,SAAoB,EACM,EAAE,CAC5B,sCAAsC,CACpC;QACE,GAAI,QAAqD;QACzD,QAAQ,EAAE,qBAAqB,CAAC,SAAS,CAAC;KAC3C,EACD,SAAS,CACV;IACH;;;;;OAKG;IACH,0BAA0B,EAAE,CAC1B,QAAiC,EACjC,SAAoB,EACM,EAAE,CAC5B,OAAO,CAAC,OAAO,CACb,sCAAsC,CAAC;QACrC,QAAQ,EAAE,qBAAqB,CAAC,SAAS,CAAC;QAC1C,QAAQ,EAAE,OAAO,QAAQ,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;KAChF,CAAC,CACH;CACkD,CAAC;AAExD,SAAS,0BAA0B,CACjC,IAAY;IAEZ,OAAO,IAAI,IAAI,6BAA6B,CAAC;AAC/C,CAAC;AAED,SAAS,4BAA4B,CACnC,MAAwB;IAExB,OAAO,CACL,OAAO,MAAM,KAAK,QAAQ;QAC1B,MAAM,KAAK,IAAI;QACf,MAAM,IAAI,MAAM;QAChB,OAAQ,MAA4B,CAAC,IAAI,KAAK,QAAQ;QACtD,0BAA0B,CAAE,MAA2B,CAAC,IAAI,CAAC,CAC9D,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sCAAsC,CAC1D,MAAoC,EACpC,CAAY;IAEZ,QAAQ,CAAC,0DAA0D,MAAM,CAAC,GAAG,IAAI,SAAS,EAAE,CAAC,CAAC;IAE9F,0CAA0C;IAC1C,OAAO,OAAO,CAAC,OAAO,CAAC,gCAAgC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AAChF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,MAA2B,EAC3B,SAAoB;IAEpB,QAAQ,CAAC,mCAAmC,CAAC,CAAC;IAE9C,OAAO,OAAO,CAAC,OAAO,CACpB,uBAAuB,CAAC;QACtB,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,GAAG;QACpC,GAAG,MAAM;KACV,CAAC,CACH,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAuC,EACvC,SAAoB;IAEpB,MAAM,gBAAgB,GAAG,OAAO,IAAI,EAAE,CAAC;IACvC,MAAM,UAAU,GAAsB,EAAE,CAAC;IAEzC,iFAAiF;IACjF,wDAAwD;IACxD,MAAM,gBAAgB,GACpB,SAAS,CAAC,aAAa,KAAK,SAAS;QACrC,SAAS,CAAC,aAAa,KAAK,KAAK;QACjC,SAAS,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC;IACrC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC,IAAI,CAC/C,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,KAAK,0BAA0B;QAChC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,0BAA0B,CAAC,CAClF,CAAC;IAEF,IAAI,gBAAgB,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC7C,QAAQ,CAAC,+EAA+E,CAAC,CAAC;QAC1F,UAAU,CAAC,IAAI,CAAC,MAAM,0BAA0B,CAAC,0BAA0B,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;IAC/F,CAAC;IAED,KAAK,MAAM,MAAM,IAAI,gBAAgB,EAAE,CAAC;QACtC,IAAI,CAAC;YACH,oEAAoE;YACpE,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAC/B,UAAU,CAAC,IAAI,CAAC,MAAM,0BAA0B,CAAC,MAAM,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;YAC3E,CAAC;YACD,oDAAoD;iBAC/C,IAAI,4BAA4B,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC9C,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,CAAC;gBACrC,UAAU,CAAC,IAAI,CAAC,MAAM,0BAA0B,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;YAC/E,CAAC;YACD,mDAAmD;iBAC9C,CAAC;gBACJ,QAAQ,CAAC,0BAA0B,CAAC,CAAC;gBACrC,UAAU,CAAC,IAAI,CAAC,MAAyB,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,cAAc,CACZ,gCAAgC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACzF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,0BAA0B,CACvC,IAAY,EACZ,QAAiC,EACjC,SAAoB;IAEpB,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,OAAO,GAAG,6BAA6B,CAAC,IAAI,CAAC,CAAC;IACpD,OAAO,OAAO,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AACtC,CAAC"}
@@ -10,7 +10,7 @@ import { AgentMiddleware } from 'langchain';
10
10
  /**
11
11
  * Predefined middleware types that can be configured via JSON config.
12
12
  */
13
- export type PredefinedMiddlewareName = 'anthropic-prompt-caching' | 'summarization' | 'binary-content-injection';
13
+ export type PredefinedMiddlewareName = 'anthropic-prompt-caching' | 'summarization' | 'binary-content-injection' | 'frontend-image-injection';
14
14
  /**
15
15
  * Configuration for Anthropic prompt caching middleware.
16
16
  */
@@ -70,6 +70,18 @@ export interface ImageFormatTransformConfig {
70
70
  */
71
71
  export interface BinaryContentInjectionConfig {
72
72
  }
73
+ /**
74
+ * Configuration for frontend image injection middleware (RC-22).
75
+ * Converts a frontend capture tool's `{mimeType,data}` tool result into a vision HumanMessage the
76
+ * model can see. Opt-in — resolved only when named in `config.middleware`.
77
+ */
78
+ export interface FrontendImageInjectionConfig {
79
+ /**
80
+ * Name of the frontend capture tool whose result is converted into a vision message.
81
+ * Defaults to `capture_image`.
82
+ */
83
+ toolName?: string;
84
+ }
73
85
  /**
74
86
  * Union type of all predefined middleware configurations.
75
87
  */
@@ -79,7 +91,9 @@ export type PredefinedMiddlewareConfig = ({
79
91
  name: 'summarization';
80
92
  } & SummarizationConfig) | ({
81
93
  name: 'binary-content-injection';
82
- } & BinaryContentInjectionConfig);
94
+ } & BinaryContentInjectionConfig) | ({
95
+ name: 'frontend-image-injection';
96
+ } & FrontendImageInjectionConfig);
83
97
  /**
84
98
  * Middleware configuration that can be specified in JSON or JS config.
85
99
  * - String: Name of predefined middleware with default settings
@@ -31,7 +31,7 @@ export interface A2ASendContext {
31
31
  * @experimental
32
32
  */
33
33
  export declare class A2AClientWrapper {
34
- private client;
34
+ private clientPromise;
35
35
  private config;
36
36
  constructor(config: A2AClientConfig);
37
37
  /**
@@ -6,17 +6,29 @@
6
6
  import { A2AClient } from '@a2a-js/sdk/client';
7
7
  import { debugLog, debugLogError } from '@gaunt-sloth/core/utils/debugUtils.js';
8
8
  import { v4 as uuidv4 } from 'uuid';
9
+ /** The A2A spec's well-known agent-card path. `A2AClient.fromCardUrl` needs the full CARD url,
10
+ * whereas {@link A2AClientConfig.agentUrl} is the agent's BASE url — so we append this, exactly as
11
+ * the deprecated `new A2AClient(url)` string constructor did internally (its `resolveAgentCardUrl`
12
+ * used the same default). Keeps the fetched card URL byte-identical across the migration. */
13
+ const AGENT_CARD_PATH = '.well-known/agent-card.json';
9
14
  /**
10
15
  * Wrapper around the A2A SDK client for communicating with external agents.
11
16
  * @experimental
12
17
  */
13
18
  export class A2AClientWrapper {
14
- client;
19
+ clientPromise;
15
20
  config;
16
21
  constructor(config) {
17
22
  this.config = config;
18
- // Initialize with agent URL. The SDK will fetch the agent card.
19
- this.client = new A2AClient(config.agentUrl);
23
+ // Construct via the non-deprecated `A2AClient.fromCardUrl(cardUrl)` (the string `new
24
+ // A2AClient(url)` constructor is deprecated and console.warns). `fromCardUrl` is async, so we
25
+ // hold the promise and await it per send. The agent card is fetched from the well-known path
26
+ // appended to the base agent URL — reproducing the old constructor's fetch target exactly.
27
+ const cardUrl = `${config.agentUrl.replace(/\/+$/, '')}/${AGENT_CARD_PATH}`;
28
+ this.clientPromise = A2AClient.fromCardUrl(cardUrl);
29
+ // Guard against an unhandled rejection if the wrapper is constructed but never used; real
30
+ // callers still observe the rejection when they await `clientPromise` inside a send method.
31
+ void this.clientPromise.catch(() => undefined);
20
32
  }
21
33
  /**
22
34
  * Sends a message to the A2A agent and returns the response.
@@ -26,8 +38,9 @@ export class A2AClientWrapper {
26
38
  async sendMessage(messageText) {
27
39
  debugLog(`Sending message to A2A agent ${this.config.agentId} at ${this.config.agentUrl}: ${messageText}`);
28
40
  try {
41
+ const client = await this.clientPromise;
29
42
  const messageId = uuidv4();
30
- const response = await this.client.sendMessage({
43
+ const response = await client.sendMessage({
31
44
  message: {
32
45
  kind: 'message',
33
46
  messageId: messageId,
@@ -82,7 +95,8 @@ export class A2AClientWrapper {
82
95
  `${this.config.agentUrl}: ${messageText}` +
83
96
  (context?.contextId ? ` [contextId=${context.contextId}]` : ''));
84
97
  try {
85
- const response = await this.client.sendMessage({
98
+ const client = await this.clientPromise;
99
+ const response = await client.sendMessage({
86
100
  message: {
87
101
  kind: 'message',
88
102
  messageId: uuidv4(),
@@ -1 +1 @@
1
- {"version":3,"file":"A2AClientWrapper.js","sourceRoot":"","sources":["../../../src/modules/a2a/A2AClientWrapper.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"A2AClientWrapper.js","sourceRoot":"","sources":["../../../src/modules/a2a/A2AClientWrapper.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,70 @@
1
1
  import { GthConfig } from '@gaunt-sloth/core/config.js';
2
+ import type { BaseMessage } from '@langchain/core/messages';
3
+ /** An AG-UI wire message as received on the run input (the shape {@link convertMessage} accepts). */
4
+ type AgUiWireMessage = {
5
+ role: string;
6
+ content?: string;
7
+ id: string;
8
+ toolCalls?: Array<{
9
+ id: string;
10
+ type: string;
11
+ function: {
12
+ name: string;
13
+ arguments: string;
14
+ };
15
+ }>;
16
+ toolCallId?: string;
17
+ };
18
+ /** Per-message options for {@link convertMessage}. */
19
+ interface ConvertMessageOptions {
20
+ /**
21
+ * Whether an assistant text-emitted tool call may be PROMOTED to a native tool_call for this
22
+ * message. Defaults to `true` (the EXT-35 behaviour). {@link convertMessages} sets this to `false`
23
+ * for a DANGLING history call (one not followed by its tool result) so a stalled replayed call
24
+ * stays plain text — see EXT-43 and that function's doc.
25
+ */
26
+ allowTextCallPromotion?: boolean;
27
+ }
28
+ /**
29
+ * Convert AG-UI message format to LangChain BaseMessage.
30
+ *
31
+ * `allowedToolNames` is the set of tool names bound to this run (config.tools + any run-input
32
+ * client tools). It gates EXT-35 plain-text tool-call repair on the assistant branch: an incoming
33
+ * assistant message with NO native `toolCalls` whose content is a STANDALONE text-emitted call
34
+ * (bracket / `<function=…>` / Harmony — the dialects small/local models produce) is promoted to a
35
+ * native tool_call so a replayed history turn is a real tool call rather than inert prose. An empty
36
+ * (or absent) allow-list promotes nothing — the prose-safe default. This runs alongside
37
+ * {@link parseToolArguments} (which rescues malformed args on an ALREADY-native tool_call).
38
+ *
39
+ * EXT-43: `options.allowTextCallPromotion` (default `true`) lets a caller suppress promotion for a
40
+ * single message; {@link convertMessages} uses it to leave a DANGLING history call as text.
41
+ */
42
+ export declare function convertMessage(msg: AgUiWireMessage, allowedToolNames?: Set<string>, options?: ConvertMessageOptions): BaseMessage;
43
+ /**
44
+ * Convert a whole AG-UI history array to LangChain messages, applying TWO symmetric replay guards
45
+ * so a poisoned history can never abort every subsequent turn on the thread.
46
+ *
47
+ * EXT-43 (forward, dangling-CALL): EXT-35's per-message promotion is unconditional, which is correct
48
+ * for a call that WILL be executed this turn. But when replaying HISTORY, promoting a STALLED text
49
+ * call (one the client recorded but that never ran) yields an `AIMessage` with `tool_calls` and NO
50
+ * following `tool_result` — a shape a strict provider (Anthropic) 400s on, where the pre-EXT-35
51
+ * plain text was valid. So promotion is allowed ONLY when the assistant message is immediately
52
+ * followed by a `tool` result message; a dangling call stays plain text (`allowTextCallPromotion:
53
+ * false`).
54
+ *
55
+ * RC-18 (backward, orphan-RESULT): the mirror image. A replayed `role:'tool'` message whose matching
56
+ * `tool_call` id is absent from EVERY PRECEDING assistant message is an ORPHAN — converting it to a
57
+ * `ToolMessage` yields a tool result with no preceding `AIMessage.tool_calls`, which the same strict
58
+ * provider 400s on (`Invalid parameter: messages with role 'tool' must be a response to a preceding
59
+ * message with 'tool_calls'`, INVALID_TOOL_RESULTS). Such orphans arise when a terminal
60
+ * (`returnDirect`) tool call's result is reconstructed by the client without its parenting assistant
61
+ * `tool_call`. We DROP the orphan (match on tool_call_id, NOT adjacency; keep genuine pairs; do NOT
62
+ * fabricate a synthetic call — mirroring EXT-43's demote-don't-invent spirit). Ids are accumulated
63
+ * in iteration order, so a result whose matching call appears only LATER is still an orphan.
64
+ *
65
+ * The live middleware path (`GthLangChainAgent`, fixing the CURRENT turn) is unaffected — both
66
+ * guards are history-replay only.
67
+ */
68
+ export declare function convertMessages(messages: AgUiWireMessage[], allowedToolNames?: Set<string>): BaseMessage[];
2
69
  export declare function startAgUiServer(config: GthConfig, port: number): Promise<void>;
70
+ export {};
@@ -6,6 +6,7 @@ import { GthDeepAgent } from '#src/core/GthDeepAgent.js';
6
6
  import { GthLangChainAgent } from '@gaunt-sloth/core/core/GthLangChainAgent.js';
7
7
  import { defaultStatusCallback, displayInfo, displayWarning, } from '@gaunt-sloth/core/utils/consoleUtils.js';
8
8
  import { getNewRunnableConfig } from '@gaunt-sloth/core/utils/llmUtils.js';
9
+ import { textToNativeToolCalls } from '@gaunt-sloth/core/core/toolCallRepair/index.js';
9
10
  import { HumanMessage, AIMessage, SystemMessage, ToolMessage } from '@langchain/core/messages';
10
11
  import { MemorySaver } from '@langchain/langgraph';
11
12
  import { tool } from '@langchain/core/tools';
@@ -97,9 +98,20 @@ function parseToolArguments(raw, toolName) {
97
98
  }
98
99
  }
99
100
  /**
100
- * Convert AG-UI message format to LangChain BaseMessage
101
+ * Convert AG-UI message format to LangChain BaseMessage.
102
+ *
103
+ * `allowedToolNames` is the set of tool names bound to this run (config.tools + any run-input
104
+ * client tools). It gates EXT-35 plain-text tool-call repair on the assistant branch: an incoming
105
+ * assistant message with NO native `toolCalls` whose content is a STANDALONE text-emitted call
106
+ * (bracket / `<function=…>` / Harmony — the dialects small/local models produce) is promoted to a
107
+ * native tool_call so a replayed history turn is a real tool call rather than inert prose. An empty
108
+ * (or absent) allow-list promotes nothing — the prose-safe default. This runs alongside
109
+ * {@link parseToolArguments} (which rescues malformed args on an ALREADY-native tool_call).
110
+ *
111
+ * EXT-43: `options.allowTextCallPromotion` (default `true`) lets a caller suppress promotion for a
112
+ * single message; {@link convertMessages} uses it to leave a DANGLING history call as text.
101
113
  */
102
- function convertMessage(msg) {
114
+ export function convertMessage(msg, allowedToolNames, options) {
103
115
  const content = typeof msg.content === 'string' ? msg.content : '';
104
116
  switch (msg.role) {
105
117
  case 'user':
@@ -116,6 +128,17 @@ function convertMessage(msg) {
116
128
  })),
117
129
  });
118
130
  }
131
+ // EXT-35: no native tool_calls — a small/local model may have emitted the call as assistant
132
+ // TEXT. Promote a standalone text-emitted call (gated by the bound-tool allow-list + payload
133
+ // cap + standalone-only) to a native tool_call; otherwise fall through to plain text.
134
+ // EXT-43: `allowTextCallPromotion === false` (a dangling history call) short-circuits the
135
+ // promotion so the message stays plain text.
136
+ const repairedToolCalls = options?.allowTextCallPromotion !== false && allowedToolNames && allowedToolNames.size > 0
137
+ ? textToNativeToolCalls(content, { allowedToolNames })
138
+ : undefined;
139
+ if (repairedToolCalls) {
140
+ return new AIMessage({ content: '', tool_calls: repairedToolCalls });
141
+ }
119
142
  return new AIMessage(content);
120
143
  }
121
144
  case 'system':
@@ -127,6 +150,65 @@ function convertMessage(msg) {
127
150
  return new HumanMessage(content);
128
151
  }
129
152
  }
153
+ /**
154
+ * Convert a whole AG-UI history array to LangChain messages, applying TWO symmetric replay guards
155
+ * so a poisoned history can never abort every subsequent turn on the thread.
156
+ *
157
+ * EXT-43 (forward, dangling-CALL): EXT-35's per-message promotion is unconditional, which is correct
158
+ * for a call that WILL be executed this turn. But when replaying HISTORY, promoting a STALLED text
159
+ * call (one the client recorded but that never ran) yields an `AIMessage` with `tool_calls` and NO
160
+ * following `tool_result` — a shape a strict provider (Anthropic) 400s on, where the pre-EXT-35
161
+ * plain text was valid. So promotion is allowed ONLY when the assistant message is immediately
162
+ * followed by a `tool` result message; a dangling call stays plain text (`allowTextCallPromotion:
163
+ * false`).
164
+ *
165
+ * RC-18 (backward, orphan-RESULT): the mirror image. A replayed `role:'tool'` message whose matching
166
+ * `tool_call` id is absent from EVERY PRECEDING assistant message is an ORPHAN — converting it to a
167
+ * `ToolMessage` yields a tool result with no preceding `AIMessage.tool_calls`, which the same strict
168
+ * provider 400s on (`Invalid parameter: messages with role 'tool' must be a response to a preceding
169
+ * message with 'tool_calls'`, INVALID_TOOL_RESULTS). Such orphans arise when a terminal
170
+ * (`returnDirect`) tool call's result is reconstructed by the client without its parenting assistant
171
+ * `tool_call`. We DROP the orphan (match on tool_call_id, NOT adjacency; keep genuine pairs; do NOT
172
+ * fabricate a synthetic call — mirroring EXT-43's demote-don't-invent spirit). Ids are accumulated
173
+ * in iteration order, so a result whose matching call appears only LATER is still an orphan.
174
+ *
175
+ * The live middleware path (`GthLangChainAgent`, fixing the CURRENT turn) is unaffected — both
176
+ * guards are history-replay only.
177
+ */
178
+ export function convertMessages(messages, allowedToolNames) {
179
+ // Ids of tool calls emitted by PRECEDING assistant messages, accumulated as we iterate in order.
180
+ // A `tool` result whose tool_call_id is not yet in this set has no preceding assistant tool_call.
181
+ const seenToolCallIds = new Set();
182
+ const converted = [];
183
+ messages.forEach((msg, index) => {
184
+ // RC-18 backward orphan-RESULT guard.
185
+ if (msg.role === 'tool') {
186
+ const toolCallId = msg.toolCallId || msg.id;
187
+ if (!seenToolCallIds.has(toolCallId)) {
188
+ displayWarning(`Dropping orphan tool result (tool_call_id ${JSON.stringify(toolCallId)}) with no ` +
189
+ 'preceding assistant tool_call in the replayed history; converting it would 400 the ' +
190
+ 'provider (INVALID_TOOL_RESULTS).');
191
+ return; // drop — do not convert to a ToolMessage
192
+ }
193
+ converted.push(convertMessage(msg, allowedToolNames));
194
+ return;
195
+ }
196
+ // Record this assistant's native tool_call ids BEFORE any following tool result is checked, so
197
+ // a genuine call→result pair (id present on a preceding assistant) survives the guard above.
198
+ if (msg.role === 'assistant' && msg.toolCalls) {
199
+ for (const tc of msg.toolCalls) {
200
+ if (tc?.id)
201
+ seenToolCallIds.add(tc.id);
202
+ }
203
+ }
204
+ // EXT-43 forward dangling-CALL guard (PRESERVED, unchanged).
205
+ const followedByToolResult = messages[index + 1]?.role === 'tool';
206
+ converted.push(convertMessage(msg, allowedToolNames, {
207
+ allowTextCallPromotion: followedByToolResult,
208
+ }));
209
+ });
210
+ return converted;
211
+ }
130
212
  /**
131
213
  * Construct the AG-UI agent for the configured backend (B5).
132
214
  * - `agent.backend: 'deep'` → {@link GthDeepAgent} (deepagents runtime, experimental).
@@ -215,6 +297,13 @@ export async function startAgUiServer(config, port) {
215
297
  // the server's statically-configured agent.
216
298
  const hasClientTools = Array.isArray(tools) && tools.length > 0;
217
299
  const activeAgent = hasClientTools ? await getAgentForTools(tools) : agent;
300
+ // EXT-35: the names of the tools bound to THIS run (server config.tools + any run-input client
301
+ // tools) — the allow-list for plain-text tool-call repair in convertMessage. Only a text-emitted
302
+ // call naming one of these is promoted to a native tool_call; an empty set promotes nothing.
303
+ const allowedToolNames = new Set([
304
+ ...(config.tools ?? []).map((t) => t?.name),
305
+ ...(hasClientTools ? tools.map((t) => t.name) : []),
306
+ ].filter((name) => Boolean(name)));
218
307
  const encoder = new EventEncoder({ accept: req.headers.accept });
219
308
  res.setHeader('Content-Type', encoder.getContentType());
220
309
  res.setHeader('Cache-Control', 'no-cache');
@@ -290,7 +379,7 @@ export async function startAgUiServer(config, port) {
290
379
  ? queued
291
380
  .map((s) => typeof s === 'string'
292
381
  ? new HumanMessage(s)
293
- : convertMessage(s))
382
+ : convertMessage(s, allowedToolNames))
294
383
  .filter((m) => Boolean(m))
295
384
  : [];
296
385
  eventStream = activeAgent.streamWithEventsResume(forwardedProps.command.resume, runConfig, queuedMessages, ac.signal);
@@ -299,7 +388,9 @@ export async function startAgUiServer(config, port) {
299
388
  // The system prompt (backstory + guidelines + mode prompt + identity) lives in the
300
389
  // deep-agent graph via createDeepAgent({ systemPrompt }) — see GthDeepAgent — so it is no
301
390
  // longer prepended here. A separate, non-first SystemMessage would be rejected by Anthropic.
302
- const langChainMessages = (messages || []).map(convertMessage);
391
+ // EXT-43: convertMessages (not a bare map) applies the dangling-call guard so a stalled
392
+ // text call replayed in history is not promoted to a native tool_call with no result.
393
+ const langChainMessages = convertMessages((messages || []), allowedToolNames);
303
394
  eventStream = activeAgent.streamWithEvents(langChainMessages, runConfig, ac.signal);
304
395
  }
305
396
  for await (const event of eventStream) {