@qduc/term2 0.1.6 → 0.1.8
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/dist/cli.js +5 -0
- package/dist/cli.js.map +1 -1
- package/dist/components/BottomArea.d.ts +1 -8
- package/dist/components/BottomArea.d.ts.map +1 -1
- package/dist/components/BottomArea.js.map +1 -1
- package/dist/components/InputBox.d.ts +6 -0
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +25 -3
- package/dist/components/InputBox.js.map +1 -1
- package/dist/components/InputBox.test.js +10 -1
- package/dist/components/InputBox.test.js.map +1 -1
- package/dist/contracts/conversation.d.ts +27 -0
- package/dist/contracts/conversation.d.ts.map +1 -0
- package/dist/contracts/conversation.js +2 -0
- package/dist/contracts/conversation.js.map +1 -0
- package/dist/hooks/use-conversation.d.ts +3 -14
- package/dist/hooks/use-conversation.d.ts.map +1 -1
- package/dist/hooks/use-conversation.js +15 -87
- package/dist/hooks/use-conversation.js.map +1 -1
- package/dist/lib/openai-agent-client.d.ts.map +1 -1
- package/dist/lib/openai-agent-client.js +2 -1
- package/dist/lib/openai-agent-client.js.map +1 -1
- package/dist/lib/openai-strict-tool-schema.d.ts +10 -0
- package/dist/lib/openai-strict-tool-schema.d.ts.map +1 -0
- package/dist/lib/openai-strict-tool-schema.js +39 -0
- package/dist/lib/openai-strict-tool-schema.js.map +1 -0
- package/dist/lib/openai-strict-tool-schema.test.d.ts +2 -0
- package/dist/lib/openai-strict-tool-schema.test.d.ts.map +1 -0
- package/dist/lib/openai-strict-tool-schema.test.js +26 -0
- package/dist/lib/openai-strict-tool-schema.test.js.map +1 -0
- package/dist/lib/tool-invoke.d.ts +13 -0
- package/dist/lib/tool-invoke.d.ts.map +1 -1
- package/dist/lib/tool-invoke.js +61 -3
- package/dist/lib/tool-invoke.js.map +1 -1
- package/dist/lib/tool-invoke.test.js +139 -1
- package/dist/lib/tool-invoke.test.js.map +1 -1
- package/dist/prompts/simple.md +4 -0
- package/dist/providers/openai-compatible/model.js +3 -3
- package/dist/providers/openai-compatible/model.js.map +1 -1
- package/dist/providers/openai-compatible/reasoning-content.test.js +70 -0
- package/dist/providers/openai-compatible/reasoning-content.test.js.map +1 -1
- package/dist/providers/openrouter/model.js +3 -3
- package/dist/providers/openrouter/model.js.map +1 -1
- package/dist/providers/openrouter/utils.d.ts +1 -0
- package/dist/providers/openrouter/utils.d.ts.map +1 -1
- package/dist/providers/openrouter/utils.js +3 -0
- package/dist/providers/openrouter/utils.js.map +1 -1
- package/dist/providers/openrouter.test.js +64 -0
- package/dist/providers/openrouter.test.js.map +1 -1
- package/dist/services/approval-presentation-policy.d.ts +17 -0
- package/dist/services/approval-presentation-policy.d.ts.map +1 -0
- package/dist/services/approval-presentation-policy.js +44 -0
- package/dist/services/approval-presentation-policy.js.map +1 -0
- package/dist/services/approval-presentation-policy.test.d.ts +2 -0
- package/dist/services/approval-presentation-policy.test.d.ts.map +1 -0
- package/dist/services/approval-presentation-policy.test.js +74 -0
- package/dist/services/approval-presentation-policy.test.js.map +1 -0
- package/dist/services/approval-state.d.ts +4 -4
- package/dist/services/approval-state.d.ts.map +1 -1
- package/dist/services/conversation-events.d.ts +12 -9
- package/dist/services/conversation-events.d.ts.map +1 -1
- package/dist/services/conversation-service.d.ts +7 -5
- package/dist/services/conversation-service.d.ts.map +1 -1
- package/dist/services/conversation-service.js +1 -1
- package/dist/services/conversation-service.js.map +1 -1
- package/dist/services/conversation-session.d.ts +4 -24
- package/dist/services/conversation-session.d.ts.map +1 -1
- package/dist/services/conversation-session.js +172 -229
- package/dist/services/conversation-session.js.map +1 -1
- package/dist/services/conversation-store.d.ts +6 -0
- package/dist/services/conversation-store.d.ts.map +1 -1
- package/dist/services/conversation-store.js +13 -0
- package/dist/services/conversation-store.js.map +1 -1
- package/dist/tools/ask-mentor.d.ts +1 -1
- package/dist/tools/ask-mentor.js +1 -1
- package/dist/tools/ask-mentor.js.map +1 -1
- package/dist/tools/ask-mentor.test.js +9 -3
- package/dist/tools/ask-mentor.test.js.map +1 -1
- package/dist/tools/find-files.d.ts +2 -2
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +2 -4
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/find-files.test.js +7 -19
- package/dist/tools/find-files.test.js.map +1 -1
- package/dist/tools/grep.d.ts +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +1 -5
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/read-file.d.ts +2 -2
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +2 -4
- package/dist/tools/read-file.js.map +1 -1
- package/dist/tools/read-file.test.js +22 -13
- package/dist/tools/read-file.test.js.map +1 -1
- package/dist/tools/search-replace.d.ts +1 -1
- package/dist/tools/search-replace.d.ts.map +1 -1
- package/dist/tools/search-replace.js +158 -1
- package/dist/tools/search-replace.js.map +1 -1
- package/dist/tools/search-replace.test.js +177 -0
- package/dist/tools/search-replace.test.js.map +1 -1
- package/dist/tools/search.d.ts +4 -4
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +5 -14
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/shell.d.ts +2 -2
- package/dist/tools/shell.d.ts.map +1 -1
- package/dist/tools/shell.js +2 -4
- package/dist/tools/shell.js.map +1 -1
- package/dist/tools/tool-capabilities.d.ts +6 -0
- package/dist/tools/tool-capabilities.d.ts.map +1 -0
- package/dist/tools/tool-capabilities.js +17 -0
- package/dist/tools/tool-capabilities.js.map +1 -0
- package/dist/tools/tool-parameter-schema.test.d.ts +2 -0
- package/dist/tools/tool-parameter-schema.test.d.ts.map +1 -0
- package/dist/tools/tool-parameter-schema.test.js +88 -0
- package/dist/tools/tool-parameter-schema.test.js.map +1 -0
- package/dist/tools/types.d.ts +4 -2
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/utils.d.ts.map +1 -1
- package/dist/tools/utils.js +4 -1
- package/dist/tools/utils.js.map +1 -1
- package/dist/tools/utils.test.d.ts +2 -0
- package/dist/tools/utils.test.d.ts.map +1 -0
- package/dist/tools/utils.test.js +26 -0
- package/dist/tools/utils.test.js.map +1 -0
- package/dist/tools/web-fetch.d.ts +3 -3
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +3 -2
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/utils/conversation-event-handler.d.ts +16 -10
- package/dist/utils/conversation-event-handler.d.ts.map +1 -1
- package/dist/utils/conversation-event-handler.js +4 -0
- package/dist/utils/conversation-event-handler.js.map +1 -1
- package/dist/utils/streaming-session-factory.d.ts +9 -9
- package/dist/utils/streaming-session-factory.d.ts.map +1 -1
- package/dist/utils/streaming-session-factory.js +7 -2
- package/dist/utils/streaming-session-factory.js.map +1 -1
- package/dist/utils/synchronized-output.d.ts +35 -0
- package/dist/utils/synchronized-output.d.ts.map +1 -0
- package/dist/utils/synchronized-output.js +66 -0
- package/dist/utils/synchronized-output.js.map +1 -0
- package/dist/utils/synchronized-output.test.d.ts +2 -0
- package/dist/utils/synchronized-output.test.d.ts.map +1 -0
- package/dist/utils/synchronized-output.test.js +70 -0
- package/dist/utils/synchronized-output.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -7,6 +7,33 @@ import { extractReasoningDelta, extractTextDelta } from './stream-event-parsing.
|
|
|
7
7
|
import { captureToolCallArguments, emitCommandMessagesFromItems } from './command-message-streaming.js';
|
|
8
8
|
import { ApprovalState } from './approval-state.js';
|
|
9
9
|
import { createInvalidToolCallDiagnostic } from './logging-contract.js';
|
|
10
|
+
const asRecord = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
11
|
+
const getString = (record, key) => {
|
|
12
|
+
const value = record?.[key];
|
|
13
|
+
return typeof value === 'string' ? value : undefined;
|
|
14
|
+
};
|
|
15
|
+
const getMethod = (target, key) => {
|
|
16
|
+
const record = asRecord(target);
|
|
17
|
+
const candidate = record?.[key];
|
|
18
|
+
return typeof candidate === 'function' ? candidate : null;
|
|
19
|
+
};
|
|
20
|
+
const getCallIdFromObject = (value) => {
|
|
21
|
+
const record = asRecord(value);
|
|
22
|
+
const callId = getString(record, 'callId') ??
|
|
23
|
+
getString(record, 'call_id') ??
|
|
24
|
+
getString(record, 'tool_call_id') ??
|
|
25
|
+
getString(record, 'toolCallId') ??
|
|
26
|
+
getString(record, 'id');
|
|
27
|
+
if (callId) {
|
|
28
|
+
return callId;
|
|
29
|
+
}
|
|
30
|
+
const rawItem = asRecord(record?.rawItem);
|
|
31
|
+
return (getString(rawItem, 'callId') ??
|
|
32
|
+
getString(rawItem, 'call_id') ??
|
|
33
|
+
getString(rawItem, 'tool_call_id') ??
|
|
34
|
+
getString(rawItem, 'toolCallId') ??
|
|
35
|
+
getString(rawItem, 'id'));
|
|
36
|
+
};
|
|
10
37
|
const getCommandFromArgs = (args) => {
|
|
11
38
|
if (!args) {
|
|
12
39
|
return '';
|
|
@@ -29,15 +56,16 @@ const getCommandFromArgs = (args) => {
|
|
|
29
56
|
}
|
|
30
57
|
}
|
|
31
58
|
if (typeof args === 'object') {
|
|
59
|
+
const argsRecord = asRecord(args);
|
|
32
60
|
// Handle shell tool's command parameter
|
|
33
|
-
const cmdFromObject =
|
|
61
|
+
const cmdFromObject = argsRecord?.command !== undefined ? String(argsRecord.command) : undefined;
|
|
34
62
|
// Fallback for old 'commands' array format
|
|
35
|
-
if (
|
|
36
|
-
return
|
|
63
|
+
if (Array.isArray(argsRecord?.commands)) {
|
|
64
|
+
return argsRecord.commands.join('\n');
|
|
37
65
|
}
|
|
38
66
|
let argsFromObject;
|
|
39
|
-
if (
|
|
40
|
-
const rawArguments =
|
|
67
|
+
if (argsRecord?.arguments !== undefined) {
|
|
68
|
+
const rawArguments = argsRecord.arguments;
|
|
41
69
|
if (typeof rawArguments === 'string') {
|
|
42
70
|
try {
|
|
43
71
|
argsFromObject = JSON.stringify(JSON.parse(rawArguments));
|
|
@@ -59,14 +87,17 @@ const getCommandFromArgs = (args) => {
|
|
|
59
87
|
*/
|
|
60
88
|
const MAX_HALLUCINATION_RETRIES = 2;
|
|
61
89
|
/**
|
|
62
|
-
* Check if an error is a
|
|
90
|
+
* Check if an error is a recoverable model behavior error (hallucination, parsing error, etc.)
|
|
63
91
|
*/
|
|
64
|
-
const
|
|
92
|
+
const isRecoverableModelError = (error) => {
|
|
65
93
|
if (!(error instanceof ModelBehaviorError)) {
|
|
66
94
|
return false;
|
|
67
95
|
}
|
|
68
96
|
const message = error.message.toLowerCase();
|
|
69
|
-
return message.includes('tool') && message.includes('not found')
|
|
97
|
+
return ((message.includes('tool') && message.includes('not found')) || // Hallucination
|
|
98
|
+
message.includes('model did not produce a final response') || // Give up/exhausted
|
|
99
|
+
message.includes('parsing tool arguments') || // Bad JSON
|
|
100
|
+
message.includes('valid json'));
|
|
70
101
|
};
|
|
71
102
|
const supportsConversationChaining = (providerId) => {
|
|
72
103
|
const providerDef = getProvider(providerId);
|
|
@@ -82,45 +113,7 @@ export class ConversationSession {
|
|
|
82
113
|
textDeltaCount = 0;
|
|
83
114
|
reasoningDeltaCount = 0;
|
|
84
115
|
toolCallArgumentsById = new Map();
|
|
85
|
-
lastEventType = null;
|
|
86
|
-
eventTypeCount = 0;
|
|
87
116
|
emittedInvalidToolCallPackets = new Set();
|
|
88
|
-
// private logStreamEvent = (eventType: string, eventData: any) => {
|
|
89
|
-
// if (eventData.item) {
|
|
90
|
-
// eventType = eventData.item.type;
|
|
91
|
-
// eventData = eventData.item.rawItem;
|
|
92
|
-
// // this.logStreamEvent(eventType, eventData);
|
|
93
|
-
// }
|
|
94
|
-
//
|
|
95
|
-
// // Deduplicate consecutive identical event types
|
|
96
|
-
// if (eventType !== this.lastEventType) {
|
|
97
|
-
// if (this.lastEventType !== null && this.eventTypeCount > 0) {
|
|
98
|
-
// this.logger.debug('Stream event summary', {
|
|
99
|
-
// eventType: this.lastEventType,
|
|
100
|
-
// count: this.eventTypeCount,
|
|
101
|
-
// });
|
|
102
|
-
// }
|
|
103
|
-
// this.lastEventType = eventType;
|
|
104
|
-
// this.eventTypeCount = 1;
|
|
105
|
-
// // Log the first occurrence with details
|
|
106
|
-
// this.logger.debug('Stream event', {
|
|
107
|
-
// eventType,
|
|
108
|
-
// ...eventData,
|
|
109
|
-
// });
|
|
110
|
-
// } else {
|
|
111
|
-
// this.eventTypeCount++;
|
|
112
|
-
// }
|
|
113
|
-
// };
|
|
114
|
-
flushStreamEventLog = () => {
|
|
115
|
-
if (this.lastEventType !== null && this.eventTypeCount > 1) {
|
|
116
|
-
this.logger.debug('Stream event summary', {
|
|
117
|
-
eventType: this.lastEventType,
|
|
118
|
-
count: this.eventTypeCount,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
this.lastEventType = null;
|
|
122
|
-
this.eventTypeCount = 0;
|
|
123
|
-
};
|
|
124
117
|
constructor(id, { agentClient, deps }) {
|
|
125
118
|
this.id = id;
|
|
126
119
|
this.agentClient = agentClient;
|
|
@@ -133,27 +126,23 @@ export class ConversationSession {
|
|
|
133
126
|
this.approvalState.clearPending();
|
|
134
127
|
this.approvalState.consumeAborted();
|
|
135
128
|
this.toolCallArgumentsById.clear();
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
129
|
+
const clearConversations = getMethod(this.agentClient, 'clearConversations');
|
|
130
|
+
clearConversations?.call(this.agentClient);
|
|
139
131
|
}
|
|
140
132
|
setModel(model) {
|
|
141
133
|
this.agentClient.setModel(model);
|
|
142
134
|
}
|
|
143
135
|
setReasoningEffort(effort) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
136
|
+
const setReasoningEffort = getMethod(this.agentClient, 'setReasoningEffort');
|
|
137
|
+
setReasoningEffort?.call(this.agentClient, effort);
|
|
147
138
|
}
|
|
148
139
|
setTemperature(temperature) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
140
|
+
const setTemperature = getMethod(this.agentClient, 'setTemperature');
|
|
141
|
+
setTemperature?.call(this.agentClient, temperature);
|
|
152
142
|
}
|
|
153
143
|
setProvider(provider) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
144
|
+
const setProvider = getMethod(this.agentClient, 'setProvider');
|
|
145
|
+
setProvider?.call(this.agentClient, provider);
|
|
157
146
|
}
|
|
158
147
|
setRetryCallback(callback) {
|
|
159
148
|
if (typeof this.agentClient.setRetryCallback === 'function') {
|
|
@@ -207,6 +196,7 @@ export class ConversationSession {
|
|
|
207
196
|
message: text,
|
|
208
197
|
});
|
|
209
198
|
const { state, interruption, emittedCommandIds, toolCallArgumentsById } = abortedContext;
|
|
199
|
+
const interruptionRecord = asRecord(interruption);
|
|
210
200
|
// Restore cached tool-call arguments captured before abort so continuation can attach them
|
|
211
201
|
this.toolCallArgumentsById.clear();
|
|
212
202
|
if (toolCallArgumentsById?.size) {
|
|
@@ -215,8 +205,8 @@ export class ConversationSession {
|
|
|
215
205
|
}
|
|
216
206
|
}
|
|
217
207
|
// Add interceptor for this tool execution
|
|
218
|
-
const toolName =
|
|
219
|
-
const expectedCallId = interruption
|
|
208
|
+
const toolName = getString(interruptionRecord, 'name') ?? 'unknown';
|
|
209
|
+
const expectedCallId = getCallIdFromObject(interruption);
|
|
220
210
|
const rejectionMessage = `Tool execution was not approved. User provided new input instead: ${text}`;
|
|
221
211
|
const removeInterceptor = this.agentClient.addToolInterceptor(async (name, _params, toolCallId) => {
|
|
222
212
|
// Match both tool name and call ID for stricter matching
|
|
@@ -226,87 +216,35 @@ export class ConversationSession {
|
|
|
226
216
|
}
|
|
227
217
|
return null;
|
|
228
218
|
});
|
|
229
|
-
state
|
|
219
|
+
const approve = getMethod(state, 'approve');
|
|
220
|
+
approve?.call(state, interruption);
|
|
230
221
|
try {
|
|
231
|
-
const
|
|
222
|
+
const continuedStream = (await this.agentClient.continueRunStream(state, {
|
|
232
223
|
previousResponseId: this.previousResponseId,
|
|
233
|
-
});
|
|
224
|
+
}));
|
|
234
225
|
const acc = {
|
|
235
226
|
finalOutput: '',
|
|
236
227
|
reasoningOutput: '',
|
|
237
228
|
emittedCommandIds: new Set(emittedCommandIds),
|
|
238
229
|
latestUsage: undefined,
|
|
239
230
|
};
|
|
240
|
-
yield* this.#streamEvents(
|
|
231
|
+
yield* this.#streamEvents(continuedStream, acc, {
|
|
241
232
|
preserveExistingToolArgs: true,
|
|
242
233
|
});
|
|
243
|
-
this.previousResponseId =
|
|
244
|
-
this.conversationStore.updateFromResult(
|
|
234
|
+
this.previousResponseId = continuedStream.lastResponseId ?? null;
|
|
235
|
+
this.conversationStore.updateFromResult(continuedStream);
|
|
245
236
|
// Check if another interruption occurred
|
|
246
|
-
if (
|
|
237
|
+
if (continuedStream.interruptions && continuedStream.interruptions.length > 0) {
|
|
247
238
|
this.logger.warn('Another interruption occurred after fake execution - handling as approval');
|
|
248
239
|
// Let the normal flow handle this
|
|
249
|
-
const result = this.#buildResult(
|
|
250
|
-
|
|
251
|
-
if (result.type === 'approval_required') {
|
|
252
|
-
const interruption = result.approval.rawInterruption;
|
|
253
|
-
const callId = interruption?.rawItem?.callId ??
|
|
254
|
-
interruption?.callId ??
|
|
255
|
-
interruption?.call_id ??
|
|
256
|
-
interruption?.tool_call_id ??
|
|
257
|
-
interruption?.toolCallId ??
|
|
258
|
-
interruption?.id;
|
|
259
|
-
yield {
|
|
260
|
-
type: 'approval_required',
|
|
261
|
-
approval: {
|
|
262
|
-
agentName: result.approval.agentName,
|
|
263
|
-
toolName: result.approval.toolName,
|
|
264
|
-
argumentsText: result.approval.argumentsText,
|
|
265
|
-
...(callId ? { callId: String(callId) } : {}),
|
|
266
|
-
},
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
yield {
|
|
271
|
-
type: 'final',
|
|
272
|
-
finalText: result.finalText,
|
|
273
|
-
...(result.reasoningText ? { reasoningText: result.reasoningText } : {}),
|
|
274
|
-
...(result.commandMessages?.length ? { commandMessages: result.commandMessages } : {}),
|
|
275
|
-
...(result.usage ? { usage: result.usage } : {}),
|
|
276
|
-
};
|
|
277
|
-
}
|
|
240
|
+
const result = this.#buildResult(continuedStream, acc.finalOutput, acc.reasoningOutput, acc.emittedCommandIds, acc.latestUsage);
|
|
241
|
+
yield this.#toTerminalEvent(result);
|
|
278
242
|
return;
|
|
279
243
|
}
|
|
280
244
|
// Successfully resolved - agent should now have processed the fake rejection
|
|
281
245
|
this.logger.debug('Fake execution completed, agent received rejection message');
|
|
282
|
-
const result = this.#buildResult(
|
|
283
|
-
|
|
284
|
-
const interruption = result.approval.rawInterruption;
|
|
285
|
-
const callId = interruption?.rawItem?.callId ??
|
|
286
|
-
interruption?.callId ??
|
|
287
|
-
interruption?.call_id ??
|
|
288
|
-
interruption?.tool_call_id ??
|
|
289
|
-
interruption?.toolCallId ??
|
|
290
|
-
interruption?.id;
|
|
291
|
-
yield {
|
|
292
|
-
type: 'approval_required',
|
|
293
|
-
approval: {
|
|
294
|
-
agentName: result.approval.agentName,
|
|
295
|
-
toolName: result.approval.toolName,
|
|
296
|
-
argumentsText: result.approval.argumentsText,
|
|
297
|
-
...(callId ? { callId: String(callId) } : {}),
|
|
298
|
-
},
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
else {
|
|
302
|
-
yield {
|
|
303
|
-
type: 'final',
|
|
304
|
-
finalText: result.finalText,
|
|
305
|
-
...(result.reasoningText ? { reasoningText: result.reasoningText } : {}),
|
|
306
|
-
...(result.commandMessages?.length ? { commandMessages: result.commandMessages } : {}),
|
|
307
|
-
...(result.usage ? { usage: result.usage } : {}),
|
|
308
|
-
};
|
|
309
|
-
}
|
|
246
|
+
const result = this.#buildResult(continuedStream, acc.finalOutput, acc.reasoningOutput, acc.emittedCommandIds, acc.latestUsage);
|
|
247
|
+
yield this.#toTerminalEvent(result);
|
|
310
248
|
return;
|
|
311
249
|
}
|
|
312
250
|
catch (error) {
|
|
@@ -321,13 +259,12 @@ export class ConversationSession {
|
|
|
321
259
|
}
|
|
322
260
|
}
|
|
323
261
|
// Normal message flow
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
: 'openai';
|
|
262
|
+
const getProvider = getMethod(this.agentClient, 'getProvider');
|
|
263
|
+
const provider = getProvider ? getProvider.call(this.agentClient) : 'openai';
|
|
327
264
|
const supportsChaining = supportsConversationChaining(provider);
|
|
328
|
-
stream = await this.agentClient.startStream(supportsChaining ? text : this.conversationStore.getHistory(), {
|
|
265
|
+
stream = (await this.agentClient.startStream(supportsChaining ? text : this.conversationStore.getHistory(), {
|
|
329
266
|
previousResponseId: this.previousResponseId,
|
|
330
|
-
});
|
|
267
|
+
}));
|
|
331
268
|
const acc = {
|
|
332
269
|
finalOutput: '',
|
|
333
270
|
reasoningOutput: '',
|
|
@@ -337,7 +274,7 @@ export class ConversationSession {
|
|
|
337
274
|
yield* this.#streamEvents(stream, acc, {
|
|
338
275
|
preserveExistingToolArgs: false,
|
|
339
276
|
});
|
|
340
|
-
this.previousResponseId = stream.lastResponseId;
|
|
277
|
+
this.previousResponseId = stream.lastResponseId ?? null;
|
|
341
278
|
this.conversationStore.updateFromResult(stream);
|
|
342
279
|
// Build terminal event (approval_required or final)
|
|
343
280
|
const result = this.#buildResult(stream, acc.finalOutput || undefined, acc.reasoningOutput || undefined, acc.emittedCommandIds, acc.latestUsage);
|
|
@@ -350,59 +287,47 @@ export class ConversationSession {
|
|
|
350
287
|
traceId: this.logger.getCorrelationId(),
|
|
351
288
|
toolName: result.approval.toolName,
|
|
352
289
|
});
|
|
353
|
-
|
|
354
|
-
const callId = interruption?.rawItem?.callId ??
|
|
355
|
-
interruption?.callId ??
|
|
356
|
-
interruption?.call_id ??
|
|
357
|
-
interruption?.tool_call_id ??
|
|
358
|
-
interruption?.toolCallId ??
|
|
359
|
-
interruption?.id;
|
|
360
|
-
yield {
|
|
361
|
-
type: 'approval_required',
|
|
362
|
-
approval: {
|
|
363
|
-
agentName: result.approval.agentName,
|
|
364
|
-
toolName: result.approval.toolName,
|
|
365
|
-
argumentsText: result.approval.argumentsText,
|
|
366
|
-
...(callId ? { callId: String(callId) } : {}),
|
|
367
|
-
},
|
|
368
|
-
};
|
|
290
|
+
yield this.#toTerminalEvent(result);
|
|
369
291
|
return;
|
|
370
292
|
}
|
|
371
|
-
yield
|
|
372
|
-
type: 'final',
|
|
373
|
-
finalText: result.finalText,
|
|
374
|
-
...(result.reasoningText ? { reasoningText: result.reasoningText } : {}),
|
|
375
|
-
...(result.commandMessages?.length ? { commandMessages: result.commandMessages } : {}),
|
|
376
|
-
...(result.usage ? { usage: result.usage } : {}),
|
|
377
|
-
};
|
|
293
|
+
yield this.#toTerminalEvent(result);
|
|
378
294
|
}
|
|
379
295
|
catch (error) {
|
|
380
|
-
// Handle
|
|
381
|
-
if (
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
296
|
+
// Handle recoverable model errors (hallucination, parsing error, etc.)
|
|
297
|
+
if (isRecoverableModelError(error) && hallucinationRetryCount < MAX_HALLUCINATION_RETRIES) {
|
|
298
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
299
|
+
const isHallucination = message.toLowerCase().includes('not found');
|
|
300
|
+
const isParsingError = message.toLowerCase().includes('parsing tool arguments') || message.toLowerCase().includes('valid json');
|
|
301
|
+
const toolName = isHallucination ? message.match(/Tool (\S+) not found/)?.[1] || 'unknown' : 'unknown';
|
|
302
|
+
this.logger.warn('Recoverable model error detected, retrying', {
|
|
303
|
+
eventType: 'retry.model_error',
|
|
385
304
|
category: 'retry',
|
|
386
305
|
phase: 'retry',
|
|
387
306
|
toolName,
|
|
388
|
-
retryType: 'hallucination',
|
|
307
|
+
retryType: isHallucination ? 'hallucination' : isParsingError ? 'parsing_error' : 'behavior',
|
|
389
308
|
retryAttempt: hallucinationRetryCount + 1,
|
|
390
309
|
attempt: hallucinationRetryCount + 1,
|
|
391
310
|
maxRetries: MAX_HALLUCINATION_RETRIES,
|
|
392
311
|
sessionId: this.id,
|
|
393
312
|
traceId: this.logger.getCorrelationId(),
|
|
394
|
-
errorMessage:
|
|
313
|
+
errorMessage: message,
|
|
395
314
|
});
|
|
396
315
|
yield {
|
|
397
316
|
type: 'retry',
|
|
398
|
-
toolName,
|
|
317
|
+
toolName: isHallucination ? toolName : 'model',
|
|
399
318
|
attempt: hallucinationRetryCount + 1,
|
|
400
319
|
maxRetries: MAX_HALLUCINATION_RETRIES,
|
|
401
|
-
errorMessage:
|
|
320
|
+
errorMessage: message,
|
|
402
321
|
};
|
|
403
322
|
if (stream) {
|
|
404
|
-
// Update conversation store with partial results (
|
|
323
|
+
// Update conversation store with partial results (including the parse error output if it was yielded)
|
|
405
324
|
this.conversationStore.updateFromResult(stream);
|
|
325
|
+
// If stream produced no usable history, inject error context so the
|
|
326
|
+
// model has explicit feedback about the failure on retry.
|
|
327
|
+
const streamHistory = Array.isArray(stream.history) ? stream.history : [];
|
|
328
|
+
if (streamHistory.length === 0) {
|
|
329
|
+
this.conversationStore.addErrorContext(`[System: Previous attempt failed with error: ${message}. Please retry with corrected output.]`);
|
|
330
|
+
}
|
|
406
331
|
// Retry from current state without re-adding user message
|
|
407
332
|
yield* this.run(text, {
|
|
408
333
|
hallucinationRetryCount: hallucinationRetryCount + 1,
|
|
@@ -445,6 +370,7 @@ export class ConversationSession {
|
|
|
445
370
|
return;
|
|
446
371
|
}
|
|
447
372
|
const { state, interruption, emittedCommandIds: previouslyEmittedIds, toolCallArgumentsById, } = pendingApprovalContext;
|
|
373
|
+
const interruptionRecord = asRecord(interruption);
|
|
448
374
|
let removeInterceptor = null;
|
|
449
375
|
if (answer === 'y') {
|
|
450
376
|
this.logger.info('Tool approval granted', {
|
|
@@ -454,11 +380,12 @@ export class ConversationSession {
|
|
|
454
380
|
sessionId: this.id,
|
|
455
381
|
traceId: this.logger.getCorrelationId(),
|
|
456
382
|
});
|
|
457
|
-
state
|
|
383
|
+
const approve = getMethod(state, 'approve');
|
|
384
|
+
approve?.call(state, interruption);
|
|
458
385
|
}
|
|
459
386
|
else {
|
|
460
|
-
const toolName =
|
|
461
|
-
const expectedCallId = interruption
|
|
387
|
+
const toolName = getString(interruptionRecord, 'name') ?? 'unknown';
|
|
388
|
+
const expectedCallId = getCallIdFromObject(interruption);
|
|
462
389
|
const rejectionMessage = rejectionReason
|
|
463
390
|
? `Tool execution was not approved. User's reason: ${rejectionReason}`
|
|
464
391
|
: 'Tool execution was not approved.';
|
|
@@ -471,13 +398,15 @@ export class ConversationSession {
|
|
|
471
398
|
return null;
|
|
472
399
|
});
|
|
473
400
|
// Approve to continue but interceptor will return rejection message
|
|
474
|
-
state
|
|
401
|
+
const approve = getMethod(state, 'approve');
|
|
402
|
+
approve?.call(state, interruption);
|
|
475
403
|
// Store interceptor cleanup for after stream
|
|
476
404
|
this.approvalState.setPendingRemoveInterceptor(removeInterceptor);
|
|
477
405
|
}
|
|
478
406
|
else {
|
|
479
407
|
// Fallback for clients without tool interceptors
|
|
480
|
-
state
|
|
408
|
+
const reject = getMethod(state, 'reject');
|
|
409
|
+
reject?.call(state, interruption);
|
|
481
410
|
}
|
|
482
411
|
this.logger.info('Tool approval rejected', {
|
|
483
412
|
eventType: 'approval.rejected',
|
|
@@ -496,9 +425,9 @@ export class ConversationSession {
|
|
|
496
425
|
}
|
|
497
426
|
}
|
|
498
427
|
try {
|
|
499
|
-
const stream = await this.agentClient.continueRunStream(state, {
|
|
428
|
+
const stream = (await this.agentClient.continueRunStream(state, {
|
|
500
429
|
previousResponseId: this.previousResponseId,
|
|
501
|
-
});
|
|
430
|
+
}));
|
|
502
431
|
const acc = {
|
|
503
432
|
finalOutput: '',
|
|
504
433
|
reasoningOutput: '',
|
|
@@ -508,7 +437,7 @@ export class ConversationSession {
|
|
|
508
437
|
yield* this.#streamEvents(stream, acc, {
|
|
509
438
|
preserveExistingToolArgs: true,
|
|
510
439
|
});
|
|
511
|
-
this.previousResponseId = stream.lastResponseId;
|
|
440
|
+
this.previousResponseId = stream.lastResponseId ?? null;
|
|
512
441
|
this.conversationStore.updateFromResult(stream);
|
|
513
442
|
// Merge previously emitted command IDs with newly emitted ones
|
|
514
443
|
// This prevents duplicates when result.history contains commands from the initial stream
|
|
@@ -523,31 +452,10 @@ export class ConversationSession {
|
|
|
523
452
|
traceId: this.logger.getCorrelationId(),
|
|
524
453
|
toolName: result.approval.toolName,
|
|
525
454
|
});
|
|
526
|
-
|
|
527
|
-
const callId = interruption?.rawItem?.callId ??
|
|
528
|
-
interruption?.callId ??
|
|
529
|
-
interruption?.call_id ??
|
|
530
|
-
interruption?.tool_call_id ??
|
|
531
|
-
interruption?.toolCallId ??
|
|
532
|
-
interruption?.id;
|
|
533
|
-
yield {
|
|
534
|
-
type: 'approval_required',
|
|
535
|
-
approval: {
|
|
536
|
-
agentName: result.approval.agentName,
|
|
537
|
-
toolName: result.approval.toolName,
|
|
538
|
-
argumentsText: result.approval.argumentsText,
|
|
539
|
-
...(callId ? { callId: String(callId) } : {}),
|
|
540
|
-
},
|
|
541
|
-
};
|
|
455
|
+
yield this.#toTerminalEvent(result);
|
|
542
456
|
return;
|
|
543
457
|
}
|
|
544
|
-
yield
|
|
545
|
-
type: 'final',
|
|
546
|
-
finalText: result.finalText,
|
|
547
|
-
...(result.reasoningText ? { reasoningText: result.reasoningText } : {}),
|
|
548
|
-
...(result.commandMessages?.length ? { commandMessages: result.commandMessages } : {}),
|
|
549
|
-
...(result.usage ? { usage: result.usage } : {}),
|
|
550
|
-
};
|
|
458
|
+
yield this.#toTerminalEvent(result);
|
|
551
459
|
}
|
|
552
460
|
catch (error) {
|
|
553
461
|
yield {
|
|
@@ -595,6 +503,7 @@ export class ConversationSession {
|
|
|
595
503
|
toolName: event.approval.toolName,
|
|
596
504
|
argumentsText: event.approval.argumentsText,
|
|
597
505
|
rawInterruption,
|
|
506
|
+
callId: event.approval.callId,
|
|
598
507
|
},
|
|
599
508
|
};
|
|
600
509
|
}
|
|
@@ -677,6 +586,7 @@ export class ConversationSession {
|
|
|
677
586
|
toolName: event.approval.toolName,
|
|
678
587
|
argumentsText: event.approval.argumentsText,
|
|
679
588
|
rawInterruption,
|
|
589
|
+
callId: event.approval.callId,
|
|
680
590
|
},
|
|
681
591
|
};
|
|
682
592
|
}
|
|
@@ -764,27 +674,37 @@ export class ConversationSession {
|
|
|
764
674
|
fullText: acc.reasoningOutput,
|
|
765
675
|
};
|
|
766
676
|
};
|
|
767
|
-
for await (const
|
|
768
|
-
|
|
769
|
-
const
|
|
677
|
+
for await (const rawEvent of stream) {
|
|
678
|
+
const event = asRecord(rawEvent);
|
|
679
|
+
const eventData = asRecord(event?.data);
|
|
680
|
+
const eventType = getString(event, 'type');
|
|
681
|
+
// Extract usage if present in any of the common locations.
|
|
682
|
+
// Check both the top-level event and event.data, since raw_model_stream_event
|
|
683
|
+
// nests response data (including usage) under .data (e.g. data.response.usage).
|
|
684
|
+
const usage = extractUsage(rawEvent) ?? (eventData ? extractUsage(eventData) : undefined);
|
|
770
685
|
if (usage) {
|
|
771
686
|
acc.latestUsage = usage;
|
|
772
687
|
this.logger.debug('Usage extracted from stream event', {
|
|
773
688
|
sessionId: this.id,
|
|
774
689
|
source: 'stream_event',
|
|
775
|
-
eventType:
|
|
690
|
+
eventType: eventType ?? getString(eventData, 'type') ?? 'unknown',
|
|
776
691
|
usage,
|
|
777
692
|
});
|
|
693
|
+
// Emit usage update event for real-time token tracking
|
|
694
|
+
yield {
|
|
695
|
+
type: 'usage_update',
|
|
696
|
+
usage,
|
|
697
|
+
};
|
|
778
698
|
}
|
|
779
699
|
// Log event type with deduplication for ordering understanding
|
|
780
|
-
const delta1 = extractTextDelta(
|
|
700
|
+
const delta1 = extractTextDelta(rawEvent);
|
|
781
701
|
if (delta1) {
|
|
782
702
|
const e = emitText(delta1);
|
|
783
703
|
if (e)
|
|
784
704
|
yield e;
|
|
785
705
|
}
|
|
786
|
-
if (
|
|
787
|
-
const delta2 = extractTextDelta(
|
|
706
|
+
if (eventData) {
|
|
707
|
+
const delta2 = extractTextDelta(eventData);
|
|
788
708
|
if (delta2) {
|
|
789
709
|
const e = emitText(delta2);
|
|
790
710
|
if (e)
|
|
@@ -792,7 +712,7 @@ export class ConversationSession {
|
|
|
792
712
|
}
|
|
793
713
|
}
|
|
794
714
|
// Handle reasoning items
|
|
795
|
-
const reasoningDelta = extractReasoningDelta(
|
|
715
|
+
const reasoningDelta = extractReasoningDelta(rawEvent);
|
|
796
716
|
if (reasoningDelta) {
|
|
797
717
|
const e = emitReasoning(reasoningDelta);
|
|
798
718
|
if (e)
|
|
@@ -802,15 +722,21 @@ export class ConversationSession {
|
|
|
802
722
|
toolCallArgumentsById,
|
|
803
723
|
emittedCommandIds: acc.emittedCommandIds,
|
|
804
724
|
});
|
|
805
|
-
if (
|
|
806
|
-
|
|
725
|
+
if (eventType === 'run_item_stream_event') {
|
|
726
|
+
const eventItem = event?.item;
|
|
727
|
+
const eventItemRecord = asRecord(eventItem);
|
|
728
|
+
captureToolCallArguments(eventItem, toolCallArgumentsById);
|
|
807
729
|
// Emit tool_started event when a function_call is detected
|
|
808
|
-
const rawItem =
|
|
809
|
-
if (rawItem
|
|
810
|
-
const callId = rawItem
|
|
730
|
+
const rawItem = asRecord(eventItemRecord?.rawItem) ?? eventItemRecord;
|
|
731
|
+
if (getString(rawItem, 'type') === 'function_call') {
|
|
732
|
+
const callId = getString(rawItem, 'callId') ??
|
|
733
|
+
getString(rawItem, 'call_id') ??
|
|
734
|
+
getString(rawItem, 'tool_call_id') ??
|
|
735
|
+
getString(rawItem, 'toolCallId') ??
|
|
736
|
+
getString(rawItem, 'id');
|
|
811
737
|
if (callId) {
|
|
812
|
-
const toolName = rawItem
|
|
813
|
-
const args = rawItem
|
|
738
|
+
const toolName = getString(rawItem, 'name') ?? getString(eventItemRecord, 'name');
|
|
739
|
+
const args = rawItem?.arguments ?? rawItem?.args ?? eventItemRecord?.arguments ?? eventItemRecord?.args;
|
|
814
740
|
// Providers sometimes surface arguments as a JSON string.
|
|
815
741
|
// Normalize here so downstream UI (pending/running display)
|
|
816
742
|
// can reliably render parameters.
|
|
@@ -871,13 +797,14 @@ export class ConversationSession {
|
|
|
871
797
|
});
|
|
872
798
|
}
|
|
873
799
|
}
|
|
874
|
-
for (const e of maybeEmitCommandMessagesFromItems([
|
|
800
|
+
for (const e of maybeEmitCommandMessagesFromItems([eventItem])) {
|
|
875
801
|
yield e;
|
|
876
802
|
}
|
|
877
803
|
}
|
|
878
|
-
else if (
|
|
879
|
-
|
|
880
|
-
|
|
804
|
+
else if (eventType === 'tool_call_output_item' ||
|
|
805
|
+
getString(asRecord(event?.rawItem), 'type') === 'function_call_output') {
|
|
806
|
+
captureToolCallArguments(rawEvent, toolCallArgumentsById);
|
|
807
|
+
for (const e of maybeEmitCommandMessagesFromItems([rawEvent])) {
|
|
881
808
|
yield e;
|
|
882
809
|
}
|
|
883
810
|
}
|
|
@@ -924,11 +851,30 @@ export class ConversationSession {
|
|
|
924
851
|
usage: Boolean(completedResultRecord?.usage),
|
|
925
852
|
usageMetadata: Boolean(completedResultRecord?.usageMetadata),
|
|
926
853
|
usage_metadata: Boolean(completedResultRecord?.usage_metadata),
|
|
927
|
-
responseUsage: Boolean(completedResultRecord?.response?.usage),
|
|
854
|
+
responseUsage: Boolean(asRecord(completedResultRecord?.response)?.usage),
|
|
928
855
|
},
|
|
929
856
|
});
|
|
930
857
|
}
|
|
931
|
-
|
|
858
|
+
}
|
|
859
|
+
#toTerminalEvent(result) {
|
|
860
|
+
if (result.type === 'approval_required') {
|
|
861
|
+
return {
|
|
862
|
+
type: 'approval_required',
|
|
863
|
+
approval: {
|
|
864
|
+
agentName: result.approval.agentName,
|
|
865
|
+
toolName: result.approval.toolName,
|
|
866
|
+
argumentsText: result.approval.argumentsText,
|
|
867
|
+
...(result.approval.callId ? { callId: result.approval.callId } : {}),
|
|
868
|
+
},
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
return {
|
|
872
|
+
type: 'final',
|
|
873
|
+
finalText: result.finalText,
|
|
874
|
+
...(result.reasoningText ? { reasoningText: result.reasoningText } : {}),
|
|
875
|
+
...(result.commandMessages?.length ? { commandMessages: result.commandMessages } : {}),
|
|
876
|
+
...(result.usage ? { usage: result.usage } : {}),
|
|
877
|
+
};
|
|
932
878
|
}
|
|
933
879
|
#buildResult(result, finalOutputOverride, reasoningOutputOverride, emittedCommandIds, usage) {
|
|
934
880
|
if (result.interruptions && result.interruptions.length > 0) {
|
|
@@ -940,29 +886,26 @@ export class ConversationSession {
|
|
|
940
886
|
toolCallArgumentsById: new Map(this.toolCallArgumentsById),
|
|
941
887
|
});
|
|
942
888
|
let argumentsText = '';
|
|
943
|
-
const
|
|
889
|
+
const interruptionRecord = asRecord(interruption);
|
|
890
|
+
const toolName = getString(interruptionRecord, 'name');
|
|
944
891
|
// For shell_call (built-in shell tool), extract commands from action
|
|
945
892
|
// For function tools (bash, shell), extract from arguments
|
|
946
|
-
if (
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
893
|
+
if (getString(interruptionRecord, 'type') === 'shell_call') {
|
|
894
|
+
const action = asRecord(interruptionRecord?.action);
|
|
895
|
+
const actionCommands = action?.commands;
|
|
896
|
+
if (actionCommands) {
|
|
897
|
+
argumentsText = Array.isArray(actionCommands) ? actionCommands.join('\n') : String(actionCommands);
|
|
951
898
|
}
|
|
952
899
|
}
|
|
953
900
|
else {
|
|
954
|
-
argumentsText = getCommandFromArgs(
|
|
901
|
+
argumentsText = getCommandFromArgs(interruptionRecord?.arguments);
|
|
955
902
|
}
|
|
956
|
-
const
|
|
957
|
-
|
|
958
|
-
interruption?.call_id ??
|
|
959
|
-
interruption?.tool_call_id ??
|
|
960
|
-
interruption?.toolCallId ??
|
|
961
|
-
interruption?.id;
|
|
903
|
+
const agent = asRecord(interruptionRecord?.agent);
|
|
904
|
+
const callId = getCallIdFromObject(interruption);
|
|
962
905
|
return {
|
|
963
906
|
type: 'approval_required',
|
|
964
907
|
approval: {
|
|
965
|
-
agentName:
|
|
908
|
+
agentName: getString(agent, 'name') ?? 'Agent',
|
|
966
909
|
toolName: toolName ?? 'Unknown Tool',
|
|
967
910
|
argumentsText,
|
|
968
911
|
rawInterruption: interruption,
|