@gaunt-sloth/agent 2.0.0-alpha.23 → 2.0.0-alpha.25
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 -24
- package/dist/core/GthDeepAgent.d.ts +3 -3
- package/dist/core/GthDeepAgent.js +82 -35
- package/dist/core/GthDeepAgent.js.map +1 -1
- package/dist/core/debugCapture.d.ts +1 -1
- package/dist/core/debugCapture.js.map +1 -1
- package/dist/core/subagentProfiles.d.ts +50 -0
- package/dist/core/subagentProfiles.js +81 -0
- package/dist/core/subagentProfiles.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 +80 -0
- package/dist/middleware/frontendImageInjectionMiddleware.js +146 -0
- package/dist/middleware/frontendImageInjectionMiddleware.js.map +1 -0
- package/dist/middleware/registry.js +36 -1
- package/dist/middleware/registry.js.map +1 -1
- package/dist/middleware/types.d.ts +16 -2
- package/dist/modules/a2a/A2AClientWrapper.d.ts +15 -6
- package/dist/modules/a2a/A2AClientWrapper.js +93 -64
- package/dist/modules/a2a/A2AClientWrapper.js.map +1 -1
- package/dist/modules/apiAgUiModule.d.ts +68 -0
- package/dist/modules/apiAgUiModule.js +95 -4
- package/dist/modules/apiAgUiModule.js.map +1 -1
- package/dist/modules/interactiveSessionModule.js +199 -43
- package/dist/modules/interactiveSessionModule.js.map +1 -1
- package/dist/modules/slashCommands.d.ts +467 -0
- package/dist/modules/slashCommands.js +888 -0
- package/dist/modules/slashCommands.js.map +1 -0
- package/dist/resolvers.js +15 -0
- package/dist/resolvers.js.map +1 -1
- package/dist/tools/GthCustomToolkit.js +72 -3
- package/dist/tools/GthCustomToolkit.js.map +1 -1
- package/dist/tools/GthDevToolkit.d.ts +6 -3
- package/dist/tools/GthDevToolkit.js +31 -11
- package/dist/tools/GthDevToolkit.js.map +1 -1
- package/dist/tools/GthFileSystemToolkit.d.ts +16 -0
- package/dist/tools/GthFileSystemToolkit.js +218 -110
- 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/shell/env.js +1 -1
- package/dist/tools/shell/hardline.d.ts +33 -2
- package/dist/tools/shell/hardline.js +525 -40
- package/dist/tools/shell/hardline.js.map +1 -1
- package/package.json +8 -8
- 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/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,80 @@
|
|
|
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` — all extend
|
|
42
|
+
* `ChatOpenAI` / report `_llmType()==='openai'`) → `{ type:'image_url', image_url:{ url:'<data-URL>' } }`.
|
|
43
|
+
* This native OpenAI shape is correct on BOTH the Completions API AND the Responses API (GS2-74
|
|
44
|
+
* flips reasoning-capable openai models to Responses). A raw `source_type` standard block
|
|
45
|
+
* serialises to an *invalid* image part on the Responses path, so we emit the provider-native
|
|
46
|
+
* shape rather than lean on `@langchain/core`'s (deprecated, internal) auto-conversion.
|
|
47
|
+
* - **anthropic / google-genai / vertexai** (and any unknown/default) → the LangChain standard
|
|
48
|
+
* base64 data content block `{ type:'image', source_type:'base64', mime_type, data }`, which
|
|
49
|
+
* those native converters decode directly and which is the most broadly decodable fallback.
|
|
50
|
+
*
|
|
51
|
+
* Pure and exported so each provider branch can be unit-tested directly.
|
|
52
|
+
*/
|
|
53
|
+
export declare function imageBlockFor(provider: string, mimeType: string, data: string): {
|
|
54
|
+
type: 'image_url';
|
|
55
|
+
image_url: string;
|
|
56
|
+
source_type?: undefined;
|
|
57
|
+
mime_type?: undefined;
|
|
58
|
+
data?: undefined;
|
|
59
|
+
} | {
|
|
60
|
+
type: 'image_url';
|
|
61
|
+
image_url: {
|
|
62
|
+
url: string;
|
|
63
|
+
};
|
|
64
|
+
source_type?: undefined;
|
|
65
|
+
mime_type?: undefined;
|
|
66
|
+
data?: undefined;
|
|
67
|
+
} | {
|
|
68
|
+
image_url?: undefined;
|
|
69
|
+
type: 'image';
|
|
70
|
+
source_type: 'base64';
|
|
71
|
+
mime_type: string;
|
|
72
|
+
data: string;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Create the frontend-image-injection middleware.
|
|
76
|
+
*
|
|
77
|
+
* @param opts.provider - provider string selecting the vision-block shape (see {@link imageBlockFor}).
|
|
78
|
+
* @param opts.toolName - capture tool name (default {@link DEFAULT_CAPTURE_TOOL_NAME}).
|
|
79
|
+
*/
|
|
80
|
+
export declare function createFrontendImageInjectionMiddleware(opts: FrontendImageInjectionOptions): AgentMiddleware;
|
|
@@ -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":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,gBAAgB,EAAwB,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAavE,6FAA6F;AAC7F,MAAM,CAAC,MAAM,yBAAyB,GAAG,eAAe,CAAC;AAkBzD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,QAAgB,EAAE,IAAY;IAC5E,MAAM,OAAO,GAAG,QAAQ,QAAQ,WAAW,IAAI,EAAE,CAAC;IAClD,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,QAAQ;YACX,OAAO,EAAE,IAAI,EAAE,WAAoB,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;QAC5D,KAAK,QAAQ,CAAC;QACd,KAAK,YAAY,CAAC;QAClB,KAAK,UAAU,CAAC;QAChB,KAAK,KAAK,CAAC;QACX,KAAK,MAAM;YACT,OAAO,EAAE,IAAI,EAAE,WAAoB,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC;QACrE,KAAK,WAAW,CAAC;QACjB,KAAK,cAAc,CAAC;QACpB,KAAK,UAAU,CAAC;QAChB;YACE,OAAO;gBACL,IAAI,EAAE,OAAgB;gBACtB,WAAW,EAAE,QAAiB;gBAC9B,SAAS,EAAE,QAAQ;gBACnB,IAAI;aACL,CAAC;IACN,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sCAAsC,CACpD,IAAmC;IAEnC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,yBAAyB,CAAC;IAE5D,kGAAkG;IAClG,+FAA+F;IAC/F,gGAAgG;IAChG,4DAA4D;IAC5D,EAAE;IACF,iGAAiG;IACjG,+FAA+F;IAC/F,oGAAoG;IACpG,mGAAmG;IACnG,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAuB,CAAC;IAExD,OAAO,gBAAgB,CAAC;QACtB,IAAI,EAAE,0BAA0B;QAEhC,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACpC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC;YACtC,+FAA+F;YAC/F,6FAA6F;YAC7F,gGAAgG;YAChG,MAAM,QAAQ,GAAG,OAAO,EAAE,YAAY,EAAE,SAAS,IAAI,aAAa,CAAC;YACnE,IAAI,WAAW,GAAG,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACjD,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;gBAChC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;YAC9C,CAAC;YAED,6FAA6F;YAC7F,wEAAwE;YACxE,MAAM,QAAQ,GAAiD,EAAE,CAAC;YAClE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACzC,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACxB;gBACE,gFAAgF;gBAChF,6FAA6F;gBAC7F,4FAA4F;gBAC5F,2FAA2F;gBAC3F,mFAAmF;gBACnF,aAAa,CAAC,GAAG,CAAC;oBAClB,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;oBAC/B,GAAG,CAAC,IAAI,KAAK,QAAQ,EACrB,CAAC;oBACD,MAAM,EAAE,GAAG,GAAG,CAAC,YAAY,CAAC;oBAC5B,IAAI,CAAC,EAAE,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;wBAAE,SAAS;oBACzC,IAAI,CAAC;wBACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAiB,EAAE,EAAE,EAAE,CAAC,CAAC;oBAC1E,CAAC;oBAAC,MAAM,CAAC;wBACP,iEAAiE;oBACnE,CAAC;gBACH,CAAC;YACH,CAAC;YAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,SAAS,CAAC;YAE5C,MAAM,WAAW,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;YAClC,KAAK,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,QAAQ,EAAE,CAAC;gBACvC,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;oBAClB,oEAAoE;oBACpE,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBACpB,WAAW,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,EAAE,OAAO,EAAE,uBAAuB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;oBACxF,SAAS;gBACX,CAAC;gBACD,IAAI,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;oBACrC,0FAA0F;oBAC1F,uFAAuF;oBACvF,mFAAmF;oBACnF,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBACpB,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;oBAC3E,WAAW,CAAC,IAAI,CACd,IAAI,YAAY,CAAC;wBACf,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,EAAE,EAAE,KAAK,CAAmB;qBACrF,CAAC,CACH,CAAC;gBACJ,CAAC;gBACD,yFAAyF;YAC3F,CAAC;YAED,6FAA6F;YAC7F,2FAA2F;YAC3F,OAAO,WAAW,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QACtF,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -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(
|
|
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":"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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"}
|
|
@@ -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
|
|
34
|
+
private clientPromise;
|
|
35
35
|
private config;
|
|
36
36
|
constructor(config: A2AClientConfig);
|
|
37
37
|
/**
|
|
@@ -42,12 +42,12 @@ export declare class A2AClientWrapper {
|
|
|
42
42
|
sendMessage(messageText: string): Promise<string>;
|
|
43
43
|
/**
|
|
44
44
|
* BATCH-14 — send one message and return its text ALONG WITH the A2A `contextId`/`taskId` needed to
|
|
45
|
-
* thread a multi-turn conversation. Reads the real `@a2a-js/sdk` result shapes rather than
|
|
46
|
-
* loose envelope
|
|
45
|
+
* thread a multi-turn conversation. Reads the real `@a2a-js/sdk` result shapes rather than a
|
|
46
|
+
* loose envelope:
|
|
47
47
|
*
|
|
48
|
-
* - A **Message** result
|
|
49
|
-
*
|
|
50
|
-
* - A **Task** result
|
|
48
|
+
* - A **Message** result carries its text in `parts` and `contextId`/`taskId` directly (both
|
|
49
|
+
* optional, and empty-string when absent in the v1.0 proto model).
|
|
50
|
+
* - A **Task** result — what Google ADK agents commonly return — carries text in
|
|
51
51
|
* `status.message.parts` (falling back to the last artifact's parts), `contextId` (required), and
|
|
52
52
|
* the taskId as `Task.id`.
|
|
53
53
|
*
|
|
@@ -59,9 +59,18 @@ export declare class A2AClientWrapper {
|
|
|
59
59
|
* @returns The agent's text answer plus the context/task ids from its response.
|
|
60
60
|
*/
|
|
61
61
|
sendMessageWithContext(messageText: string, context?: A2ASendContext): Promise<A2AMessageResult>;
|
|
62
|
+
/** Build the outgoing `SendMessageRequest`. The v1.0 data model is generated from the A2A
|
|
63
|
+
* protobufs, so every field is required rather than optional: `role` is the numeric {@link Role}
|
|
64
|
+
* enum (not `'user'`), a text part is `{ content: { $case: 'text', value } }` (not
|
|
65
|
+
* `{ kind: 'text', text }`), and the continuity handles are empty strings rather than absent
|
|
66
|
+
* when there is no conversation to continue. */
|
|
67
|
+
private static buildSendRequest;
|
|
62
68
|
/** Normalize a `Message | Task` A2A result into text + continuity handles (BATCH-14). Static +
|
|
63
69
|
* tolerant of missing optional fields so it can be reused and unit-tested directly. */
|
|
64
70
|
private static extractResult;
|
|
71
|
+
/** v1.0 dropped the `kind` discriminator that told a Message from a Task, so discriminate
|
|
72
|
+
* structurally: `messageId` is required on every Message and never present on a Task. */
|
|
73
|
+
private static isTask;
|
|
65
74
|
/** Concatenate the text of every {@link https://github.com/a2aproject/A2A TextPart} in `parts`
|
|
66
75
|
* (ignoring file/data parts), or `''` when there is no text part. */
|
|
67
76
|
private static extractText;
|
|
@@ -3,20 +3,48 @@
|
|
|
3
3
|
* Wrapper for A2A (Agent-to-Agent) protocol client.
|
|
4
4
|
* @experimental A2A support is experimental and may change.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { ClientFactory, ClientFactoryOptions, DefaultAgentCardResolver, JsonRpcTransportFactory, RestTransportFactory, } from '@a2a-js/sdk/client';
|
|
7
|
+
import { Role } from '@a2a-js/sdk';
|
|
7
8
|
import { debugLog, debugLogError } from '@gaunt-sloth/core/utils/debugUtils.js';
|
|
8
9
|
import { v4 as uuidv4 } from 'uuid';
|
|
10
|
+
/** The A2A spec's well-known agent-card path. The SDK's resolver needs the full CARD url, whereas
|
|
11
|
+
* {@link A2AClientConfig.agentUrl} is the agent's BASE url — so we append this ourselves and hand
|
|
12
|
+
* the resolver an empty relative path, keeping the fetched card URL byte-identical to what the
|
|
13
|
+
* pre-1.0 `A2AClient` string constructor produced. (Letting the resolver append its own default
|
|
14
|
+
* would resolve the path RELATIVE to the base url, silently dropping a base path segment: for
|
|
15
|
+
* `http://host/a2a` it would fetch `http://host/.well-known/agent-card.json`.) */
|
|
16
|
+
const AGENT_CARD_PATH = '.well-known/agent-card.json';
|
|
9
17
|
/**
|
|
10
18
|
* Wrapper around the A2A SDK client for communicating with external agents.
|
|
11
19
|
* @experimental
|
|
12
20
|
*/
|
|
13
21
|
export class A2AClientWrapper {
|
|
14
|
-
|
|
22
|
+
clientPromise;
|
|
15
23
|
config;
|
|
16
24
|
constructor(config) {
|
|
17
25
|
this.config = config;
|
|
18
|
-
//
|
|
19
|
-
|
|
26
|
+
// `@a2a-js/sdk` 1.0 removed the `A2AClient` class outright; a client is now built by a
|
|
27
|
+
// `ClientFactory` from the agent card. `legacyCompat` is enabled on the card resolver AND on
|
|
28
|
+
// every transport factory so we keep talking to agents still on protocol v0.3 — which is what
|
|
29
|
+
// Google ADK agents (our only real A2A peer today, see adk-eval-it/) currently speak. With it
|
|
30
|
+
// on, the resolver detects a v0.3-shaped card, translates it to the v1.0 model and stamps each
|
|
31
|
+
// synthesized interface `protocolVersion: '0.3'`, and the matching factory then instantiates
|
|
32
|
+
// the v0.3 wire transport. A v1.0 agent is served by the native transports unchanged, so this
|
|
33
|
+
// wrapper speaks to both.
|
|
34
|
+
const factory = new ClientFactory(ClientFactoryOptions.createFrom(ClientFactoryOptions.default, {
|
|
35
|
+
cardResolver: new DefaultAgentCardResolver({ legacyCompat: { enabled: true } }),
|
|
36
|
+
transports: [
|
|
37
|
+
new JsonRpcTransportFactory({ legacyCompat: { enabled: true } }),
|
|
38
|
+
new RestTransportFactory({ legacyCompat: { enabled: true } }),
|
|
39
|
+
],
|
|
40
|
+
}));
|
|
41
|
+
// Client construction is async (it fetches the card), so we hold the promise and await it per
|
|
42
|
+
// send. Empty path = "the base url IS the card url" — see AGENT_CARD_PATH.
|
|
43
|
+
const cardUrl = `${config.agentUrl.replace(/\/+$/, '')}/${AGENT_CARD_PATH}`;
|
|
44
|
+
this.clientPromise = factory.createFromUrl(cardUrl, '');
|
|
45
|
+
// Guard against an unhandled rejection if the wrapper is constructed but never used; real
|
|
46
|
+
// callers still observe the rejection when they await `clientPromise` inside a send method.
|
|
47
|
+
void this.clientPromise.catch(() => undefined);
|
|
20
48
|
}
|
|
21
49
|
/**
|
|
22
50
|
* Sends a message to the A2A agent and returns the response.
|
|
@@ -26,33 +54,10 @@ export class A2AClientWrapper {
|
|
|
26
54
|
async sendMessage(messageText) {
|
|
27
55
|
debugLog(`Sending message to A2A agent ${this.config.agentId} at ${this.config.agentUrl}: ${messageText}`);
|
|
28
56
|
try {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
messageId: messageId,
|
|
34
|
-
role: 'user',
|
|
35
|
-
parts: [{ kind: 'text', text: messageText }],
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
debugLog(`Received response from A2A agent: ${JSON.stringify(response)}`);
|
|
39
|
-
// Check for error response
|
|
40
|
-
if ('error' in response) {
|
|
41
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
|
-
throw new Error(`A2A Error: ${JSON.stringify(response.error)}`);
|
|
43
|
-
}
|
|
44
|
-
// Extract text from response
|
|
45
|
-
// The result is likely a TaskStatus which contains a message
|
|
46
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
-
const result = response.result;
|
|
48
|
-
if (result.message && result.message.parts && result.message.parts.length > 0) {
|
|
49
|
-
// Assuming the first part is text for now
|
|
50
|
-
return result.message.parts[0].text || JSON.stringify(result.message.parts);
|
|
51
|
-
}
|
|
52
|
-
else if (result.state) {
|
|
53
|
-
return `Task state: ${result.state}`;
|
|
54
|
-
}
|
|
55
|
-
return JSON.stringify(result);
|
|
57
|
+
const client = await this.clientPromise;
|
|
58
|
+
const result = await client.sendMessage(A2AClientWrapper.buildSendRequest(messageText, uuidv4()));
|
|
59
|
+
debugLog(`Received response from A2A agent: ${JSON.stringify(result)}`);
|
|
60
|
+
return A2AClientWrapper.extractResult(result).text;
|
|
56
61
|
}
|
|
57
62
|
catch (error) {
|
|
58
63
|
debugLogError('Error sending message to A2A agent', error);
|
|
@@ -61,12 +66,12 @@ export class A2AClientWrapper {
|
|
|
61
66
|
}
|
|
62
67
|
/**
|
|
63
68
|
* BATCH-14 — send one message and return its text ALONG WITH the A2A `contextId`/`taskId` needed to
|
|
64
|
-
* thread a multi-turn conversation. Reads the real `@a2a-js/sdk` result shapes rather than
|
|
65
|
-
* loose envelope
|
|
69
|
+
* thread a multi-turn conversation. Reads the real `@a2a-js/sdk` result shapes rather than a
|
|
70
|
+
* loose envelope:
|
|
66
71
|
*
|
|
67
|
-
* - A **Message** result
|
|
68
|
-
*
|
|
69
|
-
* - A **Task** result
|
|
72
|
+
* - A **Message** result carries its text in `parts` and `contextId`/`taskId` directly (both
|
|
73
|
+
* optional, and empty-string when absent in the v1.0 proto model).
|
|
74
|
+
* - A **Task** result — what Google ADK agents commonly return — carries text in
|
|
70
75
|
* `status.message.parts` (falling back to the last artifact's parts), `contextId` (required), and
|
|
71
76
|
* the taskId as `Task.id`.
|
|
72
77
|
*
|
|
@@ -82,23 +87,9 @@ export class A2AClientWrapper {
|
|
|
82
87
|
`${this.config.agentUrl}: ${messageText}` +
|
|
83
88
|
(context?.contextId ? ` [contextId=${context.contextId}]` : ''));
|
|
84
89
|
try {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
messageId: uuidv4(),
|
|
89
|
-
role: 'user',
|
|
90
|
-
parts: [{ kind: 'text', text: messageText }],
|
|
91
|
-
// Attach continuity handles only when present, so a first-turn send is unchanged.
|
|
92
|
-
...(context?.contextId ? { contextId: context.contextId } : {}),
|
|
93
|
-
...(context?.taskId ? { taskId: context.taskId } : {}),
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
debugLog(`Received response from A2A agent: ${JSON.stringify(response)}`);
|
|
97
|
-
if ('error' in response) {
|
|
98
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
99
|
-
throw new Error(`A2A Error: ${JSON.stringify(response.error)}`);
|
|
100
|
-
}
|
|
101
|
-
const result = response.result;
|
|
90
|
+
const client = await this.clientPromise;
|
|
91
|
+
const result = await client.sendMessage(A2AClientWrapper.buildSendRequest(messageText, uuidv4(), context));
|
|
92
|
+
debugLog(`Received response from A2A agent: ${JSON.stringify(result)}`);
|
|
102
93
|
return A2AClientWrapper.extractResult(result);
|
|
103
94
|
}
|
|
104
95
|
catch (error) {
|
|
@@ -106,19 +97,57 @@ export class A2AClientWrapper {
|
|
|
106
97
|
throw error;
|
|
107
98
|
}
|
|
108
99
|
}
|
|
100
|
+
/** Build the outgoing `SendMessageRequest`. The v1.0 data model is generated from the A2A
|
|
101
|
+
* protobufs, so every field is required rather than optional: `role` is the numeric {@link Role}
|
|
102
|
+
* enum (not `'user'`), a text part is `{ content: { $case: 'text', value } }` (not
|
|
103
|
+
* `{ kind: 'text', text }`), and the continuity handles are empty strings rather than absent
|
|
104
|
+
* when there is no conversation to continue. */
|
|
105
|
+
static buildSendRequest(messageText, messageId, context) {
|
|
106
|
+
const message = {
|
|
107
|
+
messageId,
|
|
108
|
+
contextId: context?.contextId ?? '',
|
|
109
|
+
taskId: context?.taskId ?? '',
|
|
110
|
+
role: Role.ROLE_USER,
|
|
111
|
+
parts: [
|
|
112
|
+
{
|
|
113
|
+
content: { $case: 'text', value: messageText },
|
|
114
|
+
metadata: undefined,
|
|
115
|
+
filename: '',
|
|
116
|
+
mediaType: 'text/plain',
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
metadata: undefined,
|
|
120
|
+
extensions: [],
|
|
121
|
+
referenceTaskIds: [],
|
|
122
|
+
};
|
|
123
|
+
return { tenant: '', message, configuration: undefined, metadata: undefined };
|
|
124
|
+
}
|
|
109
125
|
/** Normalize a `Message | Task` A2A result into text + continuity handles (BATCH-14). Static +
|
|
110
126
|
* tolerant of missing optional fields so it can be reused and unit-tested directly. */
|
|
111
127
|
static extractResult(result) {
|
|
112
|
-
if (result
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
128
|
+
if (A2AClientWrapper.isTask(result)) {
|
|
129
|
+
const statusParts = result.status?.message?.parts;
|
|
130
|
+
const artifactParts = result.artifacts?.[result.artifacts.length - 1]?.parts;
|
|
131
|
+
const text = A2AClientWrapper.extractText(statusParts ?? artifactParts) || JSON.stringify(result);
|
|
132
|
+
return {
|
|
133
|
+
text,
|
|
134
|
+
contextId: result.contextId || undefined,
|
|
135
|
+
taskId: result.id || undefined,
|
|
136
|
+
};
|
|
118
137
|
}
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
138
|
+
const text = A2AClientWrapper.extractText(result?.parts) || JSON.stringify(result);
|
|
139
|
+
// The proto model uses '' (not undefined) for an absent id; normalize back to undefined so a
|
|
140
|
+
// caller can't thread an empty handle into the next turn.
|
|
141
|
+
return {
|
|
142
|
+
text,
|
|
143
|
+
contextId: result?.contextId || undefined,
|
|
144
|
+
taskId: result?.taskId || undefined,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/** v1.0 dropped the `kind` discriminator that told a Message from a Task, so discriminate
|
|
148
|
+
* structurally: `messageId` is required on every Message and never present on a Task. */
|
|
149
|
+
static isTask(result) {
|
|
150
|
+
return typeof result?.messageId !== 'string';
|
|
122
151
|
}
|
|
123
152
|
/** Concatenate the text of every {@link https://github.com/a2aproject/A2A TextPart} in `parts`
|
|
124
153
|
* (ignoring file/data parts), or `''` when there is no text part. */
|
|
@@ -126,8 +155,8 @@ export class A2AClientWrapper {
|
|
|
126
155
|
if (!parts || parts.length === 0)
|
|
127
156
|
return '';
|
|
128
157
|
return parts
|
|
129
|
-
.filter((part) => part?.
|
|
130
|
-
.map((part) => part.
|
|
158
|
+
.filter((part) => part?.content?.$case === 'text')
|
|
159
|
+
.map((part) => part.content?.value)
|
|
131
160
|
.join('\n');
|
|
132
161
|
}
|
|
133
162
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"A2AClientWrapper.js","sourceRoot":"","sources":["../../../src/modules/a2a/A2AClientWrapper.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,
|
|
1
|
+
{"version":3,"file":"A2AClientWrapper.js","sourceRoot":"","sources":["../../../src/modules/a2a/A2AClientWrapper.ts"],"names":[],"mappings":"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"}
|