@gaunt-sloth/agent 2.0.0-alpha.9 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/README.md +73 -23
  2. package/cli-acp.js +21 -24
  3. package/dist/builtInToolsConfig.d.ts +17 -3
  4. package/dist/builtInToolsConfig.js +43 -41
  5. package/dist/builtInToolsConfig.js.map +1 -1
  6. package/dist/core/debugCapture.d.ts +7 -49
  7. package/dist/core/debugCapture.js +1 -1
  8. package/dist/core/debugCapture.js.map +1 -1
  9. package/dist/core/resolveAgentFactory.d.ts +10 -9
  10. package/dist/core/resolveAgentFactory.js +11 -12
  11. package/dist/core/resolveAgentFactory.js.map +1 -1
  12. package/dist/index.d.ts +4 -4
  13. package/dist/index.js +7 -5
  14. package/dist/index.js.map +1 -1
  15. package/dist/mcp/OAuthClientProviderImpl.js.map +1 -1
  16. package/dist/mcp/tlsTrust.d.ts +61 -0
  17. package/dist/mcp/tlsTrust.js +109 -0
  18. package/dist/mcp/tlsTrust.js.map +1 -0
  19. package/dist/middleware/binaryContentInjectionMiddleware.d.ts +8 -1
  20. package/dist/middleware/binaryContentInjectionMiddleware.js +11 -2
  21. package/dist/middleware/binaryContentInjectionMiddleware.js.map +1 -1
  22. package/dist/middleware/frontendImageInjectionMiddleware.d.ts +107 -0
  23. package/dist/middleware/frontendImageInjectionMiddleware.js +167 -0
  24. package/dist/middleware/frontendImageInjectionMiddleware.js.map +1 -0
  25. package/dist/middleware/registry.js +49 -1
  26. package/dist/middleware/registry.js.map +1 -1
  27. package/dist/middleware/types.d.ts +16 -2
  28. package/dist/modules/a2a/A2AClientWrapper.d.ts +56 -1
  29. package/dist/modules/a2a/A2AClientWrapper.js +131 -31
  30. package/dist/modules/a2a/A2AClientWrapper.js.map +1 -1
  31. package/dist/modules/acp/acpAgentApp.d.ts +57 -0
  32. package/dist/modules/acp/acpAgentApp.js +378 -0
  33. package/dist/modules/acp/acpAgentApp.js.map +1 -0
  34. package/dist/modules/acp/acpAgentAppV1.d.ts +52 -0
  35. package/dist/modules/acp/acpAgentAppV1.js +307 -0
  36. package/dist/modules/acp/acpAgentAppV1.js.map +1 -0
  37. package/dist/modules/acp/acpCommon.d.ts +167 -0
  38. package/dist/modules/acp/acpCommon.js +282 -0
  39. package/dist/modules/acp/acpCommon.js.map +1 -0
  40. package/dist/modules/acp/acpPermissions.d.ts +65 -0
  41. package/dist/modules/acp/acpPermissions.js +162 -0
  42. package/dist/modules/acp/acpPermissions.js.map +1 -0
  43. package/dist/modules/acp/acpPermissionsV1.d.ts +45 -0
  44. package/dist/modules/acp/acpPermissionsV1.js +110 -0
  45. package/dist/modules/acp/acpPermissionsV1.js.map +1 -0
  46. package/dist/modules/acp/acpRouter.d.ts +41 -0
  47. package/dist/modules/acp/acpRouter.js +48 -0
  48. package/dist/modules/acp/acpRouter.js.map +1 -0
  49. package/dist/modules/acp/acpStdio.d.ts +47 -0
  50. package/dist/modules/acp/acpStdio.js +71 -0
  51. package/dist/modules/acp/acpStdio.js.map +1 -0
  52. package/dist/modules/acp/acpToolCalls.d.ts +93 -0
  53. package/dist/modules/acp/acpToolCalls.js +193 -0
  54. package/dist/modules/acp/acpToolCalls.js.map +1 -0
  55. package/dist/modules/acp/acpUpdates.d.ts +48 -0
  56. package/dist/modules/acp/acpUpdates.js +142 -0
  57. package/dist/modules/acp/acpUpdates.js.map +1 -0
  58. package/dist/modules/acp/acpUpdatesV1.d.ts +61 -0
  59. package/dist/modules/acp/acpUpdatesV1.js +162 -0
  60. package/dist/modules/acp/acpUpdatesV1.js.map +1 -0
  61. package/dist/modules/apiAgUiModule.d.ts +87 -0
  62. package/dist/modules/apiAgUiModule.js +193 -28
  63. package/dist/modules/apiAgUiModule.js.map +1 -1
  64. package/dist/modules/interactiveSessionModule.js +497 -51
  65. package/dist/modules/interactiveSessionModule.js.map +1 -1
  66. package/dist/modules/slashCommands.d.ts +582 -0
  67. package/dist/modules/slashCommands.js +1001 -0
  68. package/dist/modules/slashCommands.js.map +1 -0
  69. package/dist/resolvers.js +119 -9
  70. package/dist/resolvers.js.map +1 -1
  71. package/dist/tools/GthCustomToolkit.js +95 -11
  72. package/dist/tools/GthCustomToolkit.js.map +1 -1
  73. package/dist/tools/GthDevToolkit.d.ts +8 -5
  74. package/dist/tools/GthDevToolkit.js +89 -43
  75. package/dist/tools/GthDevToolkit.js.map +1 -1
  76. package/dist/tools/GthFileSystemToolkit.d.ts +63 -0
  77. package/dist/tools/GthFileSystemToolkit.js +506 -105
  78. package/dist/tools/GthFileSystemToolkit.js.map +1 -1
  79. package/dist/tools/McpResourceTool.d.ts +31 -0
  80. package/dist/tools/McpResourceTool.js +106 -0
  81. package/dist/tools/McpResourceTool.js.map +1 -0
  82. package/dist/tools/gthChecklistTool.d.ts +30 -0
  83. package/dist/tools/gthChecklistTool.js +80 -0
  84. package/dist/tools/gthChecklistTool.js.map +1 -0
  85. package/dist/tools/gthGrepTool.d.ts +54 -0
  86. package/dist/tools/gthGrepTool.js +482 -0
  87. package/dist/tools/gthGrepTool.js.map +1 -0
  88. package/dist/tools/shell/env.js +1 -1
  89. package/dist/tools/shell/env.js.map +1 -1
  90. package/dist/tools/shell/outputBuffer.js.map +1 -1
  91. package/dist/tools/shell/workDir.d.ts +1 -10
  92. package/dist/tools/shell/workDir.js +10 -36
  93. package/dist/tools/shell/workDir.js.map +1 -1
  94. package/dist/utils/mcpAuthError.d.ts +36 -0
  95. package/dist/utils/mcpAuthError.js +89 -0
  96. package/dist/utils/mcpAuthError.js.map +1 -0
  97. package/package.json +13 -13
  98. package/dist/core/GthDeepAgent.d.ts +0 -181
  99. package/dist/core/GthDeepAgent.js +0 -682
  100. package/dist/core/GthDeepAgent.js.map +0 -1
  101. package/dist/core/deepAgentPermissions.d.ts +0 -115
  102. package/dist/core/deepAgentPermissions.js +0 -282
  103. package/dist/core/deepAgentPermissions.js.map +0 -1
  104. package/dist/core/gthAcpServer.d.ts +0 -27
  105. package/dist/core/gthAcpServer.js +0 -82
  106. package/dist/core/gthAcpServer.js.map +0 -1
  107. package/dist/core/gthDeepAgentFactory.d.ts +0 -8
  108. package/dist/core/gthDeepAgentFactory.js +0 -9
  109. package/dist/core/gthDeepAgentFactory.js.map +0 -1
  110. package/dist/modules/acpModule.d.ts +0 -40
  111. package/dist/modules/acpModule.js +0 -78
  112. package/dist/modules/acpModule.js.map +0 -1
  113. package/dist/tools/shell/allowlist.d.ts +0 -11
  114. package/dist/tools/shell/allowlist.js +0 -12
  115. package/dist/tools/shell/allowlist.js.map +0 -1
  116. package/dist/tools/shell/arity.d.ts +0 -11
  117. package/dist/tools/shell/arity.js +0 -12
  118. package/dist/tools/shell/arity.js.map +0 -1
  119. package/dist/tools/shell/hardline.d.ts +0 -15
  120. package/dist/tools/shell/hardline.js +0 -88
  121. package/dist/tools/shell/hardline.js.map +0 -1
  122. package/dist/tools/shell/normalize.d.ts +0 -10
  123. package/dist/tools/shell/normalize.js +0 -11
  124. package/dist/tools/shell/normalize.js.map +0 -1
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Translates the agent runtime's typed {@link AgentStreamEvent} stream into ACP **v2**
4
+ * `session/update` payloads. (`acpUpdatesV1.ts` is the v1 half; both extend the tool-call tracker
5
+ * in `acpToolCalls.ts`, which is where the parts that are not about the dialect live.)
6
+ *
7
+ * Kept as a pure, stateful-but-transport-free mapper rather than inlined in the request handlers
8
+ * for two reasons. It is the half of the ACP surface with real logic — message identity, tool-call
9
+ * upserts, which events open and close a run — so it is the half worth testing without a
10
+ * connection. And the `session/update` **upsert semantics** live here and nowhere else: what the
11
+ * mapper omits is what a client must leave unchanged, so a mapper that re-sent a full replacement
12
+ * on every update would silently erase fields a client had already rendered.
13
+ *
14
+ * ## The upsert contract this mapper is written against
15
+ *
16
+ * A client applies updates per id, in arrival order: an omitted field leaves the stored value
17
+ * unchanged, `null` clears it, a concrete value replaces it, and a chunk appends. The first
18
+ * `tool_call_update` a client sees for a `toolCallId` CREATES the tool call. So the mapper sends
19
+ * the descriptive fields **once**, on the creating update, and every later update for that call
20
+ * carries only what actually changed — which is what makes a client's rendering of a running tool
21
+ * call correct rather than flickering back to a bare id.
22
+ */
23
+ import type { SessionUpdate } from '@agentclientprotocol/sdk/experimental/v2';
24
+ import type { AgentStreamEvent } from '@gaunt-sloth/core/core/types.js';
25
+ import { AcpToolCallTracker } from '#src/modules/acp/acpToolCalls.js';
26
+ /**
27
+ * Turns one agent run's event stream into ACP v2 `session/update` payloads.
28
+ *
29
+ * One instance per prompt turn: it holds the message identity of the assistant text run and of the
30
+ * reasoning run, plus the accumulated argument text per tool call. A fresh instance per turn is
31
+ * what makes a new turn a new `messageId`, which is how a client tells two messages apart.
32
+ */
33
+ export declare class AcpUpdateMapper extends AcpToolCallTracker {
34
+ /**
35
+ * `messageId` of the assistant text message currently being streamed, or `null` when no text run
36
+ * is open. Cleared whenever something else interrupts the text (a tool call, a reasoning block),
37
+ * so the text that resumes afterwards is a NEW message rather than an append to the one the
38
+ * client already considers finished.
39
+ */
40
+ private assistantMessageId;
41
+ /** `messageId` of the reasoning message currently being streamed, or `null` outside one. */
42
+ private thoughtMessageId;
43
+ /**
44
+ * The `session/update` payloads one runtime event produces — usually one, sometimes none
45
+ * (`tool_args`, which only accumulates; the reasoning boundaries, which only move state).
46
+ */
47
+ map(event: AgentStreamEvent): SessionUpdate[];
48
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Translates the agent runtime's typed {@link AgentStreamEvent} stream into ACP **v2**
4
+ * `session/update` payloads. (`acpUpdatesV1.ts` is the v1 half; both extend the tool-call tracker
5
+ * in `acpToolCalls.ts`, which is where the parts that are not about the dialect live.)
6
+ *
7
+ * Kept as a pure, stateful-but-transport-free mapper rather than inlined in the request handlers
8
+ * for two reasons. It is the half of the ACP surface with real logic — message identity, tool-call
9
+ * upserts, which events open and close a run — so it is the half worth testing without a
10
+ * connection. And the `session/update` **upsert semantics** live here and nowhere else: what the
11
+ * mapper omits is what a client must leave unchanged, so a mapper that re-sent a full replacement
12
+ * on every update would silently erase fields a client had already rendered.
13
+ *
14
+ * ## The upsert contract this mapper is written against
15
+ *
16
+ * A client applies updates per id, in arrival order: an omitted field leaves the stored value
17
+ * unchanged, `null` clears it, a concrete value replaces it, and a chunk appends. The first
18
+ * `tool_call_update` a client sees for a `toolCallId` CREATES the tool call. So the mapper sends
19
+ * the descriptive fields **once**, on the creating update, and every later update for that call
20
+ * carries only what actually changed — which is what makes a client's rendering of a running tool
21
+ * call correct rather than flickering back to a bare id.
22
+ */
23
+ import { randomUUID } from 'node:crypto';
24
+ import { AcpToolCallTracker, toolKindFor } from '#src/modules/acp/acpToolCalls.js';
25
+ /** One text content block, the shape both message chunks and tool content wrap. */
26
+ function textBlock(text) {
27
+ return { type: 'text', text };
28
+ }
29
+ /**
30
+ * Turns one agent run's event stream into ACP v2 `session/update` payloads.
31
+ *
32
+ * One instance per prompt turn: it holds the message identity of the assistant text run and of the
33
+ * reasoning run, plus the accumulated argument text per tool call. A fresh instance per turn is
34
+ * what makes a new turn a new `messageId`, which is how a client tells two messages apart.
35
+ */
36
+ export class AcpUpdateMapper extends AcpToolCallTracker {
37
+ /**
38
+ * `messageId` of the assistant text message currently being streamed, or `null` when no text run
39
+ * is open. Cleared whenever something else interrupts the text (a tool call, a reasoning block),
40
+ * so the text that resumes afterwards is a NEW message rather than an append to the one the
41
+ * client already considers finished.
42
+ */
43
+ assistantMessageId = null;
44
+ /** `messageId` of the reasoning message currently being streamed, or `null` outside one. */
45
+ thoughtMessageId = null;
46
+ /**
47
+ * The `session/update` payloads one runtime event produces — usually one, sometimes none
48
+ * (`tool_args`, which only accumulates; the reasoning boundaries, which only move state).
49
+ */
50
+ map(event) {
51
+ switch (event.type) {
52
+ case 'text': {
53
+ this.assistantMessageId ??= randomUUID();
54
+ return [
55
+ {
56
+ sessionUpdate: 'agent_message_chunk',
57
+ messageId: this.assistantMessageId,
58
+ content: textBlock(event.delta),
59
+ },
60
+ ];
61
+ }
62
+ case 'reasoning_start': {
63
+ this.assistantMessageId = null;
64
+ this.thoughtMessageId = randomUUID();
65
+ return [];
66
+ }
67
+ case 'reasoning_delta': {
68
+ this.thoughtMessageId ??= randomUUID();
69
+ return [
70
+ {
71
+ sessionUpdate: 'agent_thought_chunk',
72
+ messageId: this.thoughtMessageId,
73
+ content: textBlock(event.delta),
74
+ },
75
+ ];
76
+ }
77
+ case 'reasoning_end': {
78
+ this.thoughtMessageId = null;
79
+ return [];
80
+ }
81
+ case 'tool_start': {
82
+ // A tool call ends the open text run: the text that follows the tool is a separate message.
83
+ this.assistantMessageId = null;
84
+ this.trackToolStart(event.id, event.name);
85
+ // The CREATING update — the first one a client sees for this id. Everything descriptive is
86
+ // sent here and never resent, because from here on omission means "unchanged".
87
+ return [
88
+ {
89
+ sessionUpdate: 'tool_call_update',
90
+ toolCallId: event.id,
91
+ name: event.name,
92
+ title: event.name,
93
+ kind: toolKindFor(event.name),
94
+ status: 'pending',
95
+ },
96
+ ];
97
+ }
98
+ case 'tool_args': {
99
+ this.appendToolArgs(event.id, event.delta);
100
+ return [];
101
+ }
102
+ case 'tool_end': {
103
+ const rawInput = this.rawInputFor(event.id);
104
+ // Status only (plus the arguments, once they are complete). No title, no kind, no name —
105
+ // the client keeps the ones the creating update set.
106
+ return [
107
+ {
108
+ sessionUpdate: 'tool_call_update',
109
+ toolCallId: event.id,
110
+ status: 'in_progress',
111
+ ...(rawInput === undefined ? {} : { rawInput }),
112
+ },
113
+ ];
114
+ }
115
+ case 'tool_output': {
116
+ // Live output from an executing tool. A CHUNK, not an update: it appends to whatever the
117
+ // client has for this call, where a `tool_call_update` carrying `content` would replace it.
118
+ if (event.id === undefined)
119
+ return [];
120
+ return [
121
+ {
122
+ sessionUpdate: 'tool_call_content_chunk',
123
+ toolCallId: event.id,
124
+ content: { type: 'content', content: textBlock(event.chunk) },
125
+ },
126
+ ];
127
+ }
128
+ case 'tool_result': {
129
+ this.trackToolSettled(event.id);
130
+ return [
131
+ {
132
+ sessionUpdate: 'tool_call_update',
133
+ toolCallId: event.id,
134
+ status: event.isError ? 'failed' : 'completed',
135
+ content: [{ type: 'content', content: textBlock(event.content) }],
136
+ },
137
+ ];
138
+ }
139
+ }
140
+ }
141
+ }
142
+ //# sourceMappingURL=acpUpdates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acpUpdates.js","sourceRoot":"","sources":["../../../src/modules/acp/acpUpdates.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Translates the agent runtime's typed {@link AgentStreamEvent} stream into ACP **v1**
4
+ * `session/update` payloads. (`acpUpdates.ts` is the v2 half.)
5
+ *
6
+ * ## Where v1 differs, and why the mapper could not be shared
7
+ *
8
+ * The two dialects report the same events, but not with the same messages, and each difference
9
+ * changes what a conforming client ends up rendering:
10
+ *
11
+ * - **v1 has a distinct `tool_call` update that CREATES a tool call**, where v2 folded creation into
12
+ * the first `tool_call_update`. Sending only `tool_call_update`s on v1 would leave a client
13
+ * patching a call it was never told about.
14
+ * - **v1 has no `tool_call_content_chunk`.** Its `tool_call_update.content` REPLACES the whole
15
+ * collection, so live tool output is streamed by accumulating it here and resending the
16
+ * collection — the opposite of v2, where each chunk appends and only a `tool_call_update` replaces.
17
+ * - **v1 has no `state_update`.** There is nowhere to report `running` / `requires_action` / `idle`,
18
+ * and no notification carries the stop reason: the turn's outcome is the `session/prompt`
19
+ * RESPONSE. See `acpAgentAppV1.ts`.
20
+ * - **A prompt turn does not echo the user's message.** v1 reserves replay for `session/load`; a
21
+ * client already renders what it sent, so echoing it would draw the message twice.
22
+ *
23
+ * What is NOT duplicated is tool-call identity — the kind hint, the argument reassembly, and the
24
+ * pairing that lets a permission request name the call it is about all come from
25
+ * {@link AcpToolCallTracker}.
26
+ */
27
+ import type { SessionUpdate } from '@agentclientprotocol/sdk';
28
+ import type { AgentStreamEvent } from '@gaunt-sloth/core/core/types.js';
29
+ import { AcpToolCallTracker } from '#src/modules/acp/acpToolCalls.js';
30
+ /**
31
+ * Turns one agent run's event stream into ACP v1 `session/update` payloads.
32
+ *
33
+ * One instance per prompt turn: it holds the message identity of the assistant text run and of the
34
+ * reasoning run, the accumulated argument text per tool call, and the output accumulated for each
35
+ * running tool. A fresh instance per turn is what makes a new turn a new `messageId`, which is how
36
+ * a client tells two messages apart.
37
+ */
38
+ export declare class AcpV1UpdateMapper extends AcpToolCallTracker {
39
+ /**
40
+ * `messageId` of the assistant text message currently being streamed, or `null` when no text run
41
+ * is open. Cleared whenever something else interrupts the text (a tool call, a reasoning block),
42
+ * so the text that resumes afterwards is a NEW message rather than an append to the one the
43
+ * client already considers finished.
44
+ */
45
+ private assistantMessageId;
46
+ /** `messageId` of the reasoning message currently being streamed, or `null` outside one. */
47
+ private thoughtMessageId;
48
+ /**
49
+ * Live output accumulated per tool call, because v1 can only REPLACE a tool call's content.
50
+ *
51
+ * With no append-a-chunk update in the dialect, the only way to show a tool's output as it
52
+ * arrives is to resend everything seen so far; keeping the collection here is what makes each
53
+ * replacement a superset of the last rather than a flicker back to the newest line alone.
54
+ */
55
+ private readonly toolOutput;
56
+ /**
57
+ * The `session/update` payloads one runtime event produces — usually one, sometimes none
58
+ * (`tool_args`, which only accumulates; the reasoning boundaries, which only move state).
59
+ */
60
+ map(event: AgentStreamEvent): SessionUpdate[];
61
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Translates the agent runtime's typed {@link AgentStreamEvent} stream into ACP **v1**
4
+ * `session/update` payloads. (`acpUpdates.ts` is the v2 half.)
5
+ *
6
+ * ## Where v1 differs, and why the mapper could not be shared
7
+ *
8
+ * The two dialects report the same events, but not with the same messages, and each difference
9
+ * changes what a conforming client ends up rendering:
10
+ *
11
+ * - **v1 has a distinct `tool_call` update that CREATES a tool call**, where v2 folded creation into
12
+ * the first `tool_call_update`. Sending only `tool_call_update`s on v1 would leave a client
13
+ * patching a call it was never told about.
14
+ * - **v1 has no `tool_call_content_chunk`.** Its `tool_call_update.content` REPLACES the whole
15
+ * collection, so live tool output is streamed by accumulating it here and resending the
16
+ * collection — the opposite of v2, where each chunk appends and only a `tool_call_update` replaces.
17
+ * - **v1 has no `state_update`.** There is nowhere to report `running` / `requires_action` / `idle`,
18
+ * and no notification carries the stop reason: the turn's outcome is the `session/prompt`
19
+ * RESPONSE. See `acpAgentAppV1.ts`.
20
+ * - **A prompt turn does not echo the user's message.** v1 reserves replay for `session/load`; a
21
+ * client already renders what it sent, so echoing it would draw the message twice.
22
+ *
23
+ * What is NOT duplicated is tool-call identity — the kind hint, the argument reassembly, and the
24
+ * pairing that lets a permission request name the call it is about all come from
25
+ * {@link AcpToolCallTracker}.
26
+ */
27
+ import { randomUUID } from 'node:crypto';
28
+ import { AcpToolCallTracker, toolKindFor } from '#src/modules/acp/acpToolCalls.js';
29
+ /** One text content block, the shape both message chunks and tool content wrap. */
30
+ function textBlock(text) {
31
+ return { type: 'text', text };
32
+ }
33
+ /** One tool-call content entry wrapping a text block. */
34
+ function toolText(text) {
35
+ return { type: 'content', content: textBlock(text) };
36
+ }
37
+ /**
38
+ * Turns one agent run's event stream into ACP v1 `session/update` payloads.
39
+ *
40
+ * One instance per prompt turn: it holds the message identity of the assistant text run and of the
41
+ * reasoning run, the accumulated argument text per tool call, and the output accumulated for each
42
+ * running tool. A fresh instance per turn is what makes a new turn a new `messageId`, which is how
43
+ * a client tells two messages apart.
44
+ */
45
+ export class AcpV1UpdateMapper extends AcpToolCallTracker {
46
+ /**
47
+ * `messageId` of the assistant text message currently being streamed, or `null` when no text run
48
+ * is open. Cleared whenever something else interrupts the text (a tool call, a reasoning block),
49
+ * so the text that resumes afterwards is a NEW message rather than an append to the one the
50
+ * client already considers finished.
51
+ */
52
+ assistantMessageId = null;
53
+ /** `messageId` of the reasoning message currently being streamed, or `null` outside one. */
54
+ thoughtMessageId = null;
55
+ /**
56
+ * Live output accumulated per tool call, because v1 can only REPLACE a tool call's content.
57
+ *
58
+ * With no append-a-chunk update in the dialect, the only way to show a tool's output as it
59
+ * arrives is to resend everything seen so far; keeping the collection here is what makes each
60
+ * replacement a superset of the last rather than a flicker back to the newest line alone.
61
+ */
62
+ toolOutput = new Map();
63
+ /**
64
+ * The `session/update` payloads one runtime event produces — usually one, sometimes none
65
+ * (`tool_args`, which only accumulates; the reasoning boundaries, which only move state).
66
+ */
67
+ map(event) {
68
+ switch (event.type) {
69
+ case 'text': {
70
+ this.assistantMessageId ??= randomUUID();
71
+ return [
72
+ {
73
+ sessionUpdate: 'agent_message_chunk',
74
+ messageId: this.assistantMessageId,
75
+ content: textBlock(event.delta),
76
+ },
77
+ ];
78
+ }
79
+ case 'reasoning_start': {
80
+ this.assistantMessageId = null;
81
+ this.thoughtMessageId = randomUUID();
82
+ return [];
83
+ }
84
+ case 'reasoning_delta': {
85
+ this.thoughtMessageId ??= randomUUID();
86
+ return [
87
+ {
88
+ sessionUpdate: 'agent_thought_chunk',
89
+ messageId: this.thoughtMessageId,
90
+ content: textBlock(event.delta),
91
+ },
92
+ ];
93
+ }
94
+ case 'reasoning_end': {
95
+ this.thoughtMessageId = null;
96
+ return [];
97
+ }
98
+ case 'tool_start': {
99
+ // A tool call ends the open text run: the text that follows the tool is a separate message.
100
+ this.assistantMessageId = null;
101
+ this.trackToolStart(event.id, event.name);
102
+ this.toolOutput.set(event.id, []);
103
+ // v1's CREATE. `title` is required here, and everything descriptive is sent with it; the
104
+ // later `tool_call_update`s carry only what changed.
105
+ return [
106
+ {
107
+ sessionUpdate: 'tool_call',
108
+ toolCallId: event.id,
109
+ name: event.name,
110
+ title: event.name,
111
+ kind: toolKindFor(event.name),
112
+ status: 'pending',
113
+ },
114
+ ];
115
+ }
116
+ case 'tool_args': {
117
+ this.appendToolArgs(event.id, event.delta);
118
+ return [];
119
+ }
120
+ case 'tool_end': {
121
+ const rawInput = this.rawInputFor(event.id);
122
+ // Status only (plus the arguments, once they are complete). No title, no kind, no name —
123
+ // the client keeps the ones the creating update set.
124
+ return [
125
+ {
126
+ sessionUpdate: 'tool_call_update',
127
+ toolCallId: event.id,
128
+ status: 'in_progress',
129
+ ...(rawInput === undefined ? {} : { rawInput }),
130
+ },
131
+ ];
132
+ }
133
+ case 'tool_output': {
134
+ if (event.id === undefined)
135
+ return [];
136
+ const collected = this.toolOutput.get(event.id) ?? [];
137
+ collected.push(toolText(event.chunk));
138
+ this.toolOutput.set(event.id, collected);
139
+ // The whole collection, because v1 replaces rather than appends. Copied so a later push
140
+ // cannot mutate an update already handed to the transport.
141
+ return [
142
+ { sessionUpdate: 'tool_call_update', toolCallId: event.id, content: [...collected] },
143
+ ];
144
+ }
145
+ case 'tool_result': {
146
+ this.trackToolSettled(event.id);
147
+ this.toolOutput.delete(event.id);
148
+ // The result REPLACES whatever live output was showing — it is the authoritative record of
149
+ // what the tool produced, and the same thing v2's final update does.
150
+ return [
151
+ {
152
+ sessionUpdate: 'tool_call_update',
153
+ toolCallId: event.id,
154
+ status: event.isError ? 'failed' : 'completed',
155
+ content: [toolText(event.content)],
156
+ },
157
+ ];
158
+ }
159
+ }
160
+ }
161
+ }
162
+ //# sourceMappingURL=acpUpdatesV1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acpUpdatesV1.js","sourceRoot":"","sources":["../../../src/modules/acp/acpUpdatesV1.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,89 @@
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
+ * RC-32: the tool NAME to stamp on a `role:'tool'` message, resolved by
29
+ * {@link convertMessages} from the parenting assistant `tool_call`. The AG-UI wire format does
30
+ * not carry it, and a `ToolMessage` without a name is invisible to every result-inspecting
31
+ * middleware — `frontend-image-injection` keys on `msg.name === 'capture_image'`, so a replayed
32
+ * capture silently stopped producing a vision block on every turn after the capture itself.
33
+ * Absent on the standalone {@link convertMessage} path (a queued resume message has no history
34
+ * to resolve against), where the name is simply unknown.
35
+ */
36
+ toolName?: string;
37
+ }
38
+ /**
39
+ * Convert AG-UI message format to LangChain BaseMessage.
40
+ *
41
+ * `allowedToolNames` is the set of tool names bound to this run (config.tools + any run-input
42
+ * client tools). It gates EXT-35 plain-text tool-call repair on the assistant branch: an incoming
43
+ * assistant message with NO native `toolCalls` whose content is a STANDALONE text-emitted call
44
+ * (bracket / `<function=…>` / Harmony — the dialects small/local models produce) is promoted to a
45
+ * native tool_call so a replayed history turn is a real tool call rather than inert prose. An empty
46
+ * (or absent) allow-list promotes nothing — the prose-safe default. This runs alongside
47
+ * {@link parseToolArguments} (which rescues malformed args on an ALREADY-native tool_call).
48
+ *
49
+ * EXT-43: `options.allowTextCallPromotion` (default `true`) lets a caller suppress promotion for a
50
+ * single message; {@link convertMessages} uses it to leave a DANGLING history call as text.
51
+ */
52
+ export declare function convertMessage(msg: AgUiWireMessage, allowedToolNames?: Set<string>, options?: ConvertMessageOptions): BaseMessage;
53
+ /**
54
+ * Convert a whole AG-UI history array to LangChain messages, applying TWO symmetric replay guards
55
+ * so a poisoned history can never abort every subsequent turn on the thread.
56
+ *
57
+ * EXT-43 (forward, dangling-CALL): EXT-35's per-message promotion is unconditional, which is correct
58
+ * for a call that WILL be executed this turn. But when replaying HISTORY, promoting a STALLED text
59
+ * call (one the client recorded but that never ran) yields an `AIMessage` with `tool_calls` and NO
60
+ * following `tool_result` — a shape a strict provider (Anthropic) 400s on, where the pre-EXT-35
61
+ * plain text was valid. So promotion is allowed ONLY when the assistant message is immediately
62
+ * followed by a `tool` result message; a dangling call stays plain text (`allowTextCallPromotion:
63
+ * false`).
64
+ *
65
+ * RC-18 (backward, orphan-RESULT): the mirror image. A replayed `role:'tool'` message whose matching
66
+ * `tool_call` id is absent from EVERY PRECEDING assistant message is an ORPHAN — converting it to a
67
+ * `ToolMessage` yields a tool result with no preceding `AIMessage.tool_calls`, which the same strict
68
+ * provider 400s on (`Invalid parameter: messages with role 'tool' must be a response to a preceding
69
+ * message with 'tool_calls'`, INVALID_TOOL_RESULTS). Such orphans arise when a terminal
70
+ * (`returnDirect`) tool call's result is reconstructed by the client without its parenting assistant
71
+ * `tool_call`. We DROP the orphan (match on tool_call_id, NOT adjacency; keep genuine pairs; do NOT
72
+ * fabricate a synthetic call — mirroring EXT-43's demote-don't-invent spirit). Ids are accumulated
73
+ * in iteration order, so a result whose matching call appears only LATER is still an orphan.
74
+ *
75
+ * RC-32 (tool NAME restoration): the AG-UI wire message for a `tool` result carries `toolCallId`
76
+ * but no tool name, so a naively-converted `ToolMessage` has none — and every middleware that
77
+ * inspects results by name is blind to it. `frontend-image-injection` keys on
78
+ * `msg.name === 'capture_image'`, so a captured photo reached the model on the resume turn (where
79
+ * the graph builds the ToolMessage itself, with a name) and then vanished from every later turn,
80
+ * leaving the model to answer questions about a picture it could no longer see. The name is
81
+ * recoverable from the parenting assistant `tool_call`, which this function already walks for the
82
+ * RC-18 guard, so it is resolved there and stamped back on.
83
+ *
84
+ * The live middleware path (`GthLangChainAgent`, fixing the CURRENT turn) is unaffected — the two
85
+ * guards and the name restoration are history-replay only.
86
+ */
87
+ export declare function convertMessages(messages: AgUiWireMessage[], allowedToolNames?: Set<string>): BaseMessage[];
2
88
  export declare function startAgUiServer(config: GthConfig, port: number): Promise<void>;
89
+ export {};