shortcutxl 0.3.70 → 0.3.71
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/CHANGELOG.md +4 -0
- package/dist/app/agent-session.d.ts +1 -1
- package/dist/app/agent-session.js +11 -11
- package/dist/app/providers/shortcut-api-v2-transport.js +1 -0
- package/dist/cli.js +168 -66
- package/dist/contracts/model-stream.d.ts +10 -0
- package/dist/contracts/model-stream.js +30 -2
- package/dist/core/run-failure.js +13 -16
- package/dist/core/session/session-error-recovery.d.ts +29 -1
- package/dist/core/session/session-error-recovery.js +119 -48
- package/dist/embedded-agent/anthropic-messages-transport.js +2 -0
- package/dist/embedded-agent/host-tools/mode-host-tools.js +2 -1
- package/dist/embedded-agent/model-registry.d.ts +1 -0
- package/dist/embedded-agent/model-registry.js +1 -0
- package/dist/embedded-agent/recovery-wiring.js +32 -10
- package/dist/model-ids.d.ts +2 -0
- package/dist/model-ids.js +2 -0
- package/dist/rpc/rpc-mode.js +1 -1
- package/dist/shared/files/file-content.d.ts +0 -1
- package/dist/shared/files/file-content.js +0 -13
- package/dist/shell/interactive/interactive-mode.js +5 -2
- package/dist/shortcut-model-catalog.d.ts +1 -1
- package/dist/shortcut-model-catalog.js +17 -1
- package/package.json +1 -1
- package/user-docs/dist/shortcutxl-docs.pdf +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.71]
|
|
4
|
+
|
|
5
|
+
- **Responsive retry recovery** - CLI sessions now keep showing retry progress and can cancel an active retry without freezing.
|
|
6
|
+
|
|
3
7
|
## [0.3.70]
|
|
4
8
|
|
|
5
9
|
- **GPT-5.6 Sol default** - ShortcutXL now defaults CLI and embedded sessions to GPT-5.6 Sol and shows clearer compact model labels.
|
|
@@ -499,7 +499,7 @@ export declare class AgentSession {
|
|
|
499
499
|
warnings: string[];
|
|
500
500
|
}>;
|
|
501
501
|
/** Cancel in-progress recovery. */
|
|
502
|
-
abortRetry(): void
|
|
502
|
+
abortRetry(): Promise<void>;
|
|
503
503
|
/** Whether error recovery is currently in progress */
|
|
504
504
|
get isRetrying(): boolean;
|
|
505
505
|
/** Whether auto-retry is enabled */
|
|
@@ -452,16 +452,8 @@ export class AgentSession {
|
|
|
452
452
|
await this._extensions.extensionRunner.runPostTurnHandlers(turnIndex, event.message, event.toolResults);
|
|
453
453
|
}
|
|
454
454
|
// --- Step 5: Retry success tracking (sync) ---
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
// race condition: session.prompt() would return while the retry loop was
|
|
458
|
-
// still executing tool calls, causing print-mode to exit and kill the
|
|
459
|
-
// in-flight tool execution.
|
|
460
|
-
if (event.type === 'agent_end' && this._recovery.attempt > 0) {
|
|
461
|
-
const lastAssistant = this._findLastAssistantInMessages(event.messages);
|
|
462
|
-
if (!lastAssistant || lastAssistant.stopReason !== 'error') {
|
|
463
|
-
this._recovery.onSuccess();
|
|
464
|
-
}
|
|
455
|
+
if (event.type === 'agent_end') {
|
|
456
|
+
this._recovery.settleAgentEnd(this._findLastAssistantInMessages(event.messages), this._checkContextOverflow);
|
|
465
457
|
}
|
|
466
458
|
// --- Step 6: Post-agent: stop hooks → retry → compaction (async, sequential, conditional) ---
|
|
467
459
|
if (event.type === 'agent_end') {
|
|
@@ -1016,6 +1008,8 @@ export class AgentSession {
|
|
|
1016
1008
|
await this.agent.prompt(messages);
|
|
1017
1009
|
await this._agentEventQueue.catch(() => { });
|
|
1018
1010
|
await this._recovery.wait();
|
|
1011
|
+
// Keep prompt open until a cancelled continuation observes its abort signal.
|
|
1012
|
+
await this.agent.waitForIdle();
|
|
1019
1013
|
}
|
|
1020
1014
|
finally {
|
|
1021
1015
|
await this._agentEventQueue.catch(() => { });
|
|
@@ -1051,6 +1045,7 @@ export class AgentSession {
|
|
|
1051
1045
|
await this.agent.prompt([buildGoalContinuationMessage(goal)]);
|
|
1052
1046
|
await this._agentEventQueue.catch(() => { });
|
|
1053
1047
|
await this._recovery.wait();
|
|
1048
|
+
await this.agent.waitForIdle();
|
|
1054
1049
|
}
|
|
1055
1050
|
finally {
|
|
1056
1051
|
await this._agentEventQueue.catch(() => { });
|
|
@@ -1434,8 +1429,13 @@ export class AgentSession {
|
|
|
1434
1429
|
// Error Recovery (delegated to SessionErrorRecovery)
|
|
1435
1430
|
// =========================================================================
|
|
1436
1431
|
/** Cancel in-progress recovery. */
|
|
1437
|
-
abortRetry() {
|
|
1432
|
+
async abortRetry() {
|
|
1433
|
+
const wasRetrying = this._recovery.isRetrying;
|
|
1438
1434
|
this._recovery.abort();
|
|
1435
|
+
if (!wasRetrying)
|
|
1436
|
+
return;
|
|
1437
|
+
this.agent.abort();
|
|
1438
|
+
await this.agent.waitForIdle();
|
|
1439
1439
|
}
|
|
1440
1440
|
/** Whether error recovery is currently in progress */
|
|
1441
1441
|
get isRetrying() {
|
|
@@ -17,6 +17,7 @@ export function resolveShortcutApiV2Transport(modelId) {
|
|
|
17
17
|
const normalized = modelId.trim().toLowerCase();
|
|
18
18
|
// Shortcut-hosted "Pivot" and Fireworks GLM both speak OpenAI completions.
|
|
19
19
|
if (normalized === SHORTCUT_MODEL_ID.Hosted ||
|
|
20
|
+
normalized === SHORTCUT_MODEL_ID.Grok45 ||
|
|
20
21
|
normalized === SHORTCUT_MODEL_ID.Glm52Fireworks.toLowerCase() ||
|
|
21
22
|
normalized.startsWith('accounts/fireworks/')) {
|
|
22
23
|
return 'openai-completions';
|
package/dist/cli.js
CHANGED
|
@@ -283150,6 +283150,7 @@ var init_model_ids = __esm({
|
|
|
283150
283150
|
ClaudeOpus48: "claude-opus-4-8",
|
|
283151
283151
|
Glm52Fireworks: "accounts/fireworks/models/glm-5p2",
|
|
283152
283152
|
Gpt56Sol: "gpt-5.6-sol",
|
|
283153
|
+
Grok45: "grok-4.5",
|
|
283153
283154
|
Hosted: "pivot",
|
|
283154
283155
|
Gemini3Flash: "gemini-3-flash-preview",
|
|
283155
283156
|
ClaudeHaiku45: "claude-haiku-4-5-20251001"
|
|
@@ -283159,6 +283160,7 @@ var init_model_ids = __esm({
|
|
|
283159
283160
|
ClaudeOpus48: `${SHORTCUT_PROVIDER_ID}/${SHORTCUT_MODEL_ID.ClaudeOpus48}`,
|
|
283160
283161
|
Glm52Fireworks: `${SHORTCUT_PROVIDER_ID}/${SHORTCUT_MODEL_ID.Glm52Fireworks}`,
|
|
283161
283162
|
Gpt56Sol: `${SHORTCUT_PROVIDER_ID}/${SHORTCUT_MODEL_ID.Gpt56Sol}`,
|
|
283163
|
+
Grok45: `${SHORTCUT_PROVIDER_ID}/${SHORTCUT_MODEL_ID.Grok45}`,
|
|
283162
283164
|
Hosted: `${SHORTCUT_PROVIDER_ID}/${SHORTCUT_MODEL_ID.Hosted}`
|
|
283163
283165
|
};
|
|
283164
283166
|
SHORTCUT_OPENAI_MODEL_ID = SHORTCUT_MODEL_ID.Gpt56Sol;
|
|
@@ -323554,6 +323556,17 @@ var init_validation = __esm({
|
|
|
323554
323556
|
});
|
|
323555
323557
|
|
|
323556
323558
|
// src/contracts/model-stream.ts
|
|
323559
|
+
function isTerminalModelStreamErrorDetails(details) {
|
|
323560
|
+
if (!details) return false;
|
|
323561
|
+
if (details.status === 402) return true;
|
|
323562
|
+
return details.code !== void 0 && TERMINAL_MODEL_STREAM_ERROR_CODE_SET.has(details.code);
|
|
323563
|
+
}
|
|
323564
|
+
function isInsufficientCreditsModelStreamFailure(details, message) {
|
|
323565
|
+
return details?.status === 402 || details?.code === INSUFFICIENT_CREDITS_ERROR_CODE || message === INSUFFICIENT_CREDITS_SAFE_MESSAGE;
|
|
323566
|
+
}
|
|
323567
|
+
function isTerminalModelStreamFailure(details, message) {
|
|
323568
|
+
return isTerminalModelStreamErrorDetails(details) || message === INSUFFICIENT_CREDITS_SAFE_MESSAGE;
|
|
323569
|
+
}
|
|
323557
323570
|
function isRecord(value) {
|
|
323558
323571
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
323559
323572
|
}
|
|
@@ -323581,8 +323594,8 @@ function isShortcutXLModelStreamErrorDetails(value) {
|
|
|
323581
323594
|
return true;
|
|
323582
323595
|
}
|
|
323583
323596
|
function safeModelStreamErrorMessage(details) {
|
|
323584
|
-
if (details
|
|
323585
|
-
return
|
|
323597
|
+
if (isInsufficientCreditsModelStreamFailure(details, void 0)) {
|
|
323598
|
+
return INSUFFICIENT_CREDITS_SAFE_MESSAGE;
|
|
323586
323599
|
}
|
|
323587
323600
|
if (details?.code === "context_overflow") {
|
|
323588
323601
|
return "The conversation exceeded the model context window.";
|
|
@@ -323659,7 +323672,7 @@ function extractStructuredHttpErrorPayload(value) {
|
|
|
323659
323672
|
}
|
|
323660
323673
|
return void 0;
|
|
323661
323674
|
}
|
|
323662
|
-
var LLMErrorMetadataHeaders, ShortcutXLModelStreamFetchStage, STREAM_ERROR_PROVIDER_TYPES, MODEL_ACCESS_DENIED_PROVIDER_ERROR_TYPE, MODEL_ACCESS_DENIED_MESSAGE_PATTERN;
|
|
323675
|
+
var LLMErrorMetadataHeaders, ShortcutXLModelStreamFetchStage, INSUFFICIENT_CREDITS_ERROR_CODE, INSUFFICIENT_CREDITS_SAFE_MESSAGE, TERMINAL_MODEL_STREAM_ERROR_CODES, TERMINAL_MODEL_STREAM_ERROR_CODE_SET, STREAM_ERROR_PROVIDER_TYPES, MODEL_ACCESS_DENIED_PROVIDER_ERROR_TYPE, MODEL_ACCESS_DENIED_MESSAGE_PATTERN;
|
|
323663
323676
|
var init_model_stream = __esm({
|
|
323664
323677
|
"src/contracts/model-stream.ts"() {
|
|
323665
323678
|
"use strict";
|
|
@@ -323681,6 +323694,21 @@ var init_model_stream = __esm({
|
|
|
323681
323694
|
/** Response started but the SSE read failed or idle-timed out mid-stream. */
|
|
323682
323695
|
ResponseBody: "response_body"
|
|
323683
323696
|
};
|
|
323697
|
+
INSUFFICIENT_CREDITS_ERROR_CODE = "insufficient_credits";
|
|
323698
|
+
INSUFFICIENT_CREDITS_SAFE_MESSAGE = "Insufficient credits. Purchase more credits to continue using paid models.";
|
|
323699
|
+
TERMINAL_MODEL_STREAM_ERROR_CODES = [
|
|
323700
|
+
INSUFFICIENT_CREDITS_ERROR_CODE,
|
|
323701
|
+
"customer_credential_scope_required",
|
|
323702
|
+
"customer_credential_resolution_failed",
|
|
323703
|
+
"customer_credential_resolution_timeout",
|
|
323704
|
+
"customer_credential_resolution_cancelled",
|
|
323705
|
+
"customer_credential_unconfigured",
|
|
323706
|
+
"unsupported_credential_mode",
|
|
323707
|
+
"model_policy_rejected"
|
|
323708
|
+
];
|
|
323709
|
+
TERMINAL_MODEL_STREAM_ERROR_CODE_SET = new Set(
|
|
323710
|
+
TERMINAL_MODEL_STREAM_ERROR_CODES
|
|
323711
|
+
);
|
|
323684
323712
|
STREAM_ERROR_PROVIDER_TYPES = /* @__PURE__ */ new Set([
|
|
323685
323713
|
"overloaded",
|
|
323686
323714
|
"rate_limit",
|
|
@@ -324045,10 +324073,7 @@ function getErrorDetails(message) {
|
|
|
324045
324073
|
}
|
|
324046
324074
|
function classifyStructuredError(details) {
|
|
324047
324075
|
if (!details) return null;
|
|
324048
|
-
if (
|
|
324049
|
-
return "terminal";
|
|
324050
|
-
}
|
|
324051
|
-
if (details.status === 402) return "terminal";
|
|
324076
|
+
if (isTerminalModelStreamErrorDetails(details)) return "terminal";
|
|
324052
324077
|
if (details.providerErrorType === "unauthorized" || details.status === 401) return "auth_error";
|
|
324053
324078
|
if (details.providerErrorType === "rate_limit" || details.status === 429) return "rate_limit";
|
|
324054
324079
|
if (details.providerErrorType === "overloaded" || details.status === 529) return "overloaded";
|
|
@@ -324060,22 +324085,28 @@ function classifyStructuredError(details) {
|
|
|
324060
324085
|
}
|
|
324061
324086
|
return null;
|
|
324062
324087
|
}
|
|
324063
|
-
function
|
|
324064
|
-
if (message.stopReason !== "error") return
|
|
324065
|
-
if (!message.errorMessage) return
|
|
324066
|
-
if (isContextOverflow2(message)) return "context_overflow";
|
|
324067
|
-
const
|
|
324088
|
+
function classifyErrorDisposition(message, isContextOverflow2) {
|
|
324089
|
+
if (message.stopReason !== "error") return { kind: "none" };
|
|
324090
|
+
if (!message.errorMessage) return { kind: "none" };
|
|
324091
|
+
if (isContextOverflow2(message)) return { kind: "categorized", category: "context_overflow" };
|
|
324092
|
+
const details = getErrorDetails(message);
|
|
324093
|
+
const structuredCategory = classifyStructuredError(details);
|
|
324068
324094
|
if (structuredCategory === "terminal") {
|
|
324069
|
-
return
|
|
324095
|
+
return { kind: "terminal" };
|
|
324070
324096
|
}
|
|
324071
|
-
if (structuredCategory) return structuredCategory;
|
|
324097
|
+
if (structuredCategory) return { kind: "categorized", category: structuredCategory };
|
|
324072
324098
|
const err = message.errorMessage;
|
|
324073
|
-
if (
|
|
324074
|
-
if (
|
|
324075
|
-
if (
|
|
324076
|
-
if (
|
|
324077
|
-
if (
|
|
324078
|
-
return "
|
|
324099
|
+
if (isTerminalModelStreamFailure(details, err)) return { kind: "terminal" };
|
|
324100
|
+
if (AUTH_ERROR_PATTERN.test(err)) return { kind: "categorized", category: "auth_error" };
|
|
324101
|
+
if (RATE_LIMIT_PATTERN.test(err)) return { kind: "categorized", category: "rate_limit" };
|
|
324102
|
+
if (OVERLOADED_PATTERN.test(err)) return { kind: "categorized", category: "overloaded" };
|
|
324103
|
+
if (SERVER_ERROR_PATTERN.test(err)) return { kind: "categorized", category: "server_error" };
|
|
324104
|
+
if (CONNECTION_ERROR_PATTERN.test(err)) return { kind: "categorized", category: "server_error" };
|
|
324105
|
+
return { kind: "categorized", category: "unknown" };
|
|
324106
|
+
}
|
|
324107
|
+
function classifyError(message, isContextOverflow2) {
|
|
324108
|
+
const disposition = classifyErrorDisposition(message, isContextOverflow2);
|
|
324109
|
+
return disposition.kind === "categorized" ? disposition.category : null;
|
|
324079
324110
|
}
|
|
324080
324111
|
function computeDelay(config2, attempt) {
|
|
324081
324112
|
if (config2.baseDelayMs === 0) return 0;
|
|
@@ -324087,26 +324118,17 @@ function computeDelay(config2, attempt) {
|
|
|
324087
324118
|
function getCompactionKeepRecentTokens(attempt) {
|
|
324088
324119
|
return COMPACTION_ESCALATION[Math.min(attempt - 1, COMPACTION_ESCALATION.length - 1)];
|
|
324089
324120
|
}
|
|
324090
|
-
var AUTH_ERROR_PATTERN, RATE_LIMIT_PATTERN, OVERLOADED_PATTERN, SERVER_ERROR_PATTERN, CONNECTION_ERROR_PATTERN,
|
|
324121
|
+
var AUTH_ERROR_PATTERN, RATE_LIMIT_PATTERN, OVERLOADED_PATTERN, SERVER_ERROR_PATTERN, CONNECTION_ERROR_PATTERN, SERVER_ERROR_MAX_ATTEMPTS, STRATEGY_CONFIGS, COMPACTION_ESCALATION, SessionErrorRecovery;
|
|
324091
324122
|
var init_session_error_recovery = __esm({
|
|
324092
324123
|
"src/core/session/session-error-recovery.ts"() {
|
|
324093
324124
|
"use strict";
|
|
324125
|
+
init_model_stream();
|
|
324094
324126
|
init_sleep();
|
|
324095
324127
|
AUTH_ERROR_PATTERN = /\b401\b|invalid.?api.?key|unauthorized|authentication.?failed/i;
|
|
324096
324128
|
RATE_LIMIT_PATTERN = /rate.?limit|too many requests|\b429\b/i;
|
|
324097
324129
|
OVERLOADED_PATTERN = /overloaded|\b529\b/i;
|
|
324098
324130
|
SERVER_ERROR_PATTERN = /\b500\b|\b502\b|\b503\b|\b504\b|\b524\b|service.?unavailable|server error|internal error/i;
|
|
324099
324131
|
CONNECTION_ERROR_PATTERN = /connection.?error|connection.?refused|other side closed|fetch failed|failed to fetch|upstream.?connect|reset before headers|terminated|retry delay|peer closed|complete message body|load failed|network.?error|internet connection appears to be offline/i;
|
|
324100
|
-
TERMINAL_GATEWAY_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
324101
|
-
"insufficient_credits",
|
|
324102
|
-
"customer_credential_scope_required",
|
|
324103
|
-
"customer_credential_resolution_failed",
|
|
324104
|
-
"customer_credential_resolution_timeout",
|
|
324105
|
-
"customer_credential_resolution_cancelled",
|
|
324106
|
-
"customer_credential_unconfigured",
|
|
324107
|
-
"unsupported_credential_mode",
|
|
324108
|
-
"model_policy_rejected"
|
|
324109
|
-
]);
|
|
324110
324132
|
SERVER_ERROR_MAX_ATTEMPTS = 5;
|
|
324111
324133
|
STRATEGY_CONFIGS = {
|
|
324112
324134
|
rate_limit: {
|
|
@@ -324195,11 +324217,13 @@ var init_session_error_recovery = __esm({
|
|
|
324195
324217
|
_emit(event) {
|
|
324196
324218
|
this._listener?.(event);
|
|
324197
324219
|
}
|
|
324198
|
-
|
|
324199
|
-
|
|
324200
|
-
|
|
324201
|
-
return
|
|
324202
|
-
|
|
324220
|
+
_continueRecovery(category, attempt, actions) {
|
|
324221
|
+
const activeRecovery = this._promise;
|
|
324222
|
+
void Promise.resolve().then(() => {
|
|
324223
|
+
if (!activeRecovery || this._promise !== activeRecovery) return;
|
|
324224
|
+
return actions.continue();
|
|
324225
|
+
}).catch((error2) => {
|
|
324226
|
+
if (!activeRecovery || this._promise !== activeRecovery) return;
|
|
324203
324227
|
this._emit({
|
|
324204
324228
|
type: "recovery_end",
|
|
324205
324229
|
category,
|
|
@@ -324208,17 +324232,37 @@ var init_session_error_recovery = __esm({
|
|
|
324208
324232
|
finalError: error2 instanceof Error ? error2.message : String(error2)
|
|
324209
324233
|
});
|
|
324210
324234
|
this._reset();
|
|
324211
|
-
|
|
324212
|
-
|
|
324235
|
+
});
|
|
324236
|
+
return true;
|
|
324213
324237
|
}
|
|
324214
324238
|
/** Reset all state to idle. */
|
|
324215
324239
|
_reset() {
|
|
324240
|
+
this._abortController = void 0;
|
|
324216
324241
|
this._attempt = 0;
|
|
324217
324242
|
this._category = null;
|
|
324218
324243
|
this._resolve?.();
|
|
324219
324244
|
this._resolve = void 0;
|
|
324220
324245
|
this._promise = void 0;
|
|
324221
324246
|
}
|
|
324247
|
+
/** Emit a cancellation event once for an active, visible recovery. */
|
|
324248
|
+
_emitCancelledRecovery() {
|
|
324249
|
+
if (!this._promise || this._attempt <= 0 || !this._category) return;
|
|
324250
|
+
this._emit({
|
|
324251
|
+
type: "recovery_end",
|
|
324252
|
+
category: this._category,
|
|
324253
|
+
success: false,
|
|
324254
|
+
attempt: this._attempt,
|
|
324255
|
+
finalError: "Recovery cancelled"
|
|
324256
|
+
});
|
|
324257
|
+
}
|
|
324258
|
+
/** Cancel recovery state and any in-progress backoff sleep. */
|
|
324259
|
+
_cancelRecovery() {
|
|
324260
|
+
const abortController = this._abortController;
|
|
324261
|
+
this._abortController = void 0;
|
|
324262
|
+
this._emitCancelledRecovery();
|
|
324263
|
+
this._reset();
|
|
324264
|
+
abortController?.abort();
|
|
324265
|
+
}
|
|
324222
324266
|
/**
|
|
324223
324267
|
* Create the recovery promise synchronously when an agent_end event arrives.
|
|
324224
324268
|
*
|
|
@@ -324256,6 +324300,15 @@ var init_session_error_recovery = __esm({
|
|
|
324256
324300
|
}
|
|
324257
324301
|
const config2 = STRATEGY_CONFIGS[category];
|
|
324258
324302
|
if (config2.maxAttempts === 0) {
|
|
324303
|
+
if (this._attempt > 0 || this._promise) {
|
|
324304
|
+
this._emit({
|
|
324305
|
+
type: "recovery_end",
|
|
324306
|
+
category: this._category ?? category,
|
|
324307
|
+
success: false,
|
|
324308
|
+
attempt: this._attempt,
|
|
324309
|
+
finalError: message.errorMessage
|
|
324310
|
+
});
|
|
324311
|
+
}
|
|
324259
324312
|
this._reset();
|
|
324260
324313
|
return false;
|
|
324261
324314
|
}
|
|
@@ -324379,6 +324432,7 @@ var init_session_error_recovery = __esm({
|
|
|
324379
324432
|
}
|
|
324380
324433
|
/** Handle abort during sleep. */
|
|
324381
324434
|
_handleAbort(category) {
|
|
324435
|
+
if (!this._promise) return false;
|
|
324382
324436
|
const attempt = this._attempt;
|
|
324383
324437
|
this._abortController = void 0;
|
|
324384
324438
|
this._emit({
|
|
@@ -324406,10 +324460,37 @@ var init_session_error_recovery = __esm({
|
|
|
324406
324460
|
});
|
|
324407
324461
|
this._reset();
|
|
324408
324462
|
}
|
|
324463
|
+
/**
|
|
324464
|
+
* Called when a continuation turn reaches an unrecoverable assistant error.
|
|
324465
|
+
* This resolves the in-flight recovery as failed instead of treating the
|
|
324466
|
+
* terminal, non-retryable failure as a successful retry.
|
|
324467
|
+
*/
|
|
324468
|
+
onTerminalFailure(finalError) {
|
|
324469
|
+
if (this._attempt <= 0) return;
|
|
324470
|
+
const category = this._category ?? "unknown";
|
|
324471
|
+
this._emit({
|
|
324472
|
+
type: "recovery_end",
|
|
324473
|
+
category,
|
|
324474
|
+
success: false,
|
|
324475
|
+
attempt: this._attempt,
|
|
324476
|
+
finalError
|
|
324477
|
+
});
|
|
324478
|
+
this._reset();
|
|
324479
|
+
}
|
|
324480
|
+
/** Settle recovery when a continuation agent loop reaches agent_end. */
|
|
324481
|
+
settleAgentEnd(lastAssistant, isContextOverflow2) {
|
|
324482
|
+
if (this._attempt <= 0) return;
|
|
324483
|
+
if (!lastAssistant || lastAssistant.stopReason !== "error") {
|
|
324484
|
+
this.onSuccess();
|
|
324485
|
+
return;
|
|
324486
|
+
}
|
|
324487
|
+
if (classifyErrorDisposition(lastAssistant, isContextOverflow2).kind === "terminal") {
|
|
324488
|
+
this.onTerminalFailure(lastAssistant.errorMessage);
|
|
324489
|
+
}
|
|
324490
|
+
}
|
|
324409
324491
|
/** Cancel in-progress recovery. */
|
|
324410
324492
|
abort() {
|
|
324411
|
-
this.
|
|
324412
|
-
this._reset();
|
|
324493
|
+
this._cancelRecovery();
|
|
324413
324494
|
}
|
|
324414
324495
|
/** Wait for any in-progress recovery to complete. */
|
|
324415
324496
|
async wait() {
|
|
@@ -431129,7 +431210,8 @@ function cloneShortcutModelDefinition(model) {
|
|
|
431129
431210
|
return {
|
|
431130
431211
|
...model,
|
|
431131
431212
|
input: [...model.input],
|
|
431132
|
-
cost: { ...model.cost }
|
|
431213
|
+
cost: { ...model.cost },
|
|
431214
|
+
...model.compat ? { compat: { ...model.compat } } : {}
|
|
431133
431215
|
};
|
|
431134
431216
|
}
|
|
431135
431217
|
function createShortcutPublicModelDefinitions() {
|
|
@@ -431174,6 +431256,21 @@ var init_shortcut_model_catalog = __esm({
|
|
|
431174
431256
|
contextWindow: 1e6,
|
|
431175
431257
|
maxTokens: 128e3
|
|
431176
431258
|
},
|
|
431259
|
+
{
|
|
431260
|
+
id: SHORTCUT_MODEL_ID.Grok45,
|
|
431261
|
+
name: "Grok 4.5",
|
|
431262
|
+
reasoning: true,
|
|
431263
|
+
input: ["text", "image"],
|
|
431264
|
+
cost: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
|
|
431265
|
+
contextWindow: 5e5,
|
|
431266
|
+
maxTokens: 128e3,
|
|
431267
|
+
compat: {
|
|
431268
|
+
supportsStore: false,
|
|
431269
|
+
supportsDeveloperRole: false,
|
|
431270
|
+
supportsReasoningEffort: true,
|
|
431271
|
+
maxTokensField: "max_completion_tokens"
|
|
431272
|
+
}
|
|
431273
|
+
},
|
|
431177
431274
|
{
|
|
431178
431275
|
id: SHORTCUT_MODEL_ID.Glm52Fireworks,
|
|
431179
431276
|
name: "GLM 5.2",
|
|
@@ -431583,7 +431680,7 @@ var init_google_vertex_gateway = __esm({
|
|
|
431583
431680
|
// src/app/providers/shortcut-api-v2-transport.ts
|
|
431584
431681
|
function resolveShortcutApiV2Transport(modelId) {
|
|
431585
431682
|
const normalized = modelId.trim().toLowerCase();
|
|
431586
|
-
if (normalized === SHORTCUT_MODEL_ID.Hosted || normalized === SHORTCUT_MODEL_ID.Glm52Fireworks.toLowerCase() || normalized.startsWith("accounts/fireworks/")) {
|
|
431683
|
+
if (normalized === SHORTCUT_MODEL_ID.Hosted || normalized === SHORTCUT_MODEL_ID.Grok45 || normalized === SHORTCUT_MODEL_ID.Glm52Fireworks.toLowerCase() || normalized.startsWith("accounts/fireworks/")) {
|
|
431587
431684
|
return "openai-completions";
|
|
431588
431685
|
}
|
|
431589
431686
|
if (normalized.startsWith("claude-")) {
|
|
@@ -453414,9 +453511,6 @@ function operationKindToFailureCategory(kind, message) {
|
|
|
453414
453511
|
return RunFailureCategory.LlmStreamFailed;
|
|
453415
453512
|
}
|
|
453416
453513
|
}
|
|
453417
|
-
function isInsufficientCreditsFailure(details) {
|
|
453418
|
-
return details?.status === 402 || details?.code === "insufficient_credits";
|
|
453419
|
-
}
|
|
453420
453514
|
function createOperationFailureCause(input) {
|
|
453421
453515
|
const category = operationKindToFailureCategory(input.opKind, input.errorMessage);
|
|
453422
453516
|
return {
|
|
@@ -453430,6 +453524,17 @@ function createOperationFailureCause(input) {
|
|
|
453430
453524
|
};
|
|
453431
453525
|
}
|
|
453432
453526
|
function classifyModelStreamFailure(details, message) {
|
|
453527
|
+
if (isInsufficientCreditsModelStreamFailure(details, message)) {
|
|
453528
|
+
return {
|
|
453529
|
+
category: RunFailureCategory.BillingInsufficientCredits,
|
|
453530
|
+
source: RunFailureSource.Billing,
|
|
453531
|
+
httpStatus: details?.status,
|
|
453532
|
+
errorCode: details?.code ?? INSUFFICIENT_CREDITS_ERROR_CODE,
|
|
453533
|
+
retryable: details?.retryable,
|
|
453534
|
+
retryAfterMs: details?.retryAfterMs,
|
|
453535
|
+
transportFailureStage: details?.transportFailureStage
|
|
453536
|
+
};
|
|
453537
|
+
}
|
|
453433
453538
|
if (details) {
|
|
453434
453539
|
if (details.providerErrorType === "model_access_denied" || details.code === "model_access_denied") {
|
|
453435
453540
|
return {
|
|
@@ -453472,17 +453577,6 @@ function classifyModelStreamFailure(details, message) {
|
|
|
453472
453577
|
transportFailureStage: details.transportFailureStage
|
|
453473
453578
|
};
|
|
453474
453579
|
}
|
|
453475
|
-
if (isInsufficientCreditsFailure(details)) {
|
|
453476
|
-
return {
|
|
453477
|
-
category: RunFailureCategory.BillingInsufficientCredits,
|
|
453478
|
-
source: RunFailureSource.Billing,
|
|
453479
|
-
httpStatus: details.status,
|
|
453480
|
-
errorCode: details.code ?? "insufficient_credits",
|
|
453481
|
-
retryable: details.retryable,
|
|
453482
|
-
retryAfterMs: details.retryAfterMs,
|
|
453483
|
-
transportFailureStage: details.transportFailureStage
|
|
453484
|
-
};
|
|
453485
|
-
}
|
|
453486
453580
|
if (details.providerErrorType === "network" && details.transportFailureStage === ShortcutXLModelStreamFetchStage.BeforeResponse) {
|
|
453487
453581
|
return {
|
|
453488
453582
|
category: RunFailureCategory.LlmFetchFailed,
|
|
@@ -453603,7 +453697,7 @@ var init_run_failure = __esm({
|
|
|
453603
453697
|
init_error_classification();
|
|
453604
453698
|
init_session_error_recovery();
|
|
453605
453699
|
SAFE_MESSAGE_BY_CATEGORY = {
|
|
453606
|
-
[RunFailureCategory.BillingInsufficientCredits]:
|
|
453700
|
+
[RunFailureCategory.BillingInsufficientCredits]: INSUFFICIENT_CREDITS_SAFE_MESSAGE,
|
|
453607
453701
|
[RunFailureCategory.CompactionFailed]: "Context compaction failed.",
|
|
453608
453702
|
[RunFailureCategory.CompactionTimeout]: "Context compaction timed out.",
|
|
453609
453703
|
[RunFailureCategory.ContextTooLong]: "The conversation exceeded the model context window.",
|
|
@@ -473984,11 +474078,11 @@ ${budgetText}` : budgetText;
|
|
|
473984
474078
|
event.toolResults
|
|
473985
474079
|
);
|
|
473986
474080
|
}
|
|
473987
|
-
if (event.type === "agent_end"
|
|
473988
|
-
|
|
473989
|
-
|
|
473990
|
-
this.
|
|
473991
|
-
|
|
474081
|
+
if (event.type === "agent_end") {
|
|
474082
|
+
this._recovery.settleAgentEnd(
|
|
474083
|
+
this._findLastAssistantInMessages(event.messages),
|
|
474084
|
+
this._checkContextOverflow
|
|
474085
|
+
);
|
|
473992
474086
|
}
|
|
473993
474087
|
if (event.type === "agent_end") {
|
|
473994
474088
|
const stopHookContinued = await this._runStopHooks(event.messages);
|
|
@@ -474516,6 +474610,7 @@ ${budgetText}` : budgetText;
|
|
|
474516
474610
|
await this._agentEventQueue.catch(() => {
|
|
474517
474611
|
});
|
|
474518
474612
|
await this._recovery.wait();
|
|
474613
|
+
await this.agent.waitForIdle();
|
|
474519
474614
|
} finally {
|
|
474520
474615
|
await this._agentEventQueue.catch(() => {
|
|
474521
474616
|
});
|
|
@@ -474551,6 +474646,7 @@ ${budgetText}` : budgetText;
|
|
|
474551
474646
|
await this._agentEventQueue.catch(() => {
|
|
474552
474647
|
});
|
|
474553
474648
|
await this._recovery.wait();
|
|
474649
|
+
await this.agent.waitForIdle();
|
|
474554
474650
|
} finally {
|
|
474555
474651
|
await this._agentEventQueue.catch(() => {
|
|
474556
474652
|
});
|
|
@@ -474939,8 +475035,12 @@ ${budgetText}` : budgetText;
|
|
|
474939
475035
|
// Error Recovery (delegated to SessionErrorRecovery)
|
|
474940
475036
|
// =========================================================================
|
|
474941
475037
|
/** Cancel in-progress recovery. */
|
|
474942
|
-
abortRetry() {
|
|
475038
|
+
async abortRetry() {
|
|
475039
|
+
const wasRetrying = this._recovery.isRetrying;
|
|
474943
475040
|
this._recovery.abort();
|
|
475041
|
+
if (!wasRetrying) return;
|
|
475042
|
+
this.agent.abort();
|
|
475043
|
+
await this.agent.waitForIdle();
|
|
474944
475044
|
}
|
|
474945
475045
|
/** Whether error recovery is currently in progress */
|
|
474946
475046
|
get isRetrying() {
|
|
@@ -540208,7 +540308,7 @@ ${message}`, ["Yes", "No"], opts);
|
|
|
540208
540308
|
handleRecoveryStartEvent(event) {
|
|
540209
540309
|
this.retryEscapeHandler = this.defaultEditor.onEscape;
|
|
540210
540310
|
this.defaultEditor.onEscape = () => {
|
|
540211
|
-
this.session.abortRetry();
|
|
540311
|
+
void this.session.abortRetry();
|
|
540212
540312
|
};
|
|
540213
540313
|
const retryChildren = this.chatContainer.children;
|
|
540214
540314
|
for (let i2 = retryChildren.length - 1; i2 >= 0; i2--) {
|
|
@@ -540240,7 +540340,9 @@ ${message}`, ["Yes", "No"], opts);
|
|
|
540240
540340
|
this.retryLoader = void 0;
|
|
540241
540341
|
this.statusContainer.clear();
|
|
540242
540342
|
}
|
|
540243
|
-
if (!event.success) {
|
|
540343
|
+
if (!event.success && event.finalError === "Recovery cancelled") {
|
|
540344
|
+
this.showStatus("Recovery cancelled");
|
|
540345
|
+
} else if (!event.success) {
|
|
540244
540346
|
this.showError(
|
|
540245
540347
|
`Recovery failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`
|
|
540246
540348
|
);
|
|
@@ -544779,7 +544881,7 @@ async function runRpcMode(session) {
|
|
|
544779
544881
|
return success2(id, "set_auto_retry");
|
|
544780
544882
|
}
|
|
544781
544883
|
case "abort_retry": {
|
|
544782
|
-
session.abortRetry();
|
|
544884
|
+
await session.abortRetry();
|
|
544783
544885
|
return success2(id, "abort_retry");
|
|
544784
544886
|
}
|
|
544785
544887
|
// =================================================================
|
|
@@ -136,6 +136,10 @@ export interface ShortcutXLModelStreamFetchTrace {
|
|
|
136
136
|
readonly effortLevel?: ShortcutXLModelStreamEffortLevel;
|
|
137
137
|
readonly speed?: 'fast' | 'standard';
|
|
138
138
|
readonly thinkingType?: ShortcutXLModelStreamThinkingType;
|
|
139
|
+
/** Runtime run id when the host can safely correlate this fetch to an active run. */
|
|
140
|
+
readonly runId?: string;
|
|
141
|
+
/** Conversation/thread id when the host can safely correlate this fetch to a thread. */
|
|
142
|
+
readonly threadId?: string;
|
|
139
143
|
readonly sessionId?: string;
|
|
140
144
|
readonly userMessageId?: string;
|
|
141
145
|
readonly messageCount?: number;
|
|
@@ -200,6 +204,12 @@ export interface ShortcutXLModelStreamErrorDetails {
|
|
|
200
204
|
/** Client-side transport stage when the browser fetch failed outside HTTP. */
|
|
201
205
|
readonly transportFailureStage?: ShortcutXLModelStreamFetchStage;
|
|
202
206
|
}
|
|
207
|
+
export declare const INSUFFICIENT_CREDITS_ERROR_CODE = "insufficient_credits";
|
|
208
|
+
export declare const INSUFFICIENT_CREDITS_SAFE_MESSAGE = "Insufficient credits. Purchase more credits to continue using paid models.";
|
|
209
|
+
export declare const TERMINAL_MODEL_STREAM_ERROR_CODES: readonly ["insufficient_credits", "customer_credential_scope_required", "customer_credential_resolution_failed", "customer_credential_resolution_timeout", "customer_credential_resolution_cancelled", "customer_credential_unconfigured", "unsupported_credential_mode", "model_policy_rejected"];
|
|
210
|
+
export declare function isTerminalModelStreamErrorDetails(details: ShortcutXLModelStreamErrorDetails | undefined): boolean;
|
|
211
|
+
export declare function isInsufficientCreditsModelStreamFailure(details: ShortcutXLModelStreamErrorDetails | undefined, message: string | undefined): boolean;
|
|
212
|
+
export declare function isTerminalModelStreamFailure(details: ShortcutXLModelStreamErrorDetails | undefined, message: string | undefined): boolean;
|
|
203
213
|
export interface ShortcutXLAgentAssistantMessage {
|
|
204
214
|
readonly api: string;
|
|
205
215
|
readonly content: readonly ShortcutXLAgentAssistantContent[];
|
|
@@ -86,6 +86,34 @@ export const ShortcutXLModelStreamFetchStage = {
|
|
|
86
86
|
/** Response started but the SSE read failed or idle-timed out mid-stream. */
|
|
87
87
|
ResponseBody: 'response_body'
|
|
88
88
|
};
|
|
89
|
+
export const INSUFFICIENT_CREDITS_ERROR_CODE = 'insufficient_credits';
|
|
90
|
+
export const INSUFFICIENT_CREDITS_SAFE_MESSAGE = 'Insufficient credits. Purchase more credits to continue using paid models.';
|
|
91
|
+
export const TERMINAL_MODEL_STREAM_ERROR_CODES = [
|
|
92
|
+
INSUFFICIENT_CREDITS_ERROR_CODE,
|
|
93
|
+
'customer_credential_scope_required',
|
|
94
|
+
'customer_credential_resolution_failed',
|
|
95
|
+
'customer_credential_resolution_timeout',
|
|
96
|
+
'customer_credential_resolution_cancelled',
|
|
97
|
+
'customer_credential_unconfigured',
|
|
98
|
+
'unsupported_credential_mode',
|
|
99
|
+
'model_policy_rejected'
|
|
100
|
+
];
|
|
101
|
+
const TERMINAL_MODEL_STREAM_ERROR_CODE_SET = new Set(TERMINAL_MODEL_STREAM_ERROR_CODES);
|
|
102
|
+
export function isTerminalModelStreamErrorDetails(details) {
|
|
103
|
+
if (!details)
|
|
104
|
+
return false;
|
|
105
|
+
if (details.status === 402)
|
|
106
|
+
return true;
|
|
107
|
+
return details.code !== undefined && TERMINAL_MODEL_STREAM_ERROR_CODE_SET.has(details.code);
|
|
108
|
+
}
|
|
109
|
+
export function isInsufficientCreditsModelStreamFailure(details, message) {
|
|
110
|
+
return (details?.status === 402 ||
|
|
111
|
+
details?.code === INSUFFICIENT_CREDITS_ERROR_CODE ||
|
|
112
|
+
message === INSUFFICIENT_CREDITS_SAFE_MESSAGE);
|
|
113
|
+
}
|
|
114
|
+
export function isTerminalModelStreamFailure(details, message) {
|
|
115
|
+
return (isTerminalModelStreamErrorDetails(details) || message === INSUFFICIENT_CREDITS_SAFE_MESSAGE);
|
|
116
|
+
}
|
|
89
117
|
function isRecord(value) {
|
|
90
118
|
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
91
119
|
}
|
|
@@ -189,8 +217,8 @@ export function networkErrorDetails(transportFailureStage) {
|
|
|
189
217
|
};
|
|
190
218
|
}
|
|
191
219
|
export function safeModelStreamErrorMessage(details) {
|
|
192
|
-
if (details
|
|
193
|
-
return
|
|
220
|
+
if (isInsufficientCreditsModelStreamFailure(details, undefined)) {
|
|
221
|
+
return INSUFFICIENT_CREDITS_SAFE_MESSAGE;
|
|
194
222
|
}
|
|
195
223
|
if (details?.code === 'context_overflow') {
|
|
196
224
|
return 'The conversation exceeded the model context window.';
|
package/dist/core/run-failure.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { RunFailureCategory, RunFailureSource } from '../contracts/agent-failure.js';
|
|
2
|
-
import { ShortcutXLModelStreamFetchStage } from '../contracts/model-stream.js';
|
|
2
|
+
import { INSUFFICIENT_CREDITS_ERROR_CODE, INSUFFICIENT_CREDITS_SAFE_MESSAGE, isInsufficientCreditsModelStreamFailure, ShortcutXLModelStreamFetchStage } from '../contracts/model-stream.js';
|
|
3
3
|
import { classifyAgentError, getAgentErrorMessage } from './error-classification.js';
|
|
4
4
|
import { getErrorDetails } from './session/session-error-recovery.js';
|
|
5
5
|
const SAFE_MESSAGE_BY_CATEGORY = {
|
|
6
|
-
[RunFailureCategory.BillingInsufficientCredits]:
|
|
6
|
+
[RunFailureCategory.BillingInsufficientCredits]: INSUFFICIENT_CREDITS_SAFE_MESSAGE,
|
|
7
7
|
[RunFailureCategory.CompactionFailed]: 'Context compaction failed.',
|
|
8
8
|
[RunFailureCategory.CompactionTimeout]: 'Context compaction timed out.',
|
|
9
9
|
[RunFailureCategory.ContextTooLong]: 'The conversation exceeded the model context window.',
|
|
@@ -59,9 +59,6 @@ function operationKindToFailureCategory(kind, message) {
|
|
|
59
59
|
return RunFailureCategory.LlmStreamFailed;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
function isInsufficientCreditsFailure(details) {
|
|
63
|
-
return details?.status === 402 || details?.code === 'insufficient_credits';
|
|
64
|
-
}
|
|
65
62
|
export function createOperationFailureCause(input) {
|
|
66
63
|
const category = operationKindToFailureCategory(input.opKind, input.errorMessage);
|
|
67
64
|
return {
|
|
@@ -75,6 +72,17 @@ export function createOperationFailureCause(input) {
|
|
|
75
72
|
};
|
|
76
73
|
}
|
|
77
74
|
function classifyModelStreamFailure(details, message) {
|
|
75
|
+
if (isInsufficientCreditsModelStreamFailure(details, message)) {
|
|
76
|
+
return {
|
|
77
|
+
category: RunFailureCategory.BillingInsufficientCredits,
|
|
78
|
+
source: RunFailureSource.Billing,
|
|
79
|
+
httpStatus: details?.status,
|
|
80
|
+
errorCode: details?.code ?? INSUFFICIENT_CREDITS_ERROR_CODE,
|
|
81
|
+
retryable: details?.retryable,
|
|
82
|
+
retryAfterMs: details?.retryAfterMs,
|
|
83
|
+
transportFailureStage: details?.transportFailureStage
|
|
84
|
+
};
|
|
85
|
+
}
|
|
78
86
|
if (details) {
|
|
79
87
|
if (details.providerErrorType === 'model_access_denied' ||
|
|
80
88
|
details.code === 'model_access_denied') {
|
|
@@ -118,17 +126,6 @@ function classifyModelStreamFailure(details, message) {
|
|
|
118
126
|
transportFailureStage: details.transportFailureStage
|
|
119
127
|
};
|
|
120
128
|
}
|
|
121
|
-
if (isInsufficientCreditsFailure(details)) {
|
|
122
|
-
return {
|
|
123
|
-
category: RunFailureCategory.BillingInsufficientCredits,
|
|
124
|
-
source: RunFailureSource.Billing,
|
|
125
|
-
httpStatus: details.status,
|
|
126
|
-
errorCode: details.code ?? 'insufficient_credits',
|
|
127
|
-
retryable: details.retryable,
|
|
128
|
-
retryAfterMs: details.retryAfterMs,
|
|
129
|
-
transportFailureStage: details.transportFailureStage
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
129
|
if (details.providerErrorType === 'network' &&
|
|
133
130
|
details.transportFailureStage === ShortcutXLModelStreamFetchStage.BeforeResponse) {
|
|
134
131
|
return {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import type { AssistantMessage } from '../../ai/types.js';
|
|
21
21
|
import type { RuntimeErrorRecoverySettings } from '../../contracts/agent-settings.js';
|
|
22
|
-
import type
|
|
22
|
+
import { type ShortcutXLModelStreamErrorDetails } from '../../contracts/model-stream.js';
|
|
23
23
|
/**
|
|
24
24
|
* Error categories for multi-strategy recovery.
|
|
25
25
|
*
|
|
@@ -84,6 +84,22 @@ export type RuntimeAssistantMessage = AssistantMessage & {
|
|
|
84
84
|
};
|
|
85
85
|
/** Accessor for the runtime-only `errorDetails` field on a owned AI assistant message. */
|
|
86
86
|
export declare function getErrorDetails(message: AssistantMessage): ShortcutXLModelStreamErrorDetails | undefined;
|
|
87
|
+
export type ErrorClassificationDisposition = {
|
|
88
|
+
kind: 'none';
|
|
89
|
+
} | {
|
|
90
|
+
kind: 'terminal';
|
|
91
|
+
} | {
|
|
92
|
+
kind: 'categorized';
|
|
93
|
+
category: ErrorCategory;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Classify an assistant message into an explicit recovery disposition.
|
|
97
|
+
*
|
|
98
|
+
* MUST remain synchronous — called from the sync _handleAgentLoopEvent handler
|
|
99
|
+
* before async queue processing, to ensure the recovery promise exists
|
|
100
|
+
* before waitForRetry() is called.
|
|
101
|
+
*/
|
|
102
|
+
export declare function classifyErrorDisposition(message: AssistantMessage, isContextOverflow: ContextOverflowChecker): ErrorClassificationDisposition;
|
|
87
103
|
/**
|
|
88
104
|
* Classify an error into a recovery category.
|
|
89
105
|
*
|
|
@@ -141,6 +157,10 @@ export declare class SessionErrorRecovery {
|
|
|
141
157
|
private _continueRecovery;
|
|
142
158
|
/** Reset all state to idle. */
|
|
143
159
|
private _reset;
|
|
160
|
+
/** Emit a cancellation event once for an active, visible recovery. */
|
|
161
|
+
private _emitCancelledRecovery;
|
|
162
|
+
/** Cancel recovery state and any in-progress backoff sleep. */
|
|
163
|
+
private _cancelRecovery;
|
|
144
164
|
/**
|
|
145
165
|
* Create the recovery promise synchronously when an agent_end event arrives.
|
|
146
166
|
*
|
|
@@ -178,6 +198,14 @@ export declare class SessionErrorRecovery {
|
|
|
178
198
|
* Resets state and resolves the promise.
|
|
179
199
|
*/
|
|
180
200
|
onSuccess(): void;
|
|
201
|
+
/**
|
|
202
|
+
* Called when a continuation turn reaches an unrecoverable assistant error.
|
|
203
|
+
* This resolves the in-flight recovery as failed instead of treating the
|
|
204
|
+
* terminal, non-retryable failure as a successful retry.
|
|
205
|
+
*/
|
|
206
|
+
onTerminalFailure(finalError: string | undefined): void;
|
|
207
|
+
/** Settle recovery when a continuation agent loop reaches agent_end. */
|
|
208
|
+
settleAgentEnd(lastAssistant: AssistantMessage | undefined, isContextOverflow: ContextOverflowChecker): void;
|
|
181
209
|
/** Cancel in-progress recovery. */
|
|
182
210
|
abort(): void;
|
|
183
211
|
/** Wait for any in-progress recovery to complete. */
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* classifyError + promise creation happens synchronously in _handleAgentLoopEvent,
|
|
18
18
|
* strategy dispatch happens asynchronously in the queued _processAgentLoopEvent.
|
|
19
19
|
*/
|
|
20
|
+
import { isTerminalModelStreamErrorDetails, isTerminalModelStreamFailure } from '../../contracts/model-stream.js';
|
|
20
21
|
import { sleep } from '../../utils/sleep.js';
|
|
21
22
|
/** Accessor for the runtime-only `errorDetails` field on a owned AI assistant message. */
|
|
22
23
|
export function getErrorDetails(message) {
|
|
@@ -43,23 +44,10 @@ const SERVER_ERROR_PATTERN = /\b500\b|\b502\b|\b503\b|\b504\b|\b524\b|service.?u
|
|
|
43
44
|
// - Chrome/Node: "Failed to fetch", "network error", and the
|
|
44
45
|
// already-listed connection/reset strings
|
|
45
46
|
const CONNECTION_ERROR_PATTERN = /connection.?error|connection.?refused|other side closed|fetch failed|failed to fetch|upstream.?connect|reset before headers|terminated|retry delay|peer closed|complete message body|load failed|network.?error|internet connection appears to be offline/i;
|
|
46
|
-
const TERMINAL_GATEWAY_ERROR_CODES = new Set([
|
|
47
|
-
'insufficient_credits',
|
|
48
|
-
'customer_credential_scope_required',
|
|
49
|
-
'customer_credential_resolution_failed',
|
|
50
|
-
'customer_credential_resolution_timeout',
|
|
51
|
-
'customer_credential_resolution_cancelled',
|
|
52
|
-
'customer_credential_unconfigured',
|
|
53
|
-
'unsupported_credential_mode',
|
|
54
|
-
'model_policy_rejected'
|
|
55
|
-
]);
|
|
56
47
|
function classifyStructuredError(details) {
|
|
57
48
|
if (!details)
|
|
58
49
|
return null;
|
|
59
|
-
if (
|
|
60
|
-
return 'terminal';
|
|
61
|
-
}
|
|
62
|
-
if (details.status === 402)
|
|
50
|
+
if (isTerminalModelStreamErrorDetails(details))
|
|
63
51
|
return 'terminal';
|
|
64
52
|
if (details.providerErrorType === 'unauthorized' || details.status === 401)
|
|
65
53
|
return 'auth_error';
|
|
@@ -78,45 +66,59 @@ function classifyStructuredError(details) {
|
|
|
78
66
|
return null;
|
|
79
67
|
}
|
|
80
68
|
/**
|
|
81
|
-
* Classify an
|
|
69
|
+
* Classify an assistant message into an explicit recovery disposition.
|
|
82
70
|
*
|
|
83
71
|
* MUST remain synchronous — called from the sync _handleAgentLoopEvent handler
|
|
84
72
|
* before async queue processing, to ensure the recovery promise exists
|
|
85
73
|
* before waitForRetry() is called.
|
|
86
|
-
*
|
|
87
|
-
* @returns ErrorCategory if error is classifiable, null if not an error
|
|
88
74
|
*/
|
|
89
|
-
export function
|
|
75
|
+
export function classifyErrorDisposition(message, isContextOverflow) {
|
|
90
76
|
if (message.stopReason !== 'error')
|
|
91
|
-
return
|
|
77
|
+
return { kind: 'none' };
|
|
92
78
|
if (!message.errorMessage)
|
|
93
|
-
return
|
|
79
|
+
return { kind: 'none' };
|
|
94
80
|
// Context overflow has highest priority — exclusive path
|
|
95
81
|
if (isContextOverflow(message))
|
|
96
|
-
return 'context_overflow';
|
|
97
|
-
const
|
|
82
|
+
return { kind: 'categorized', category: 'context_overflow' };
|
|
83
|
+
const details = getErrorDetails(message);
|
|
84
|
+
const structuredCategory = classifyStructuredError(details);
|
|
98
85
|
if (structuredCategory === 'terminal') {
|
|
99
|
-
return
|
|
86
|
+
return { kind: 'terminal' };
|
|
100
87
|
}
|
|
101
88
|
if (structuredCategory)
|
|
102
|
-
return structuredCategory;
|
|
89
|
+
return { kind: 'categorized', category: structuredCategory };
|
|
103
90
|
const err = message.errorMessage;
|
|
91
|
+
if (isTerminalModelStreamFailure(details, err))
|
|
92
|
+
return { kind: 'terminal' };
|
|
104
93
|
// Auth errors — must check before server errors (401 overlaps)
|
|
105
94
|
if (AUTH_ERROR_PATTERN.test(err))
|
|
106
|
-
return 'auth_error';
|
|
95
|
+
return { kind: 'categorized', category: 'auth_error' };
|
|
107
96
|
// Rate limits
|
|
108
97
|
if (RATE_LIMIT_PATTERN.test(err))
|
|
109
|
-
return 'rate_limit';
|
|
98
|
+
return { kind: 'categorized', category: 'rate_limit' };
|
|
110
99
|
// Overloaded
|
|
111
100
|
if (OVERLOADED_PATTERN.test(err))
|
|
112
|
-
return 'overloaded';
|
|
101
|
+
return { kind: 'categorized', category: 'overloaded' };
|
|
113
102
|
// Server errors
|
|
114
103
|
if (SERVER_ERROR_PATTERN.test(err))
|
|
115
|
-
return 'server_error';
|
|
104
|
+
return { kind: 'categorized', category: 'server_error' };
|
|
116
105
|
// Connection errors → server_error (transient infrastructure)
|
|
117
106
|
if (CONNECTION_ERROR_PATTERN.test(err))
|
|
118
|
-
return 'server_error';
|
|
119
|
-
return 'unknown';
|
|
107
|
+
return { kind: 'categorized', category: 'server_error' };
|
|
108
|
+
return { kind: 'categorized', category: 'unknown' };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Classify an error into a recovery category.
|
|
112
|
+
*
|
|
113
|
+
* MUST remain synchronous — called from the sync _handleAgentLoopEvent handler
|
|
114
|
+
* before async queue processing, to ensure the recovery promise exists
|
|
115
|
+
* before waitForRetry() is called.
|
|
116
|
+
*
|
|
117
|
+
* @returns ErrorCategory if error is classifiable, null if not an error
|
|
118
|
+
*/
|
|
119
|
+
export function classifyError(message, isContextOverflow) {
|
|
120
|
+
const disposition = classifyErrorDisposition(message, isContextOverflow);
|
|
121
|
+
return disposition.kind === 'categorized' ? disposition.category : null;
|
|
120
122
|
}
|
|
121
123
|
/**
|
|
122
124
|
* Server-error retry budget. 5 attempts × exponential backoff (1s, 2s, 4s,
|
|
@@ -238,20 +240,26 @@ export class SessionErrorRecovery {
|
|
|
238
240
|
_emit(event) {
|
|
239
241
|
this._listener?.(event);
|
|
240
242
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
243
|
+
_continueRecovery(category, attempt, actions) {
|
|
244
|
+
// Launch the continuation without awaiting it here. CLI AgentSession calls
|
|
245
|
+
// handleError() from its serialized event queue; awaiting agent.continue()
|
|
246
|
+
// would hold that queue until the entire retry finishes, starving the
|
|
247
|
+
// retry's own agent_start/message/tool events and leaving the UI frozen on
|
|
248
|
+
// the recovery loader. The recovery promise remains armed until the
|
|
249
|
+
// continuation's agent_end is processed by settleAgentEnd().
|
|
250
|
+
const activeRecovery = this._promise;
|
|
251
|
+
void Promise.resolve()
|
|
252
|
+
.then(() => {
|
|
253
|
+
// Cancellation can race the end of the backoff delay. Re-check before
|
|
254
|
+
// starting so an already-cancelled retry cannot launch invisibly.
|
|
255
|
+
if (!activeRecovery || this._promise !== activeRecovery)
|
|
256
|
+
return;
|
|
257
|
+
return actions.continue();
|
|
258
|
+
})
|
|
259
|
+
.catch((error) => {
|
|
260
|
+
// Ignore completion from a continuation that was cancelled or replaced.
|
|
261
|
+
if (!activeRecovery || this._promise !== activeRecovery)
|
|
262
|
+
return;
|
|
255
263
|
this._emit({
|
|
256
264
|
type: 'recovery_end',
|
|
257
265
|
category,
|
|
@@ -260,17 +268,38 @@ export class SessionErrorRecovery {
|
|
|
260
268
|
finalError: error instanceof Error ? error.message : String(error)
|
|
261
269
|
});
|
|
262
270
|
this._reset();
|
|
263
|
-
|
|
264
|
-
|
|
271
|
+
});
|
|
272
|
+
return true;
|
|
265
273
|
}
|
|
266
274
|
/** Reset all state to idle. */
|
|
267
275
|
_reset() {
|
|
276
|
+
this._abortController = undefined;
|
|
268
277
|
this._attempt = 0;
|
|
269
278
|
this._category = null;
|
|
270
279
|
this._resolve?.();
|
|
271
280
|
this._resolve = undefined;
|
|
272
281
|
this._promise = undefined;
|
|
273
282
|
}
|
|
283
|
+
/** Emit a cancellation event once for an active, visible recovery. */
|
|
284
|
+
_emitCancelledRecovery() {
|
|
285
|
+
if (!this._promise || this._attempt <= 0 || !this._category)
|
|
286
|
+
return;
|
|
287
|
+
this._emit({
|
|
288
|
+
type: 'recovery_end',
|
|
289
|
+
category: this._category,
|
|
290
|
+
success: false,
|
|
291
|
+
attempt: this._attempt,
|
|
292
|
+
finalError: 'Recovery cancelled'
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/** Cancel recovery state and any in-progress backoff sleep. */
|
|
296
|
+
_cancelRecovery() {
|
|
297
|
+
const abortController = this._abortController;
|
|
298
|
+
this._abortController = undefined;
|
|
299
|
+
this._emitCancelledRecovery();
|
|
300
|
+
this._reset();
|
|
301
|
+
abortController?.abort();
|
|
302
|
+
}
|
|
274
303
|
/**
|
|
275
304
|
* Create the recovery promise synchronously when an agent_end event arrives.
|
|
276
305
|
*
|
|
@@ -314,6 +343,15 @@ export class SessionErrorRecovery {
|
|
|
314
343
|
const config = STRATEGY_CONFIGS[category];
|
|
315
344
|
// No retry for non-retryable categories
|
|
316
345
|
if (config.maxAttempts === 0) {
|
|
346
|
+
if (this._attempt > 0 || this._promise) {
|
|
347
|
+
this._emit({
|
|
348
|
+
type: 'recovery_end',
|
|
349
|
+
category: this._category ?? category,
|
|
350
|
+
success: false,
|
|
351
|
+
attempt: this._attempt,
|
|
352
|
+
finalError: message.errorMessage
|
|
353
|
+
});
|
|
354
|
+
}
|
|
317
355
|
this._reset();
|
|
318
356
|
return false;
|
|
319
357
|
}
|
|
@@ -448,6 +486,10 @@ export class SessionErrorRecovery {
|
|
|
448
486
|
}
|
|
449
487
|
/** Handle abort during sleep. */
|
|
450
488
|
_handleAbort(category) {
|
|
489
|
+
// abort() emits and resets synchronously. If the aborted sleep rejects
|
|
490
|
+
// afterward, do not emit a second recovery_end event.
|
|
491
|
+
if (!this._promise)
|
|
492
|
+
return false;
|
|
451
493
|
const attempt = this._attempt;
|
|
452
494
|
this._abortController = undefined;
|
|
453
495
|
this._emit({
|
|
@@ -476,10 +518,39 @@ export class SessionErrorRecovery {
|
|
|
476
518
|
});
|
|
477
519
|
this._reset();
|
|
478
520
|
}
|
|
521
|
+
/**
|
|
522
|
+
* Called when a continuation turn reaches an unrecoverable assistant error.
|
|
523
|
+
* This resolves the in-flight recovery as failed instead of treating the
|
|
524
|
+
* terminal, non-retryable failure as a successful retry.
|
|
525
|
+
*/
|
|
526
|
+
onTerminalFailure(finalError) {
|
|
527
|
+
if (this._attempt <= 0)
|
|
528
|
+
return;
|
|
529
|
+
const category = this._category ?? 'unknown';
|
|
530
|
+
this._emit({
|
|
531
|
+
type: 'recovery_end',
|
|
532
|
+
category,
|
|
533
|
+
success: false,
|
|
534
|
+
attempt: this._attempt,
|
|
535
|
+
finalError
|
|
536
|
+
});
|
|
537
|
+
this._reset();
|
|
538
|
+
}
|
|
539
|
+
/** Settle recovery when a continuation agent loop reaches agent_end. */
|
|
540
|
+
settleAgentEnd(lastAssistant, isContextOverflow) {
|
|
541
|
+
if (this._attempt <= 0)
|
|
542
|
+
return;
|
|
543
|
+
if (!lastAssistant || lastAssistant.stopReason !== 'error') {
|
|
544
|
+
this.onSuccess();
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (classifyErrorDisposition(lastAssistant, isContextOverflow).kind === 'terminal') {
|
|
548
|
+
this.onTerminalFailure(lastAssistant.errorMessage);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
479
551
|
/** Cancel in-progress recovery. */
|
|
480
552
|
abort() {
|
|
481
|
-
this.
|
|
482
|
-
this._reset();
|
|
553
|
+
this._cancelRecovery();
|
|
483
554
|
}
|
|
484
555
|
/** Wait for any in-progress recovery to complete. */
|
|
485
556
|
async wait() {
|
|
@@ -362,9 +362,11 @@ function toShortcutXLErrorReason(stopReason) {
|
|
|
362
362
|
: ShortcutXLModelStreamStopReason.Error;
|
|
363
363
|
}
|
|
364
364
|
function toShortcutXLAssistantMessage(message) {
|
|
365
|
+
const errorDetails = message.errorDetails;
|
|
365
366
|
return {
|
|
366
367
|
api: message.api,
|
|
367
368
|
content: message.content.map((content) => toShortcutXLAssistantContent(content)),
|
|
369
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
368
370
|
errorMessage: message.errorMessage,
|
|
369
371
|
model: message.model,
|
|
370
372
|
provider: message.provider,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { ACTION_TOOL_NAMES } from './action-tool-names.js';
|
|
7
7
|
import { buildHostToolList } from './build-tool-list.js';
|
|
8
|
-
import { BASH_COMMAND, EXECUTE_CODE, EXECUTE_TOOL, GET_TOOL_INFO, MCP, READ_SKILL, SEND_MESSAGE, SWITCH_MODE, TAKE_SCREENSHOT, TASK } from './tool-names.js';
|
|
8
|
+
import { BASH_COMMAND, EXECUTE_CODE, EXECUTE_TOOL, GET_TOOL_INFO, MCP, MODIFY_SKILL, READ_SKILL, SEND_MESSAGE, SWITCH_MODE, TAKE_SCREENSHOT, TASK } from './tool-names.js';
|
|
9
9
|
/** First-class tool names exposed to the LLM per agent mode. */
|
|
10
10
|
export const TOOLS_BY_MODE_NAMES = {
|
|
11
11
|
action: ACTION_TOOL_NAMES,
|
|
@@ -17,6 +17,7 @@ export const TOOLS_BY_MODE_NAMES = {
|
|
|
17
17
|
TAKE_SCREENSHOT,
|
|
18
18
|
MCP,
|
|
19
19
|
READ_SKILL,
|
|
20
|
+
MODIFY_SKILL,
|
|
20
21
|
TASK,
|
|
21
22
|
SEND_MESSAGE
|
|
22
23
|
],
|
|
@@ -14,6 +14,7 @@ export declare const ShortcutXLAgentModelId: {
|
|
|
14
14
|
readonly ClaudeFable5: "claude-fable-5";
|
|
15
15
|
readonly ClaudeOpus48: "claude-opus-4-8";
|
|
16
16
|
readonly Glm52Fireworks: "accounts/fireworks/models/glm-5p2";
|
|
17
|
+
readonly Grok45: "grok-4.5";
|
|
17
18
|
readonly OpenAIReasoning: "gpt-5.6-sol";
|
|
18
19
|
/** Low-cost Shortcut-hosted model. */
|
|
19
20
|
readonly Hosted: "pivot";
|
|
@@ -9,6 +9,7 @@ export const ShortcutXLAgentModelId = {
|
|
|
9
9
|
ClaudeFable5: SHORTCUT_MODEL_ID.ClaudeFable5,
|
|
10
10
|
ClaudeOpus48: SHORTCUT_MODEL_ID.ClaudeOpus48,
|
|
11
11
|
Glm52Fireworks: SHORTCUT_MODEL_ID.Glm52Fireworks,
|
|
12
|
+
Grok45: SHORTCUT_MODEL_ID.Grok45,
|
|
12
13
|
OpenAIReasoning: SHORTCUT_OPENAI_MODEL_ID,
|
|
13
14
|
/** Low-cost Shortcut-hosted model. */
|
|
14
15
|
Hosted: SHORTCUT_MODEL_ID.Hosted
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isContextOverflow } from '../core/session/context-overflow.js';
|
|
2
|
-
import {
|
|
2
|
+
import { classifyErrorDisposition, SessionErrorRecovery } from '../core/session/session-error-recovery.js';
|
|
3
3
|
function findLastAssistant(messages) {
|
|
4
4
|
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
5
5
|
const message = messages[i];
|
|
@@ -31,9 +31,14 @@ function removeLastErrorGroup(agent, sessionStore, funnel) {
|
|
|
31
31
|
/** Build the embedded equivalent of CLI `SessionErrorRecovery`. */
|
|
32
32
|
export function createEmbeddedRecoveryWiring(options) {
|
|
33
33
|
const recovery = new SessionErrorRecovery({ sleep: options.recoverySleep });
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
let recoverySucceeded;
|
|
35
|
+
recovery.onEvent((event) => {
|
|
36
|
+
if (event.type === 'recovery_start')
|
|
37
|
+
recoverySucceeded = undefined;
|
|
38
|
+
if (event.type === 'recovery_end')
|
|
39
|
+
recoverySucceeded = event.success;
|
|
40
|
+
options.onRecoveryEvent?.(event);
|
|
41
|
+
});
|
|
37
42
|
const isOverflow = (message) => isContextOverflow(message, options.agent.state.model?.contextWindow ?? 0);
|
|
38
43
|
return {
|
|
39
44
|
abort() {
|
|
@@ -46,8 +51,12 @@ export function createEmbeddedRecoveryWiring(options) {
|
|
|
46
51
|
// Embedded context-overflow recovery is already owned by compactionWiring,
|
|
47
52
|
// which can compact persisted entries and continue the agent. Do not arm
|
|
48
53
|
// SessionErrorRecovery for the same event or the two systems race.
|
|
49
|
-
if (lastAssistant
|
|
50
|
-
|
|
54
|
+
if (lastAssistant) {
|
|
55
|
+
const disposition = classifyErrorDisposition(lastAssistant, isOverflow);
|
|
56
|
+
if (disposition.kind === 'categorized' && disposition.category === 'context_overflow') {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
51
60
|
recovery.prepareForAgentEnd(lastAssistant, isOverflow, options.getSettings());
|
|
52
61
|
},
|
|
53
62
|
async handleAgentEnd(event) {
|
|
@@ -59,20 +68,33 @@ export function createEmbeddedRecoveryWiring(options) {
|
|
|
59
68
|
recovery.onSuccess();
|
|
60
69
|
return false;
|
|
61
70
|
}
|
|
62
|
-
const
|
|
63
|
-
if (category === 'context_overflow')
|
|
71
|
+
const disposition = classifyErrorDisposition(lastAssistant, isOverflow);
|
|
72
|
+
if (disposition.kind === 'categorized' && disposition.category === 'context_overflow') {
|
|
64
73
|
return false;
|
|
65
|
-
|
|
74
|
+
}
|
|
75
|
+
if (disposition.kind === 'none') {
|
|
66
76
|
if (recovery.attempt > 0)
|
|
67
77
|
recovery.onSuccess();
|
|
68
78
|
return false;
|
|
69
79
|
}
|
|
70
|
-
|
|
80
|
+
if (disposition.kind === 'terminal') {
|
|
81
|
+
if (recovery.attempt > 0)
|
|
82
|
+
recovery.onTerminalFailure(lastAssistant.errorMessage);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
const initiated = await recovery.handleError(lastAssistant, disposition.category, options.getSettings(), {
|
|
71
86
|
removeLastAssistantMessage: () => removeLastErrorGroup(options.agent, options.sessionStore, options.funnel),
|
|
72
87
|
continue: options.continueAgent,
|
|
73
88
|
runCompaction: async () => false,
|
|
74
89
|
switchToFallbackModel: async () => false
|
|
75
90
|
});
|
|
91
|
+
// Embedded recovery runs outside a serialized agent-event queue, so it
|
|
92
|
+
// can retain its completion-ordering contract without starving events.
|
|
93
|
+
// CLI AgentSession instead relies on recovery.wait() from prompt().
|
|
94
|
+
if (!initiated)
|
|
95
|
+
return false;
|
|
96
|
+
await recovery.wait();
|
|
97
|
+
return recoverySucceeded === true;
|
|
76
98
|
},
|
|
77
99
|
wait() {
|
|
78
100
|
return recovery.wait();
|
package/dist/model-ids.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare const SHORTCUT_MODEL_ID: {
|
|
|
4
4
|
readonly ClaudeOpus48: "claude-opus-4-8";
|
|
5
5
|
readonly Glm52Fireworks: "accounts/fireworks/models/glm-5p2";
|
|
6
6
|
readonly Gpt56Sol: "gpt-5.6-sol";
|
|
7
|
+
readonly Grok45: "grok-4.5";
|
|
7
8
|
readonly Hosted: "pivot";
|
|
8
9
|
readonly Gemini3Flash: "gemini-3-flash-preview";
|
|
9
10
|
readonly ClaudeHaiku45: "claude-haiku-4-5-20251001";
|
|
@@ -13,6 +14,7 @@ export declare const SHORTCUT_MODEL_REF: {
|
|
|
13
14
|
readonly ClaudeOpus48: "shortcut/claude-opus-4-8";
|
|
14
15
|
readonly Glm52Fireworks: "shortcut/accounts/fireworks/models/glm-5p2";
|
|
15
16
|
readonly Gpt56Sol: "shortcut/gpt-5.6-sol";
|
|
17
|
+
readonly Grok45: "shortcut/grok-4.5";
|
|
16
18
|
readonly Hosted: "shortcut/pivot";
|
|
17
19
|
};
|
|
18
20
|
export declare const SHORTCUT_OPENAI_MODEL_ID: "gpt-5.6-sol";
|
package/dist/model-ids.js
CHANGED
|
@@ -4,6 +4,7 @@ export const SHORTCUT_MODEL_ID = {
|
|
|
4
4
|
ClaudeOpus48: 'claude-opus-4-8',
|
|
5
5
|
Glm52Fireworks: 'accounts/fireworks/models/glm-5p2',
|
|
6
6
|
Gpt56Sol: 'gpt-5.6-sol',
|
|
7
|
+
Grok45: 'grok-4.5',
|
|
7
8
|
Hosted: 'pivot',
|
|
8
9
|
Gemini3Flash: 'gemini-3-flash-preview',
|
|
9
10
|
ClaudeHaiku45: 'claude-haiku-4-5-20251001'
|
|
@@ -13,6 +14,7 @@ export const SHORTCUT_MODEL_REF = {
|
|
|
13
14
|
ClaudeOpus48: `${SHORTCUT_PROVIDER_ID}/${SHORTCUT_MODEL_ID.ClaudeOpus48}`,
|
|
14
15
|
Glm52Fireworks: `${SHORTCUT_PROVIDER_ID}/${SHORTCUT_MODEL_ID.Glm52Fireworks}`,
|
|
15
16
|
Gpt56Sol: `${SHORTCUT_PROVIDER_ID}/${SHORTCUT_MODEL_ID.Gpt56Sol}`,
|
|
17
|
+
Grok45: `${SHORTCUT_PROVIDER_ID}/${SHORTCUT_MODEL_ID.Grok45}`,
|
|
16
18
|
Hosted: `${SHORTCUT_PROVIDER_ID}/${SHORTCUT_MODEL_ID.Hosted}`
|
|
17
19
|
};
|
|
18
20
|
export const SHORTCUT_OPENAI_MODEL_ID = SHORTCUT_MODEL_ID.Gpt56Sol;
|
package/dist/rpc/rpc-mode.js
CHANGED
|
@@ -246,7 +246,7 @@ export async function runRpcMode(session) {
|
|
|
246
246
|
return success(id, 'set_auto_retry');
|
|
247
247
|
}
|
|
248
248
|
case 'abort_retry': {
|
|
249
|
-
session.abortRetry();
|
|
249
|
+
await session.abortRetry();
|
|
250
250
|
return success(id, 'abort_retry');
|
|
251
251
|
}
|
|
252
252
|
// =================================================================
|
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
import { fileTypeFromBuffer } from 'file-type';
|
|
2
|
-
import { open } from 'node:fs/promises';
|
|
3
2
|
import { TextDecoder } from 'node:util';
|
|
4
|
-
const FILE_TYPE_SNIFF_BYTES = 8192;
|
|
5
3
|
const utf8Decoder = new TextDecoder('utf-8', { fatal: true });
|
|
6
|
-
export async function readFilePrefix(filePath, maxBytes = FILE_TYPE_SNIFF_BYTES) {
|
|
7
|
-
const fileHandle = await open(filePath, 'r');
|
|
8
|
-
try {
|
|
9
|
-
const buffer = Buffer.alloc(maxBytes);
|
|
10
|
-
const { bytesRead } = await fileHandle.read(buffer, 0, maxBytes, 0);
|
|
11
|
-
return buffer.subarray(0, bytesRead);
|
|
12
|
-
}
|
|
13
|
-
finally {
|
|
14
|
-
await fileHandle.close();
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
4
|
export async function detectFileMimeTypeFromBuffer(sample) {
|
|
18
5
|
const detected = sample.length > 0 ? await fileTypeFromBuffer(sample) : undefined;
|
|
19
6
|
return detected?.mime ?? null;
|
|
@@ -2529,7 +2529,7 @@ export class InteractiveMode {
|
|
|
2529
2529
|
handleRecoveryStartEvent(event) {
|
|
2530
2530
|
this.retryEscapeHandler = this.defaultEditor.onEscape;
|
|
2531
2531
|
this.defaultEditor.onEscape = () => {
|
|
2532
|
-
this.session.abortRetry();
|
|
2532
|
+
void this.session.abortRetry();
|
|
2533
2533
|
};
|
|
2534
2534
|
const retryChildren = this.chatContainer.children;
|
|
2535
2535
|
for (let i = retryChildren.length - 1; i >= 0; i--) {
|
|
@@ -2556,7 +2556,10 @@ export class InteractiveMode {
|
|
|
2556
2556
|
this.retryLoader = undefined;
|
|
2557
2557
|
this.statusContainer.clear();
|
|
2558
2558
|
}
|
|
2559
|
-
if (!event.success) {
|
|
2559
|
+
if (!event.success && event.finalError === 'Recovery cancelled') {
|
|
2560
|
+
this.showStatus('Recovery cancelled');
|
|
2561
|
+
}
|
|
2562
|
+
else if (!event.success) {
|
|
2560
2563
|
this.showError(`Recovery failed after ${event.attempt} attempts: ${event.finalError || 'Unknown error'}`);
|
|
2561
2564
|
}
|
|
2562
2565
|
this.ui.requestRender();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Api, Model } from './ai/types.js';
|
|
2
|
-
export type ShortcutModelDefinition = Pick<Model<Api>, 'id' | 'name' | 'reasoning' | 'input' | 'cost' | 'contextWindow' | 'maxTokens'>;
|
|
2
|
+
export type ShortcutModelDefinition = Pick<Model<Api>, 'id' | 'name' | 'reasoning' | 'input' | 'cost' | 'contextWindow' | 'maxTokens' | 'compat'>;
|
|
3
3
|
export declare const DEFAULT_SHORTCUT_MODEL_ID: "gpt-5.6-sol";
|
|
4
4
|
export declare function createShortcutPublicModelDefinitions(): ShortcutModelDefinition[];
|
|
5
5
|
export declare function createShortcutHostedModelDefinitions(): ShortcutModelDefinition[];
|
|
@@ -28,6 +28,21 @@ const SHORTCUT_PUBLIC_MODEL_DEFINITIONS = [
|
|
|
28
28
|
contextWindow: 1_000_000,
|
|
29
29
|
maxTokens: 128000
|
|
30
30
|
},
|
|
31
|
+
{
|
|
32
|
+
id: SHORTCUT_MODEL_ID.Grok45,
|
|
33
|
+
name: 'Grok 4.5',
|
|
34
|
+
reasoning: true,
|
|
35
|
+
input: ['text', 'image'],
|
|
36
|
+
cost: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
|
|
37
|
+
contextWindow: 500_000,
|
|
38
|
+
maxTokens: 128000,
|
|
39
|
+
compat: {
|
|
40
|
+
supportsStore: false,
|
|
41
|
+
supportsDeveloperRole: false,
|
|
42
|
+
supportsReasoningEffort: true,
|
|
43
|
+
maxTokensField: 'max_completion_tokens'
|
|
44
|
+
}
|
|
45
|
+
},
|
|
31
46
|
{
|
|
32
47
|
id: SHORTCUT_MODEL_ID.Glm52Fireworks,
|
|
33
48
|
name: 'GLM 5.2',
|
|
@@ -75,7 +90,8 @@ function cloneShortcutModelDefinition(model) {
|
|
|
75
90
|
return {
|
|
76
91
|
...model,
|
|
77
92
|
input: [...model.input],
|
|
78
|
-
cost: { ...model.cost }
|
|
93
|
+
cost: { ...model.cost },
|
|
94
|
+
...(model.compat ? { compat: { ...model.compat } } : {})
|
|
79
95
|
};
|
|
80
96
|
}
|
|
81
97
|
export function createShortcutPublicModelDefinitions() {
|
package/package.json
CHANGED
|
Binary file
|