shortcutxl 0.4.1 → 0.4.2
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/BINARY-INVENTORY.json +10 -10
- package/dist/app/agent-session-runtime-adapter.d.ts +5 -0
- package/dist/app/agent-session-runtime-adapter.js +7 -4
- package/dist/app/agent-session.d.ts +7 -2
- package/dist/app/agent-session.js +9 -5
- package/dist/app/observability/client-logging/client-logger.d.ts +2 -1
- package/dist/app/observability/client-logging/client-logger.js +11 -5
- package/dist/app/providers/model-resolver.d.ts +6 -0
- package/dist/app/providers/model-resolver.js +23 -20
- package/dist/app/services/user-context.d.ts +2 -0
- package/dist/app/services/user-context.js +4 -1
- package/dist/app/session/account-default-model.d.ts +33 -0
- package/dist/app/session/account-default-model.js +53 -0
- package/dist/app/session/model-manager.d.ts +14 -3
- package/dist/app/session/model-manager.js +19 -7
- package/dist/cli.js +1880 -948
- package/dist/config.d.ts +13 -5
- package/dist/config.js +37 -23
- package/dist/contracts/model-stream.d.ts +5 -0
- package/dist/contracts/wire.d.ts +3 -1
- package/dist/core/agent-engine.d.ts +2 -1
- package/dist/core/agent-engine.js +3 -0
- package/dist/core/agent-loop.js +8 -2
- package/dist/core/core-types.d.ts +3 -1
- package/dist/core/session/compaction/compaction.d.ts +2 -0
- package/dist/core/session/compaction/compaction.js +3 -0
- package/dist/core/session/compaction-bridge.d.ts +2 -1
- package/dist/core/session/compaction-bridge.js +4 -1
- package/dist/embedded-agent/compaction-wiring.js +4 -1
- package/dist/embedded-agent/compose.js +12 -4
- package/dist/main.d.ts +7 -0
- package/dist/main.js +21 -17
- package/dist/rpc/rpc-host.js +7 -1
- package/dist/shell/interactive/auth-workflow.js +7 -1
- package/dist/shortcut-default-model.d.ts +30 -0
- package/dist/shortcut-default-model.js +86 -0
- package/dist/startup/terminal-update-flow.js +4 -1
- package/dist/startup/update-action.d.ts +17 -2
- package/dist/startup/update-action.js +114 -40
- package/dist/startup/update-diagnostics.d.ts +85 -0
- package/dist/startup/update-diagnostics.js +188 -0
- package/dist/startup/update-execution.d.ts +13 -1
- package/dist/startup/update-execution.js +205 -6
- package/dist/startup/update-installation.d.ts +55 -0
- package/dist/startup/update-installation.js +242 -0
- package/dist/startup/update-manager.d.ts +5 -0
- package/dist/startup/update-manager.js +23 -5
- package/dist/startup/update-verification.d.ts +87 -0
- package/dist/startup/update-verification.js +204 -0
- package/native-app/main/chunks/{config-CwtqLhpn.js → config-DSoKsgPT.js} +1 -1
- package/native-app/main/chunks/{linux-C5J5oKJy.js → linux-TBGGGecs.js} +3 -3
- package/native-app/main/chunks/{mac-Bt_bFwUg.js → mac-Dg93p_2s.js} +3 -3
- package/native-app/main/chunks/{setup-worker-protocol-QUVpcuEb.js → setup-worker-protocol-CHVmPxBP.js} +54 -35
- package/native-app/main/chunks/{shared-Q59yhreZ.js → shared-BbAcLTbB.js} +1 -1
- package/native-app/main/index.js +189 -15
- package/native-app/main/setup-worker.js +3 -3
- package/native-app/package.json +1 -1
- package/native-app/renderer/assets/{index-De3NGXD5.js → index-BJUrz0xE.js} +2204 -163
- package/native-app/renderer/assets/server-PLrRTws-.js +2689 -0
- package/native-app/renderer/index.html +1 -1
- package/package.json +1 -1
- package/xll/python/Lib/site-packages/httpx-0.28.1.dist-info/RECORD +1 -1
- package/xll/python/Lib/site-packages/idna-3.18.dist-info/RECORD +1 -1
- package/xll/python/Lib/site-packages/pip-26.2.dist-info/RECORD +3 -3
- package/xll/python/Lib/site-packages/pywin32-311.dist-info/RECORD +2 -2
- package/xll/python/Scripts/httpx.exe +0 -0
- package/xll/python/Scripts/idna.exe +0 -0
- package/xll/python/Scripts/pip.exe +0 -0
- package/xll/python/Scripts/pip3.13.exe +0 -0
- package/xll/python/Scripts/pip3.exe +0 -0
- package/xll/python/Scripts/pywin32_postinstall.exe +0 -0
- package/xll/python/Scripts/pywin32_testall.exe +0 -0
package/BINARY-INVENTORY.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"generatedAt": "2026-10-20T23:01:29.571Z",
|
|
4
4
|
"package": "shortcutxl",
|
|
5
5
|
"build": {
|
|
6
6
|
"builder": "github-actions",
|
|
7
7
|
"repository": "lyfegame/shortcut",
|
|
8
8
|
"sourceRef": "refs/heads/main",
|
|
9
|
-
"sourceCommit": "
|
|
9
|
+
"sourceCommit": "29226447fc54eb8379f8b0b5823e158a24d562d3",
|
|
10
10
|
"workflowRef": "lyfegame/shortcut/.github/workflows/release-shortcut-cli-npm-package.yml@refs/heads/main",
|
|
11
|
-
"runId": "
|
|
11
|
+
"runId": "39512005187",
|
|
12
12
|
"runnerOs": "Windows",
|
|
13
13
|
"runnerImage": "win25-vs2026",
|
|
14
14
|
"runnerImageVersion": "20261007.264.1"
|
|
@@ -550,7 +550,7 @@
|
|
|
550
550
|
},
|
|
551
551
|
{
|
|
552
552
|
"path": "xll/python/Scripts/httpx.exe",
|
|
553
|
-
"sha256": "
|
|
553
|
+
"sha256": "1ef3333c745fc7dbaff8a2dcf6de6491e9e44e44580a1b3ca630019ec18aac8b",
|
|
554
554
|
"source": "httpx console launcher installed into embedded Python",
|
|
555
555
|
"version": "see packaged httpx distribution",
|
|
556
556
|
"builtBy": "third-party",
|
|
@@ -558,7 +558,7 @@
|
|
|
558
558
|
},
|
|
559
559
|
{
|
|
560
560
|
"path": "xll/python/Scripts/idna.exe",
|
|
561
|
-
"sha256": "
|
|
561
|
+
"sha256": "89c1645a20d2119a8b1271a572cbf398e7000b9dd57a04b56906c513024f4771",
|
|
562
562
|
"source": "Python package console launcher installed into embedded Python",
|
|
563
563
|
"version": "see owning Python package metadata in site-packages",
|
|
564
564
|
"builtBy": "third-party",
|
|
@@ -566,7 +566,7 @@
|
|
|
566
566
|
},
|
|
567
567
|
{
|
|
568
568
|
"path": "xll/python/Scripts/pip.exe",
|
|
569
|
-
"sha256": "
|
|
569
|
+
"sha256": "733632fc67f2f4f54f646f7a97c126ea76dc88a25761908d1c4411eed0b600ff",
|
|
570
570
|
"source": "pip console launcher installed into embedded Python",
|
|
571
571
|
"version": "see packaged pip distribution",
|
|
572
572
|
"builtBy": "third-party",
|
|
@@ -574,7 +574,7 @@
|
|
|
574
574
|
},
|
|
575
575
|
{
|
|
576
576
|
"path": "xll/python/Scripts/pip3.13.exe",
|
|
577
|
-
"sha256": "
|
|
577
|
+
"sha256": "733632fc67f2f4f54f646f7a97c126ea76dc88a25761908d1c4411eed0b600ff",
|
|
578
578
|
"source": "pip console launcher installed into embedded Python",
|
|
579
579
|
"version": "see packaged pip distribution",
|
|
580
580
|
"builtBy": "third-party",
|
|
@@ -582,7 +582,7 @@
|
|
|
582
582
|
},
|
|
583
583
|
{
|
|
584
584
|
"path": "xll/python/Scripts/pip3.exe",
|
|
585
|
-
"sha256": "
|
|
585
|
+
"sha256": "733632fc67f2f4f54f646f7a97c126ea76dc88a25761908d1c4411eed0b600ff",
|
|
586
586
|
"source": "pip console launcher installed into embedded Python",
|
|
587
587
|
"version": "see packaged pip distribution",
|
|
588
588
|
"builtBy": "third-party",
|
|
@@ -590,7 +590,7 @@
|
|
|
590
590
|
},
|
|
591
591
|
{
|
|
592
592
|
"path": "xll/python/Scripts/pywin32_postinstall.exe",
|
|
593
|
-
"sha256": "
|
|
593
|
+
"sha256": "36d372cde19e37f53f33cfe10de6ee4b4f26f6abeb98b2831bf3fe4c95655380",
|
|
594
594
|
"source": "Python package console launcher installed into embedded Python",
|
|
595
595
|
"version": "see owning Python package metadata in site-packages",
|
|
596
596
|
"builtBy": "third-party",
|
|
@@ -598,7 +598,7 @@
|
|
|
598
598
|
},
|
|
599
599
|
{
|
|
600
600
|
"path": "xll/python/Scripts/pywin32_testall.exe",
|
|
601
|
-
"sha256": "
|
|
601
|
+
"sha256": "223e9358a7135a893a91ac7e2ab575cbe6ab83052292687c55b3e33424bd94d1",
|
|
602
602
|
"source": "Python package console launcher installed into embedded Python",
|
|
603
603
|
"version": "see owning Python package metadata in site-packages",
|
|
604
604
|
"builtBy": "third-party",
|
|
@@ -48,6 +48,8 @@ export interface CreateAgentSessionRuntimeAdapterOptions {
|
|
|
48
48
|
fastModeDisabledReason?: FastModeDisabledReason;
|
|
49
49
|
modeName?: string;
|
|
50
50
|
model?: Model<Api>;
|
|
51
|
+
/** Whether `model` is the user's choice; false for a resolved default. Defaults to true. */
|
|
52
|
+
modelExplicit?: boolean;
|
|
51
53
|
observability?: RuntimeObservability;
|
|
52
54
|
permissionPolicy?: RuntimePermissionPolicy;
|
|
53
55
|
prePromptContext?: () => Promise<{
|
|
@@ -73,12 +75,15 @@ export interface CreateAgentSessionRuntimeAdapterResult extends CreateAgentSessi
|
|
|
73
75
|
}
|
|
74
76
|
export declare function resolveInitialSessionModel(options: {
|
|
75
77
|
model?: Model<Api>;
|
|
78
|
+
modelExplicit?: boolean;
|
|
76
79
|
modelRegistry: AgentModelRegistry;
|
|
77
80
|
sessionManager?: ProductSessionStore;
|
|
78
81
|
settingsManager: AgentSessionSettingsStore;
|
|
79
82
|
}): Promise<{
|
|
80
83
|
model: Model<Api> | undefined;
|
|
81
84
|
modelFallbackMessage?: string;
|
|
85
|
+
/** False when the model is a resolved default that login may replace. */
|
|
86
|
+
modelExplicit: boolean;
|
|
82
87
|
}>;
|
|
83
88
|
export declare function createAgentSessionRuntimeAdapter(options: CreateAgentSessionRuntimeAdapterOptions): Promise<CreateAgentSessionRuntimeAdapterResult>;
|
|
84
89
|
//# sourceMappingURL=agent-session-runtime-adapter.d.ts.map
|
|
@@ -23,14 +23,14 @@ import { convertToLlm } from './messages.js';
|
|
|
23
23
|
import { findInitialModel } from './providers/model-resolver.js';
|
|
24
24
|
export async function resolveInitialSessionModel(options) {
|
|
25
25
|
if (options.model)
|
|
26
|
-
return { model: options.model };
|
|
26
|
+
return { model: options.model, modelExplicit: options.modelExplicit ?? true };
|
|
27
27
|
const existingSession = options.sessionManager?.buildSessionContext();
|
|
28
28
|
const hasExistingSession = (existingSession?.messages.length ?? 0) > 0;
|
|
29
29
|
let modelFallbackMessage;
|
|
30
30
|
if (hasExistingSession && existingSession?.model) {
|
|
31
31
|
const restoredModel = options.modelRegistry.find(existingSession.model.provider, existingSession.model.modelId);
|
|
32
32
|
if (restoredModel && (await options.modelRegistry.getApiKey(restoredModel))) {
|
|
33
|
-
return { model: restoredModel };
|
|
33
|
+
return { model: restoredModel, modelExplicit: true };
|
|
34
34
|
}
|
|
35
35
|
modelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;
|
|
36
36
|
}
|
|
@@ -44,13 +44,14 @@ export async function resolveInitialSessionModel(options) {
|
|
|
44
44
|
if (!resolved.model) {
|
|
45
45
|
return {
|
|
46
46
|
model: undefined,
|
|
47
|
-
modelFallbackMessage: 'No models available. Configure credentials or provide a model.'
|
|
47
|
+
modelFallbackMessage: 'No models available. Configure credentials or provide a model.',
|
|
48
|
+
modelExplicit: false
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
if (modelFallbackMessage) {
|
|
51
52
|
modelFallbackMessage += `. Using ${resolved.model.provider}/${resolved.model.id}`;
|
|
52
53
|
}
|
|
53
|
-
return { model: resolved.model, modelFallbackMessage };
|
|
54
|
+
return { model: resolved.model, modelFallbackMessage, modelExplicit: resolved.explicit };
|
|
54
55
|
}
|
|
55
56
|
function createAgentSessionCore(session, pendingHostToolRequests, shortyRuntime) {
|
|
56
57
|
// AgentSession itself doesn't carry a "the last turn was cancelled" flag —
|
|
@@ -141,6 +142,7 @@ export async function createAgentSessionRuntimeAdapter(options) {
|
|
|
141
142
|
.some((entry) => entry.type === 'thinking_level_change');
|
|
142
143
|
const initialModel = await resolveInitialSessionModel({
|
|
143
144
|
model: options.model,
|
|
145
|
+
modelExplicit: options.modelExplicit,
|
|
144
146
|
modelRegistry,
|
|
145
147
|
sessionManager,
|
|
146
148
|
settingsManager
|
|
@@ -264,6 +266,7 @@ export async function createAgentSessionRuntimeAdapter(options) {
|
|
|
264
266
|
modelRegistry,
|
|
265
267
|
fastModeAllowed: options.fastModeAllowed,
|
|
266
268
|
fastModeDisabledReason: options.fastModeDisabledReason,
|
|
269
|
+
initialModelExplicit: initialModel.modelExplicit,
|
|
267
270
|
extensionRunnerRef,
|
|
268
271
|
prePromptContext: options.prePromptContext,
|
|
269
272
|
budgetLimits: options.budgetLimits,
|
|
@@ -29,6 +29,7 @@ import type { PromptTemplate } from './resources/prompt-templates.js';
|
|
|
29
29
|
import { type AutonomousState, type ConsumedPendingRefresh, type PendingRefresh } from './session/autonomous-workflow.js';
|
|
30
30
|
import { type ForkResult, type NavigateTreeResult } from './session/branch-manager.js';
|
|
31
31
|
import { type ExtensionBindings } from './session/extension-lifecycle.js';
|
|
32
|
+
import { type SetModelOptions } from './session/model-manager.js';
|
|
32
33
|
import { type ModelCycleResult } from './session/session-models.js';
|
|
33
34
|
import type { AgentSessionSettingsStore } from './session/session-settings-store.js';
|
|
34
35
|
import { type FastModeDisabledReason } from './session/session-speed.js';
|
|
@@ -105,6 +106,8 @@ export interface AgentSessionConfig {
|
|
|
105
106
|
modelRegistry: AgentModelRegistry;
|
|
106
107
|
fastModeAllowed?: boolean;
|
|
107
108
|
fastModeDisabledReason?: FastModeDisabledReason;
|
|
109
|
+
/** False when the initial model is a resolved default that login may re-resolve (default true). */
|
|
110
|
+
initialModelExplicit?: boolean;
|
|
108
111
|
/** Mutable ref used by AgentController to access the current ExtensionRunner */
|
|
109
112
|
extensionRunnerRef?: {
|
|
110
113
|
current?: ExtensionRunner;
|
|
@@ -449,8 +452,10 @@ export declare class AgentSession {
|
|
|
449
452
|
setup?: (sessionManager: ExtensionSessionSetupStore) => Promise<void>;
|
|
450
453
|
preserveAutonomous?: boolean;
|
|
451
454
|
}): Promise<boolean>;
|
|
452
|
-
/** Switch the
|
|
453
|
-
setModel(model: Model<any
|
|
455
|
+
/** Switch the active LLM; `{ explicit: false }` marks a runtime-chosen default (not saved). */
|
|
456
|
+
setModel(model: Model<any>, options?: SetModelOptions): Promise<void>;
|
|
457
|
+
/** True when the current model is the user's choice rather than a resolved default. */
|
|
458
|
+
get isModelExplicitlySelected(): boolean;
|
|
454
459
|
/** Cycle to the next configured model and report the switch result. */
|
|
455
460
|
cycleModel(direction?: 'forward' | 'backward'): Promise<ModelCycleResult | undefined>;
|
|
456
461
|
/** Set the model's reasoning/thinking level for subsequent turns. */
|
|
@@ -163,7 +163,7 @@ export class AgentSession {
|
|
|
163
163
|
});
|
|
164
164
|
// Construct extracted modules with stable deps
|
|
165
165
|
this._tools = new ToolRegistry(this.agent, this._resourceLoader, this.settingsManager, config.cwd, config.initialPermissionPolicy);
|
|
166
|
-
this._models = new ModelManager(this.agent, this._modelRegistry, this.sessionManager, this.settingsManager);
|
|
166
|
+
this._models = new ModelManager(this.agent, this._modelRegistry, this.sessionManager, this.settingsManager, config.initialModelExplicit ?? true);
|
|
167
167
|
this._branches = new BranchManager({
|
|
168
168
|
agent: this.agent,
|
|
169
169
|
sessionManager: this.sessionManager,
|
|
@@ -242,7 +242,7 @@ export class AgentSession {
|
|
|
242
242
|
compaction: this._compaction,
|
|
243
243
|
compactionActions: () => this._compactionActions,
|
|
244
244
|
removeLastErrorGroup: () => this._removeLastErrorGroup(),
|
|
245
|
-
setModel: (model) => this.setModel(model)
|
|
245
|
+
setModel: (model) => this.setModel(model, { explicit: false }) // automatic fallback, not a pick
|
|
246
246
|
});
|
|
247
247
|
// Forward extracted module events to session listeners
|
|
248
248
|
this._recovery.onEvent((event) => this._emit(event));
|
|
@@ -1330,11 +1330,15 @@ export class AgentSession {
|
|
|
1330
1330
|
// =========================================================================
|
|
1331
1331
|
// Model Management (delegated to ModelManager)
|
|
1332
1332
|
// =========================================================================
|
|
1333
|
-
/** Switch the
|
|
1334
|
-
async setModel(model) {
|
|
1335
|
-
await this._models.setModel(model);
|
|
1333
|
+
/** Switch the active LLM; `{ explicit: false }` marks a runtime-chosen default (not saved). */
|
|
1334
|
+
async setModel(model, options) {
|
|
1335
|
+
await this._models.setModel(model, options);
|
|
1336
1336
|
this._speed.clearUnavailableSpeed();
|
|
1337
1337
|
}
|
|
1338
|
+
/** True when the current model is the user's choice rather than a resolved default. */
|
|
1339
|
+
get isModelExplicitlySelected() {
|
|
1340
|
+
return this._models.isExplicit;
|
|
1341
|
+
}
|
|
1338
1342
|
/** Cycle to the next configured model and report the switch result. */
|
|
1339
1343
|
async cycleModel(direction = 'forward') {
|
|
1340
1344
|
const result = await this._models.cycleModel(direction);
|
|
@@ -60,7 +60,8 @@ export declare class ShortcutClientLogger {
|
|
|
60
60
|
private bufferAccountGeneration?;
|
|
61
61
|
private emit;
|
|
62
62
|
private startPeriodicFlush;
|
|
63
|
-
|
|
63
|
+
/** Send buffered entries; resolves true only when this call delivered a batch. */
|
|
64
|
+
flush(): Promise<boolean>;
|
|
64
65
|
destroy(): Promise<void>;
|
|
65
66
|
}
|
|
66
67
|
//# sourceMappingURL=client-logger.d.ts.map
|
|
@@ -79,9 +79,9 @@ export class ShortcutClientLogger {
|
|
|
79
79
|
this.flushTimer = null;
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
disable() {
|
|
82
|
+
async disable() {
|
|
83
83
|
this.setEnabled(false);
|
|
84
|
-
|
|
84
|
+
await this.flush();
|
|
85
85
|
}
|
|
86
86
|
info(input) {
|
|
87
87
|
this.emit('info', input);
|
|
@@ -127,27 +127,32 @@ export class ShortcutClientLogger {
|
|
|
127
127
|
void this.flush();
|
|
128
128
|
}, this.flushIntervalMs);
|
|
129
129
|
}
|
|
130
|
+
/** Send buffered entries; resolves true only when this call delivered a batch. */
|
|
130
131
|
async flush() {
|
|
131
132
|
const routing = getLocalShortcutRouting();
|
|
132
133
|
if (routing &&
|
|
133
134
|
(!routing.canSendUsageLogs() || this.bufferAccountGeneration !== routing.generation)) {
|
|
134
135
|
this.buffer = [];
|
|
135
|
-
return;
|
|
136
|
+
return false;
|
|
136
137
|
}
|
|
137
138
|
if (this.pendingFlush) {
|
|
138
139
|
await this.pendingFlush;
|
|
139
|
-
return;
|
|
140
|
+
return false;
|
|
140
141
|
}
|
|
141
142
|
if (this.buffer.length === 0) {
|
|
142
|
-
return;
|
|
143
|
+
return false;
|
|
143
144
|
}
|
|
144
145
|
const batch = {
|
|
145
146
|
client_time: this.now().toISOString(),
|
|
146
147
|
entries: this.buffer
|
|
147
148
|
};
|
|
148
149
|
this.buffer = [];
|
|
150
|
+
let delivered = false;
|
|
149
151
|
this.pendingFlush = this.transport
|
|
150
152
|
.send(batch)
|
|
153
|
+
.then(() => {
|
|
154
|
+
delivered = true;
|
|
155
|
+
})
|
|
151
156
|
.catch(() => {
|
|
152
157
|
// Fail open. Observability must never break the primary user flow.
|
|
153
158
|
})
|
|
@@ -155,6 +160,7 @@ export class ShortcutClientLogger {
|
|
|
155
160
|
this.pendingFlush = null;
|
|
156
161
|
});
|
|
157
162
|
await this.pendingFlush;
|
|
163
|
+
return delivered;
|
|
158
164
|
}
|
|
159
165
|
async destroy() {
|
|
160
166
|
this.destroyed = true;
|
|
@@ -70,6 +70,12 @@ export interface InitialModelResult {
|
|
|
70
70
|
model: Model<Api> | undefined;
|
|
71
71
|
thinkingLevel: ThinkingLevel;
|
|
72
72
|
fallbackMessage: string | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* True when the model was chosen by the user (CLI flag or saved settings), false when it is
|
|
75
|
+
* the resolved default. A default may be re-resolved when the account changes (login).
|
|
76
|
+
* Saved settings are honoured as written, even for a model the picker no longer lists.
|
|
77
|
+
*/
|
|
78
|
+
explicit: boolean;
|
|
73
79
|
}
|
|
74
80
|
/**
|
|
75
81
|
* Find the initial model to use based on priority:
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { resolveModelReasoningLevel } from '../../ai/models.js';
|
|
6
6
|
import { DEFAULT_THINKING_LEVEL, isValidThinkingLevel } from '../../core/defaults.js';
|
|
7
|
-
import {
|
|
7
|
+
import { SHORTCUT_MODEL_ID, SHORTCUT_OPENAI_MODEL_ID, SHORTCUT_PROVIDER_ID } from '../../model-ids.js';
|
|
8
|
+
import { getShortcutDefaultModelCandidates, getShortcutDefaultModelPlan, SHORTCUT_PRODUCT_DEFAULT_MODEL_ID } from '../../shortcut-default-model.js';
|
|
9
|
+
import { filterInteractiveSelectableModels } from '../session/session-models.js';
|
|
8
10
|
export function formatModelRef(model) {
|
|
9
11
|
return `${model.provider}/${model.id}`;
|
|
10
12
|
}
|
|
@@ -37,7 +39,7 @@ export function resolveExactModelRef(availableModels, requested) {
|
|
|
37
39
|
}
|
|
38
40
|
/** Default model IDs for each provider with a preferred initial model. */
|
|
39
41
|
export const defaultModelPerProvider = {
|
|
40
|
-
[SHORTCUT_PROVIDER_ID]:
|
|
42
|
+
[SHORTCUT_PROVIDER_ID]: SHORTCUT_PRODUCT_DEFAULT_MODEL_ID,
|
|
41
43
|
anthropic: SHORTCUT_MODEL_ID.ClaudeOpus5,
|
|
42
44
|
openai: SHORTCUT_OPENAI_MODEL_ID,
|
|
43
45
|
'azure-openai-responses': SHORTCUT_OPENAI_MODEL_ID,
|
|
@@ -57,16 +59,8 @@ export const defaultModelPerProvider = {
|
|
|
57
59
|
};
|
|
58
60
|
function getDefaultModelCandidatesForProvider(provider) {
|
|
59
61
|
if (provider === SHORTCUT_PROVIDER_ID) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
SHORTCUT_MODEL_ID.Gpt6Sol,
|
|
63
|
-
SHORTCUT_OPENAI_MODEL_ID,
|
|
64
|
-
SHORTCUT_MODEL_ID.ClaudeFable51,
|
|
65
|
-
SHORTCUT_MODEL_ID.ClaudeFable5,
|
|
66
|
-
SHORTCUT_MODEL_ID.ClaudeOpus5,
|
|
67
|
-
// Accounts without a paid plan see none of the models above.
|
|
68
|
-
SHORTCUT_FREE_MODEL_ID
|
|
69
|
-
];
|
|
62
|
+
// Plan-aware and picker-listed only, matching the web app's plan default.
|
|
63
|
+
return getShortcutDefaultModelCandidates(getShortcutDefaultModelPlan());
|
|
70
64
|
}
|
|
71
65
|
return [defaultModelPerProvider[provider]].filter((modelId) => !!modelId);
|
|
72
66
|
}
|
|
@@ -330,7 +324,8 @@ export async function findInitialModel(options) {
|
|
|
330
324
|
return {
|
|
331
325
|
model: found,
|
|
332
326
|
thinkingLevel: resolveModelReasoningLevel(found, undefined),
|
|
333
|
-
fallbackMessage: undefined
|
|
327
|
+
fallbackMessage: undefined,
|
|
328
|
+
explicit: true
|
|
334
329
|
};
|
|
335
330
|
}
|
|
336
331
|
// 2. Try saved default from settings
|
|
@@ -339,7 +334,7 @@ export async function findInitialModel(options) {
|
|
|
339
334
|
if (found) {
|
|
340
335
|
model = found;
|
|
341
336
|
thinkingLevel = resolveModelReasoningLevel(found, defaultThinkingLevel);
|
|
342
|
-
return { model, thinkingLevel, fallbackMessage: undefined };
|
|
337
|
+
return { model, thinkingLevel, fallbackMessage: undefined, explicit: true };
|
|
343
338
|
}
|
|
344
339
|
}
|
|
345
340
|
// 4. Try first available model with valid API key
|
|
@@ -353,19 +348,27 @@ export async function findInitialModel(options) {
|
|
|
353
348
|
return {
|
|
354
349
|
model: match,
|
|
355
350
|
thinkingLevel: resolveModelReasoningLevel(match, undefined),
|
|
356
|
-
fallbackMessage: undefined
|
|
351
|
+
fallbackMessage: undefined,
|
|
352
|
+
explicit: false
|
|
357
353
|
};
|
|
358
354
|
}
|
|
359
355
|
}
|
|
360
356
|
}
|
|
361
|
-
// If no default found, use first available
|
|
357
|
+
// If no default found, use the first available model the picker lists.
|
|
358
|
+
const firstModel = filterInteractiveSelectableModels(availableModels)[0] ?? availableModels[0];
|
|
362
359
|
return {
|
|
363
|
-
model:
|
|
364
|
-
thinkingLevel: resolveModelReasoningLevel(
|
|
365
|
-
fallbackMessage: undefined
|
|
360
|
+
model: firstModel,
|
|
361
|
+
thinkingLevel: resolveModelReasoningLevel(firstModel, undefined),
|
|
362
|
+
fallbackMessage: undefined,
|
|
363
|
+
explicit: false
|
|
366
364
|
};
|
|
367
365
|
}
|
|
368
366
|
// 5. No model found
|
|
369
|
-
return {
|
|
367
|
+
return {
|
|
368
|
+
model: undefined,
|
|
369
|
+
thinkingLevel: DEFAULT_THINKING_LEVEL,
|
|
370
|
+
fallbackMessage: undefined,
|
|
371
|
+
explicit: false
|
|
372
|
+
};
|
|
370
373
|
}
|
|
371
374
|
//# sourceMappingURL=model-resolver.js.map
|
|
@@ -19,6 +19,8 @@ export interface UserInfo {
|
|
|
19
19
|
proCreditsTier?: number | null;
|
|
20
20
|
authorizedSkillTeamIds?: string[];
|
|
21
21
|
additionalModelPickerEntries?: UserModelPickerEntry[];
|
|
22
|
+
/** Picker key the user's default resolves to (the org-admin-saved key for Team accounts). */
|
|
23
|
+
defaultModelPickerEntryKey?: string | null;
|
|
22
24
|
}
|
|
23
25
|
export interface UserModelPickerEntry {
|
|
24
26
|
modelName: string;
|
|
@@ -90,7 +90,10 @@ export async function fetchUserInfo(accessToken, options) {
|
|
|
90
90
|
plan: hasPlan ? (data.plan ?? null) : undefined,
|
|
91
91
|
proCreditsTier: data.proCreditsTier ?? null,
|
|
92
92
|
authorizedSkillTeamIds: parseAuthorizedSkillTeamIds(data),
|
|
93
|
-
...(additionalModelPickerEntries.length > 0 ? { additionalModelPickerEntries } : {})
|
|
93
|
+
...(additionalModelPickerEntries.length > 0 ? { additionalModelPickerEntries } : {}),
|
|
94
|
+
...(typeof data.defaultModelPickerEntryKey === 'string'
|
|
95
|
+
? { defaultModelPickerEntryKey: data.defaultModelPickerEntryKey }
|
|
96
|
+
: {})
|
|
94
97
|
};
|
|
95
98
|
}
|
|
96
99
|
catch (error) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeps a session's model in line with the signed-in account.
|
|
3
|
+
*
|
|
4
|
+
* A model the runtime resolved itself (no user pick yet) follows the account's plan
|
|
5
|
+
* default when the account changes, so a runtime that starts signed out lands on the
|
|
6
|
+
* same model as one that starts signed in. A model the user picked is kept unless the
|
|
7
|
+
* new account cannot use it.
|
|
8
|
+
*/
|
|
9
|
+
import type { Model } from '../../ai/types.js';
|
|
10
|
+
import type { SetModelOptions } from './model-manager.js';
|
|
11
|
+
export interface AccountModelSession {
|
|
12
|
+
readonly model: Model<any> | undefined;
|
|
13
|
+
readonly isModelExplicitlySelected: boolean;
|
|
14
|
+
/** The user's saved pick (settings defaultProvider/defaultModel), if any. */
|
|
15
|
+
readonly settingsManager?: {
|
|
16
|
+
getDefaultProvider(): string | undefined;
|
|
17
|
+
getDefaultModel(): string | undefined;
|
|
18
|
+
};
|
|
19
|
+
setModel(model: Model<any>, options?: SetModelOptions): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export interface AccountModelRegistry {
|
|
22
|
+
find(provider: string, modelId: string): Model<any> | undefined;
|
|
23
|
+
getAvailable(): Model<any>[] | Promise<Model<any>[]>;
|
|
24
|
+
}
|
|
25
|
+
/** Move off a model the account's policy no longer allows, onto the plan default. */
|
|
26
|
+
export declare function moveSessionOffPolicyDeniedModel(session: AccountModelSession | null | undefined, modelRegistry: AccountModelRegistry): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* After a login or account refresh: a resolved default becomes the user's saved pick when
|
|
29
|
+
* there is one (a signed-out start cannot restore it), otherwise the account's plan default;
|
|
30
|
+
* an explicit pick is kept unless the account cannot use it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function alignSessionModelWithAccount(session: AccountModelSession | null | undefined, modelRegistry: AccountModelRegistry): Promise<void>;
|
|
33
|
+
//# sourceMappingURL=account-default-model.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { resolveShortcutDefaultModel } from '../../shortcut-default-model.js';
|
|
2
|
+
import { filterInteractiveSelectableModels } from './session-models.js';
|
|
3
|
+
function sameModel(a, b) {
|
|
4
|
+
return a?.provider === b.provider && a?.id === b.id;
|
|
5
|
+
}
|
|
6
|
+
/** Move off a model the account's policy no longer allows, onto the plan default. */
|
|
7
|
+
export async function moveSessionOffPolicyDeniedModel(session, modelRegistry) {
|
|
8
|
+
const currentModel = session?.model;
|
|
9
|
+
if (!session || !currentModel)
|
|
10
|
+
return;
|
|
11
|
+
if (modelRegistry.find(currentModel.provider, currentModel.id))
|
|
12
|
+
return;
|
|
13
|
+
const availableModels = await modelRegistry.getAvailable();
|
|
14
|
+
const fallback = resolveShortcutDefaultModel(availableModels) ??
|
|
15
|
+
filterInteractiveSelectableModels(availableModels)[0] ??
|
|
16
|
+
availableModels[0];
|
|
17
|
+
if (fallback)
|
|
18
|
+
await session.setModel(fallback, { explicit: false });
|
|
19
|
+
}
|
|
20
|
+
/** The user's saved pick (settings), when the account can run it. */
|
|
21
|
+
function savedPick(session, availableModels) {
|
|
22
|
+
const provider = session.settingsManager?.getDefaultProvider();
|
|
23
|
+
const modelId = session.settingsManager?.getDefaultModel();
|
|
24
|
+
if (!provider || !modelId)
|
|
25
|
+
return undefined;
|
|
26
|
+
return availableModels.find((model) => model.provider === provider && model.id === modelId);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* After a login or account refresh: a resolved default becomes the user's saved pick when
|
|
30
|
+
* there is one (a signed-out start cannot restore it), otherwise the account's plan default;
|
|
31
|
+
* an explicit pick is kept unless the account cannot use it.
|
|
32
|
+
*/
|
|
33
|
+
export async function alignSessionModelWithAccount(session, modelRegistry) {
|
|
34
|
+
if (!session)
|
|
35
|
+
return;
|
|
36
|
+
if (!session.isModelExplicitlySelected) {
|
|
37
|
+
const availableModels = await modelRegistry.getAvailable();
|
|
38
|
+
const pick = savedPick(session, availableModels);
|
|
39
|
+
if (pick) {
|
|
40
|
+
await session.setModel(pick);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const planDefault = resolveShortcutDefaultModel(availableModels);
|
|
44
|
+
if (planDefault) {
|
|
45
|
+
if (!sameModel(session.model, planDefault)) {
|
|
46
|
+
await session.setModel(planDefault, { explicit: false });
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
await moveSessionOffPolicyDeniedModel(session, modelRegistry);
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=account-default-model.js.map
|
|
@@ -21,14 +21,25 @@ export interface ModelManagerAgent {
|
|
|
21
21
|
setModel(model: Model<any>): void;
|
|
22
22
|
setThinkingLevel(level: ThinkingLevel): void;
|
|
23
23
|
}
|
|
24
|
+
export interface SetModelOptions {
|
|
25
|
+
/**
|
|
26
|
+
* False when the runtime picks the model itself (the plan default after login, a
|
|
27
|
+
* policy fallback). Such a pick is not saved as the user's default model and may be
|
|
28
|
+
* re-resolved when the account changes. Defaults to true: a user selection.
|
|
29
|
+
*/
|
|
30
|
+
explicit?: boolean;
|
|
31
|
+
}
|
|
24
32
|
export declare class ModelManager {
|
|
25
33
|
private readonly _agent;
|
|
26
34
|
private readonly _modelRegistry;
|
|
27
35
|
private readonly _sessionManager;
|
|
28
36
|
private readonly _settingsManager;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
private _explicit;
|
|
38
|
+
constructor(_agent: ModelManagerAgent, _modelRegistry: AgentModelRegistry, _sessionManager: ModelSessionStore, _settingsManager: RuntimeDefaultsStore, initialModelExplicit?: boolean);
|
|
39
|
+
/** True when the current model is the user's choice rather than a resolved default. */
|
|
40
|
+
get isExplicit(): boolean;
|
|
41
|
+
/** Set a specific model. Validates API key; persists as the default only for user picks. */
|
|
42
|
+
setModel(model: Model<any>, options?: SetModelOptions): Promise<void>;
|
|
32
43
|
/** Cycle to the next available model. Returns result or undefined if none available. */
|
|
33
44
|
cycleModel(direction?: 'forward' | 'backward'): Promise<ModelCycleResult | undefined>;
|
|
34
45
|
/**
|
|
@@ -16,16 +16,24 @@ export class ModelManager {
|
|
|
16
16
|
_modelRegistry;
|
|
17
17
|
_sessionManager;
|
|
18
18
|
_settingsManager;
|
|
19
|
-
|
|
19
|
+
_explicit;
|
|
20
|
+
constructor(_agent, _modelRegistry, _sessionManager, _settingsManager, initialModelExplicit = true) {
|
|
20
21
|
this._agent = _agent;
|
|
21
22
|
this._modelRegistry = _modelRegistry;
|
|
22
23
|
this._sessionManager = _sessionManager;
|
|
23
24
|
this._settingsManager = _settingsManager;
|
|
25
|
+
this._explicit = initialModelExplicit;
|
|
24
26
|
}
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
+
/** True when the current model is the user's choice rather than a resolved default. */
|
|
28
|
+
get isExplicit() {
|
|
29
|
+
return this._explicit;
|
|
30
|
+
}
|
|
31
|
+
/** Set a specific model. Validates API key; persists as the default only for user picks. */
|
|
32
|
+
async setModel(model, options = {}) {
|
|
27
33
|
await this._validateApiKey(model);
|
|
28
|
-
|
|
34
|
+
const explicit = options.explicit ?? true;
|
|
35
|
+
this._applyAndPersist(model, explicit);
|
|
36
|
+
this._explicit = explicit;
|
|
29
37
|
this.setThinkingLevel(this._agent.state.thinkingLevel);
|
|
30
38
|
}
|
|
31
39
|
/** Cycle to the next available model. Returns result or undefined if none available. */
|
|
@@ -35,7 +43,8 @@ export class ModelManager {
|
|
|
35
43
|
if (!next)
|
|
36
44
|
return undefined;
|
|
37
45
|
await this._validateApiKey(next);
|
|
38
|
-
this._applyAndPersist(next);
|
|
46
|
+
this._applyAndPersist(next, true);
|
|
47
|
+
this._explicit = true;
|
|
39
48
|
this.setThinkingLevel(this._agent.state.thinkingLevel);
|
|
40
49
|
return { model: next, thinkingLevel: this._agent.state.thinkingLevel };
|
|
41
50
|
}
|
|
@@ -45,6 +54,8 @@ export class ModelManager {
|
|
|
45
54
|
*/
|
|
46
55
|
async restoreModel(model) {
|
|
47
56
|
this._agent.setModel(model);
|
|
57
|
+
// A resumed session keeps the model it was using; login does not replace it.
|
|
58
|
+
this._explicit = true;
|
|
48
59
|
}
|
|
49
60
|
/** Set thinking level, clamped to model capabilities. Persists only on change. */
|
|
50
61
|
setThinkingLevel(level) {
|
|
@@ -75,10 +86,11 @@ export class ModelManager {
|
|
|
75
86
|
if (!apiKey)
|
|
76
87
|
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
77
88
|
}
|
|
78
|
-
_applyAndPersist(model) {
|
|
89
|
+
_applyAndPersist(model, saveAsDefault) {
|
|
79
90
|
this._agent.setModel(model);
|
|
80
91
|
this._sessionManager.appendModelChange(model.provider, model.id);
|
|
81
|
-
|
|
92
|
+
if (saveAsDefault)
|
|
93
|
+
this._settingsManager.setDefaultModelAndProvider(model.provider, model.id);
|
|
82
94
|
}
|
|
83
95
|
}
|
|
84
96
|
//# sourceMappingURL=model-manager.js.map
|