@thegitai/cli 1.0.0-preview.39 → 1.0.0-preview.40
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/src/api/chat.js +24 -7
- package/dist/src/api/http.js +58 -0
- package/package.json +6 -6
package/dist/src/api/chat.js
CHANGED
|
@@ -4,7 +4,7 @@ import { applySessionSnapshot, saveSessionState, snapshotFromSession, } from '..
|
|
|
4
4
|
import { executeLocalToolCall } from '../tool-executor.js';
|
|
5
5
|
import { saveGeneratedImage } from '../tools/save-generated-image.js';
|
|
6
6
|
import { isUserInputQuestionArray } from './contracts.js';
|
|
7
|
-
import { createTraceContext, gatewayFailureCategory, normalizeServerUrl, readErrorResponse, } from './http.js';
|
|
7
|
+
import { createTraceContext, gatewayFailureCategory, transportFailureDetail, normalizeServerUrl, readErrorResponse, } from './http.js';
|
|
8
8
|
import { collectClientEnvironment } from '../client-environment.js';
|
|
9
9
|
import { collectProjectOrientation } from '../project-orientation.js';
|
|
10
10
|
import { describeSessionImageStore } from '../core/session-image-store.js';
|
|
@@ -126,6 +126,18 @@ function preserveFailedTurnInput(session, input, category) {
|
|
|
126
126
|
});
|
|
127
127
|
appendTurnFailureMarker(session, category);
|
|
128
128
|
}
|
|
129
|
+
function tagTurnPhase(phase, run) {
|
|
130
|
+
return run().catch((error) => {
|
|
131
|
+
if (error && typeof error === 'object' && !error.thegitaiPhase) {
|
|
132
|
+
error.thegitaiPhase = phase;
|
|
133
|
+
}
|
|
134
|
+
throw error;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function turnPhaseOf(error) {
|
|
138
|
+
const phase = error?.thegitaiPhase;
|
|
139
|
+
return typeof phase === 'string' ? phase : null;
|
|
140
|
+
}
|
|
129
141
|
function historyHasToolCall(session, callId) {
|
|
130
142
|
return session.history.some((entry) => (entry.parts ?? []).some((part) => String(part?.functionCall?.id ?? '') === callId));
|
|
131
143
|
}
|
|
@@ -216,7 +228,7 @@ async function postToolResult({ config, turnId, event, result, session, fetchImp
|
|
|
216
228
|
toolStateSeq: ++toolStateSeqCounter,
|
|
217
229
|
};
|
|
218
230
|
const trace = createTraceContext(traceId);
|
|
219
|
-
const response = await fetchImpl(`${normalizeServerUrl(config.serverUrl)}/v1/chat/turn/${encodeURIComponent(turnId)}/tool-result`, {
|
|
231
|
+
const response = await tagTurnPhase('posting a tool result', () => fetchImpl(`${normalizeServerUrl(config.serverUrl)}/v1/chat/turn/${encodeURIComponent(turnId)}/tool-result`, {
|
|
220
232
|
method: 'POST',
|
|
221
233
|
headers: {
|
|
222
234
|
authorization: `Bearer ${config.token}`,
|
|
@@ -224,7 +236,7 @@ async function postToolResult({ config, turnId, event, result, session, fetchImp
|
|
|
224
236
|
...trace.headers,
|
|
225
237
|
},
|
|
226
238
|
body: JSON.stringify(payload),
|
|
227
|
-
});
|
|
239
|
+
}));
|
|
228
240
|
if (response.status === 410) {
|
|
229
241
|
return;
|
|
230
242
|
}
|
|
@@ -566,7 +578,7 @@ export async function sendServerUserMessage({ config, session, input, imageAttac
|
|
|
566
578
|
signal?.addEventListener('abort', preserveOnAbort, { once: true });
|
|
567
579
|
}
|
|
568
580
|
try {
|
|
569
|
-
const response = await fetchImpl(`${normalizeServerUrl(config.serverUrl)}/v1/chat/turn`, {
|
|
581
|
+
const response = await tagTurnPhase('opening the turn', () => fetchImpl(`${normalizeServerUrl(config.serverUrl)}/v1/chat/turn`, {
|
|
570
582
|
method: 'POST',
|
|
571
583
|
headers: {
|
|
572
584
|
accept: 'text/event-stream',
|
|
@@ -576,11 +588,11 @@ export async function sendServerUserMessage({ config, session, input, imageAttac
|
|
|
576
588
|
},
|
|
577
589
|
body: JSON.stringify(request),
|
|
578
590
|
signal,
|
|
579
|
-
});
|
|
591
|
+
}));
|
|
580
592
|
if (!response.ok) {
|
|
581
593
|
throw await readErrorResponse(response, trace.traceId);
|
|
582
594
|
}
|
|
583
|
-
const result = await consumeTurnStream({
|
|
595
|
+
const result = await tagTurnPhase('reading the turn stream', () => consumeTurnStream({
|
|
584
596
|
response,
|
|
585
597
|
config,
|
|
586
598
|
session,
|
|
@@ -590,7 +602,7 @@ export async function sendServerUserMessage({ config, session, input, imageAttac
|
|
|
590
602
|
traceId: trace.traceId,
|
|
591
603
|
onTurnStart,
|
|
592
604
|
onInterjectionDelivered,
|
|
593
|
-
});
|
|
605
|
+
}));
|
|
594
606
|
applySessionSnapshot(session, result.snapshot, { preserveAgentMode: true });
|
|
595
607
|
return {
|
|
596
608
|
text: result.text,
|
|
@@ -606,6 +618,11 @@ export async function sendServerUserMessage({ config, session, input, imageAttac
|
|
|
606
618
|
? error
|
|
607
619
|
: new TurnCancelledError();
|
|
608
620
|
}
|
|
621
|
+
const transportDetail = error instanceof ChatTurnFailedError ? null : transportFailureDetail(error);
|
|
622
|
+
if (transportDetail && error instanceof Error) {
|
|
623
|
+
const phase = turnPhaseOf(error) ?? 'reaching the server';
|
|
624
|
+
error.message = `connection lost while ${phase}: ${transportDetail} [trace ${trace.traceId}]`;
|
|
625
|
+
}
|
|
609
626
|
const category = error instanceof ChatTurnFailedError
|
|
610
627
|
? error.category
|
|
611
628
|
:
|
package/dist/src/api/http.js
CHANGED
|
@@ -126,7 +126,65 @@ export function nonJsonErrorMessage(body, status) {
|
|
|
126
126
|
const base = gateway ?? (summary || `Request failed with ${status}`);
|
|
127
127
|
return rayId ? `${base} (Cloudflare Ray ID: ${rayId})` : base;
|
|
128
128
|
}
|
|
129
|
+
const TRANSPORT_CODES = new Set([
|
|
130
|
+
'ECONNRESET',
|
|
131
|
+
'ECONNREFUSED',
|
|
132
|
+
'ECONNABORTED',
|
|
133
|
+
'EPIPE',
|
|
134
|
+
'ETIMEDOUT',
|
|
135
|
+
'ENOTFOUND',
|
|
136
|
+
'EAI_AGAIN',
|
|
137
|
+
'EHOSTUNREACH',
|
|
138
|
+
'ENETUNREACH',
|
|
139
|
+
'EPROTO',
|
|
140
|
+
'ERR_STREAM_PREMATURE_CLOSE',
|
|
141
|
+
]);
|
|
142
|
+
const TRANSPORT_PHRASES = [
|
|
143
|
+
[/\bsocket hang up\b/i, 'socket hang up'],
|
|
144
|
+
[/\bother side closed\b/i, 'other side closed'],
|
|
145
|
+
[/\bpremature close\b/i, 'premature close'],
|
|
146
|
+
[/\bterminated\b/i, 'terminated'],
|
|
147
|
+
[/\bfetch failed\b/i, 'fetch failed'],
|
|
148
|
+
[/\bnetwork timeout\b/i, 'network timeout'],
|
|
149
|
+
[/\bnetwork error\b/i, 'network error'],
|
|
150
|
+
[/\bheaders timeout\b/i, 'headers timeout'],
|
|
151
|
+
[/\bbody timeout\b/i, 'body timeout'],
|
|
152
|
+
];
|
|
153
|
+
export function transportFailureDetail(error) {
|
|
154
|
+
if (error instanceof ServerApiError)
|
|
155
|
+
return null;
|
|
156
|
+
const parts = [];
|
|
157
|
+
const seen = new Set();
|
|
158
|
+
const queue = [error];
|
|
159
|
+
while (queue.length && seen.size < 24) {
|
|
160
|
+
const current = queue.shift();
|
|
161
|
+
if (!current || typeof current !== 'object' || seen.has(current))
|
|
162
|
+
continue;
|
|
163
|
+
seen.add(current);
|
|
164
|
+
const code = String(current.code ?? '');
|
|
165
|
+
if (TRANSPORT_CODES.has(code) || /^UND_ERR_[A-Z_]+$/.test(code)) {
|
|
166
|
+
if (!parts.includes(code))
|
|
167
|
+
parts.push(code);
|
|
168
|
+
}
|
|
169
|
+
const message = String(current.message ?? '');
|
|
170
|
+
for (const [pattern, label] of TRANSPORT_PHRASES) {
|
|
171
|
+
if (pattern.test(message) && !parts.includes(label))
|
|
172
|
+
parts.push(label);
|
|
173
|
+
}
|
|
174
|
+
if (current.cause)
|
|
175
|
+
queue.push(current.cause);
|
|
176
|
+
const nested = current.errors;
|
|
177
|
+
if (Array.isArray(nested))
|
|
178
|
+
queue.push(...nested);
|
|
179
|
+
}
|
|
180
|
+
return parts.length ? parts.slice(0, 3).join(' <- ') : null;
|
|
181
|
+
}
|
|
182
|
+
function isTransportFailure(error) {
|
|
183
|
+
return transportFailureDetail(error) !== null;
|
|
184
|
+
}
|
|
129
185
|
export function gatewayFailureCategory(error) {
|
|
186
|
+
if (isTransportFailure(error))
|
|
187
|
+
return 'connection_error';
|
|
130
188
|
if (!(error instanceof ServerApiError))
|
|
131
189
|
return null;
|
|
132
190
|
return error.status in GATEWAY_STATUS_MESSAGES ? 'gateway_error' : null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thegitai/cli",
|
|
3
|
-
"version": "1.0.0-preview.
|
|
3
|
+
"version": "1.0.0-preview.40",
|
|
4
4
|
"description": "TheGitAI is an agentic AI coding tool for your terminal. It reads and searches your repository, writes and edits files, runs your tests, and verifies the change before handing it back.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentic-ai",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"@lydell/node-pty-linux-x64": "1.1.0",
|
|
45
45
|
"@lydell/node-pty-win32-arm64": "1.1.0",
|
|
46
46
|
"@lydell/node-pty-win32-x64": "1.1.0",
|
|
47
|
-
"@thegitai/tui-darwin-arm64": "1.0.0-preview.
|
|
48
|
-
"@thegitai/tui-darwin-x64": "1.0.0-preview.
|
|
49
|
-
"@thegitai/tui-linux-arm64": "1.0.0-preview.
|
|
50
|
-
"@thegitai/tui-linux-x64": "1.0.0-preview.
|
|
51
|
-
"@thegitai/tui-win32-x64": "1.0.0-preview.
|
|
47
|
+
"@thegitai/tui-darwin-arm64": "1.0.0-preview.40",
|
|
48
|
+
"@thegitai/tui-darwin-x64": "1.0.0-preview.40",
|
|
49
|
+
"@thegitai/tui-linux-arm64": "1.0.0-preview.40",
|
|
50
|
+
"@thegitai/tui-linux-x64": "1.0.0-preview.40",
|
|
51
|
+
"@thegitai/tui-win32-x64": "1.0.0-preview.40",
|
|
52
52
|
"@vscode/ripgrep": "1.18.0"
|
|
53
53
|
},
|
|
54
54
|
"repository": {
|