@makefinks/daemon 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -14
- package/package.json +4 -2
- package/src/ai/copilot-client.ts +775 -0
- package/src/ai/daemon-ai.ts +32 -234
- package/src/ai/model-config.ts +55 -14
- package/src/ai/providers/capabilities.ts +16 -0
- package/src/ai/providers/copilot-provider.ts +632 -0
- package/src/ai/providers/openrouter-provider.ts +217 -0
- package/src/ai/providers/registry.ts +14 -0
- package/src/ai/providers/types.ts +31 -0
- package/src/ai/system-prompt.ts +16 -0
- package/src/ai/tools/subagents.ts +1 -1
- package/src/ai/tools/tool-registry.ts +22 -1
- package/src/ai/tools/write-file.ts +51 -0
- package/src/app/components/AppOverlays.tsx +9 -1
- package/src/app/components/ConversationPane.tsx +8 -2
- package/src/components/ModelMenu.tsx +202 -140
- package/src/components/OnboardingOverlay.tsx +147 -1
- package/src/components/SettingsMenu.tsx +27 -1
- package/src/components/TokenUsageDisplay.tsx +5 -3
- package/src/components/tool-layouts/layouts/index.ts +1 -0
- package/src/components/tool-layouts/layouts/write-file.tsx +117 -0
- package/src/hooks/daemon-event-handlers.ts +61 -14
- package/src/hooks/keyboard-handlers.ts +109 -28
- package/src/hooks/use-app-callbacks.ts +141 -43
- package/src/hooks/use-app-context-builder.ts +5 -0
- package/src/hooks/use-app-controller.ts +31 -2
- package/src/hooks/use-app-copilot-models-loader.ts +45 -0
- package/src/hooks/use-app-display-state.ts +24 -2
- package/src/hooks/use-app-model.ts +103 -17
- package/src/hooks/use-app-preferences-bootstrap.ts +54 -10
- package/src/hooks/use-bootstrap-controller.ts +5 -0
- package/src/hooks/use-daemon-events.ts +8 -2
- package/src/hooks/use-daemon-keyboard.ts +19 -6
- package/src/hooks/use-daemon-runtime-controller.ts +4 -0
- package/src/hooks/use-menu-keyboard.ts +6 -1
- package/src/state/app-context.tsx +6 -0
- package/src/types/index.ts +24 -1
- package/src/utils/copilot-models.ts +77 -0
- package/src/utils/preferences.ts +3 -0
|
@@ -123,8 +123,13 @@ export function useAppController({
|
|
|
123
123
|
showProviderMenu,
|
|
124
124
|
});
|
|
125
125
|
const {
|
|
126
|
+
currentModelProvider,
|
|
127
|
+
setCurrentModelProvider,
|
|
126
128
|
currentModelId,
|
|
129
|
+
currentModelSupportsReasoning,
|
|
130
|
+
currentModelSupportsReasoningXHigh,
|
|
127
131
|
setCurrentModelId,
|
|
132
|
+
setCurrentModelForProvider,
|
|
128
133
|
currentOpenRouterProviderTag,
|
|
129
134
|
setCurrentOpenRouterProviderTag,
|
|
130
135
|
modelsWithPricing,
|
|
@@ -154,6 +159,7 @@ export function useAppController({
|
|
|
154
159
|
}, [onboardingComplete]);
|
|
155
160
|
|
|
156
161
|
const daemon = useDaemonRuntimeController({
|
|
162
|
+
currentModelProvider,
|
|
157
163
|
currentModelId,
|
|
158
164
|
preferencesLoaded,
|
|
159
165
|
sessionId: session.currentSessionId,
|
|
@@ -166,12 +172,18 @@ export function useAppController({
|
|
|
166
172
|
const [apiKeyMissingError, setApiKeyMissingError] = useState<string>("");
|
|
167
173
|
const [escPendingCancel, setEscPendingCancel] = useState(false);
|
|
168
174
|
|
|
169
|
-
const supportsReasoning =
|
|
175
|
+
const supportsReasoning =
|
|
176
|
+
currentModelProvider === "copilot"
|
|
177
|
+
? currentModelSupportsReasoning
|
|
178
|
+
: (daemon.modelMetadata?.supportsReasoning ?? false);
|
|
179
|
+
const supportsReasoningXHigh =
|
|
180
|
+
currentModelProvider === "copilot" ? currentModelSupportsReasoningXHigh : false;
|
|
170
181
|
|
|
171
182
|
// Preferences bootstrap (hook): returns a stable persist callback.
|
|
172
183
|
const { persistPreferences } = useAppPreferencesBootstrap({
|
|
173
184
|
manager,
|
|
174
|
-
|
|
185
|
+
setCurrentModelProvider,
|
|
186
|
+
setCurrentModelForProvider,
|
|
175
187
|
setCurrentOpenRouterProviderTag,
|
|
176
188
|
setCurrentDevice: bootstrap.setCurrentDevice,
|
|
177
189
|
setCurrentOutputDevice: bootstrap.setCurrentOutputDevice,
|
|
@@ -186,6 +198,7 @@ export function useAppController({
|
|
|
186
198
|
setLoadedPreferences: bootstrap.setLoadedPreferences,
|
|
187
199
|
setOnboardingActive: bootstrap.setOnboardingActive,
|
|
188
200
|
setOnboardingStep: bootstrap.setOnboardingStep,
|
|
201
|
+
setCopilotAuthenticated: bootstrap.setCopilotAuthenticated,
|
|
189
202
|
setPreferencesLoaded,
|
|
190
203
|
});
|
|
191
204
|
|
|
@@ -218,13 +231,17 @@ export function useAppController({
|
|
|
218
231
|
handleDeviceSelect,
|
|
219
232
|
handleOutputDeviceSelect,
|
|
220
233
|
handleModelSelect,
|
|
234
|
+
cycleModelProvider,
|
|
221
235
|
handleProviderSelect,
|
|
222
236
|
toggleInteractionMode,
|
|
223
237
|
completeOnboarding,
|
|
224
238
|
handleApiKeySubmit,
|
|
225
239
|
} = useAppCallbacks({
|
|
240
|
+
currentModelProvider,
|
|
241
|
+
setCurrentModelProvider,
|
|
226
242
|
currentModelId,
|
|
227
243
|
setCurrentModelId,
|
|
244
|
+
setCurrentModelForProvider,
|
|
228
245
|
setCurrentDevice: bootstrap.setCurrentDevice,
|
|
229
246
|
setCurrentOutputDevice: bootstrap.setCurrentOutputDevice,
|
|
230
247
|
setCurrentOpenRouterProviderTag,
|
|
@@ -235,7 +252,9 @@ export function useAppController({
|
|
|
235
252
|
persistPreferences,
|
|
236
253
|
loadedPreferences: bootstrap.loadedPreferences,
|
|
237
254
|
onboardingStep: bootstrap.onboardingStep,
|
|
255
|
+
copilotAuthenticated: bootstrap.copilotAuthenticated,
|
|
238
256
|
setOnboardingStep: bootstrap.setOnboardingStep,
|
|
257
|
+
setCopilotAuthenticated: bootstrap.setCopilotAuthenticated,
|
|
239
258
|
apiKeyTextareaRef: bootstrap.apiKeyTextareaRef,
|
|
240
259
|
setShowDeviceMenu,
|
|
241
260
|
setShowModelMenu,
|
|
@@ -367,6 +386,7 @@ export function useAppController({
|
|
|
367
386
|
hasGrounding: session.hasGrounding,
|
|
368
387
|
showFullReasoning,
|
|
369
388
|
showToolOutput,
|
|
389
|
+
currentModelProvider,
|
|
370
390
|
},
|
|
371
391
|
keyboardActions
|
|
372
392
|
);
|
|
@@ -379,8 +399,11 @@ export function useAppController({
|
|
|
379
399
|
reasoningQueue: daemon.reasoning.reasoningQueue,
|
|
380
400
|
responseElapsedMs: daemon.responseElapsedMs,
|
|
381
401
|
hasInteracted: daemon.hasInteracted,
|
|
402
|
+
currentModelProvider,
|
|
382
403
|
currentModelId,
|
|
383
404
|
modelMetadata: daemon.modelMetadata,
|
|
405
|
+
curatedModels: modelsWithPricing,
|
|
406
|
+
availableModels: openRouterModels,
|
|
384
407
|
preferencesLoaded,
|
|
385
408
|
currentSessionId: session.currentSessionId,
|
|
386
409
|
sessionMenuItems: session.sessionMenuItems,
|
|
@@ -466,6 +489,7 @@ export function useAppController({
|
|
|
466
489
|
reasoningEffort,
|
|
467
490
|
bashApprovalLevel,
|
|
468
491
|
supportsReasoning,
|
|
492
|
+
supportsReasoningXHigh,
|
|
469
493
|
canEnableVoiceOutput,
|
|
470
494
|
showFullReasoning,
|
|
471
495
|
setShowFullReasoning,
|
|
@@ -481,6 +505,8 @@ export function useAppController({
|
|
|
481
505
|
openRouterModels,
|
|
482
506
|
openRouterModelsLoading,
|
|
483
507
|
openRouterModelsUpdatedAt,
|
|
508
|
+
currentModelProvider,
|
|
509
|
+
setCurrentModelProvider,
|
|
484
510
|
currentModelId,
|
|
485
511
|
setCurrentModelId,
|
|
486
512
|
providerMenuItems,
|
|
@@ -499,6 +525,7 @@ export function useAppController({
|
|
|
499
525
|
onboarding: {
|
|
500
526
|
onboardingActive: bootstrap.onboardingActive,
|
|
501
527
|
onboardingStep: bootstrap.onboardingStep,
|
|
528
|
+
copilotAuthenticated: bootstrap.copilotAuthenticated,
|
|
502
529
|
setOnboardingStep: bootstrap.setOnboardingStep,
|
|
503
530
|
onboardingPreferences: bootstrap.loadedPreferences,
|
|
504
531
|
apiKeyTextareaRef: bootstrap.apiKeyTextareaRef,
|
|
@@ -509,6 +536,7 @@ export function useAppController({
|
|
|
509
536
|
},
|
|
510
537
|
settingsCallbacks: {
|
|
511
538
|
onToggleInteractionMode: toggleInteractionMode,
|
|
539
|
+
onCycleModelProvider: cycleModelProvider,
|
|
512
540
|
onSetVoiceInteractionType: setVoiceInteractionType,
|
|
513
541
|
onSetSpeechSpeed: setSpeechSpeed,
|
|
514
542
|
onSetReasoningEffort: setReasoningEffort,
|
|
@@ -590,6 +618,7 @@ export function useAppController({
|
|
|
590
618
|
},
|
|
591
619
|
sessionUsage: daemon.sessionUsage,
|
|
592
620
|
modelMetadata: daemon.modelMetadata,
|
|
621
|
+
currentModelProvider,
|
|
593
622
|
hasInteracted: daemon.hasInteracted,
|
|
594
623
|
frostColor,
|
|
595
624
|
initialStatusTop,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useCallback, useEffect } from "react";
|
|
2
|
+
import type { ModelOption } from "../types";
|
|
3
|
+
import { getCopilotModels } from "../utils/copilot-models";
|
|
4
|
+
|
|
5
|
+
export interface UseAppCopilotModelsLoaderParams {
|
|
6
|
+
preferencesLoaded: boolean;
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
setModels: React.Dispatch<React.SetStateAction<ModelOption[]>>;
|
|
9
|
+
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
|
10
|
+
setUpdatedAt: React.Dispatch<React.SetStateAction<number | null>>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UseAppCopilotModelsLoaderResult {
|
|
14
|
+
refresh: () => Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useAppCopilotModelsLoader(
|
|
18
|
+
params: UseAppCopilotModelsLoaderParams
|
|
19
|
+
): UseAppCopilotModelsLoaderResult {
|
|
20
|
+
const { preferencesLoaded, enabled = true, setModels, setLoading, setUpdatedAt } = params;
|
|
21
|
+
|
|
22
|
+
const refresh = useCallback(
|
|
23
|
+
async (forceRefresh = false) => {
|
|
24
|
+
if (!preferencesLoaded || !enabled) return;
|
|
25
|
+
setLoading(true);
|
|
26
|
+
try {
|
|
27
|
+
const result = await getCopilotModels({ forceRefresh });
|
|
28
|
+
setModels(result.models);
|
|
29
|
+
setUpdatedAt(result.timestamp);
|
|
30
|
+
} finally {
|
|
31
|
+
setLoading(false);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
[enabled, preferencesLoaded, setLoading, setModels, setUpdatedAt]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (!preferencesLoaded || !enabled) return;
|
|
39
|
+
void refresh(false);
|
|
40
|
+
}, [enabled, preferencesLoaded, refresh]);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
refresh: () => refresh(true),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import { DaemonState } from "../types";
|
|
3
|
-
import type { ContentBlock, SessionInfo } from "../types";
|
|
3
|
+
import type { ContentBlock, LlmProvider, ModelOption, SessionInfo } from "../types";
|
|
4
4
|
import { COLORS, STATE_COLOR_HEX, STATUS_TEXT } from "../ui/constants";
|
|
5
5
|
import { formatElapsedTime } from "../utils/formatters";
|
|
6
6
|
import type { ModelMetadata } from "../utils/model-metadata";
|
|
@@ -14,8 +14,11 @@ export interface UseAppDisplayStateParams {
|
|
|
14
14
|
responseElapsedMs: number;
|
|
15
15
|
hasInteracted: boolean;
|
|
16
16
|
|
|
17
|
+
currentModelProvider: LlmProvider;
|
|
17
18
|
currentModelId: string;
|
|
18
19
|
modelMetadata: ModelMetadata | null;
|
|
20
|
+
curatedModels: ModelOption[];
|
|
21
|
+
availableModels: ModelOption[];
|
|
19
22
|
preferencesLoaded: boolean;
|
|
20
23
|
|
|
21
24
|
currentSessionId: string | null;
|
|
@@ -57,8 +60,11 @@ export function useAppDisplayState(params: UseAppDisplayStateParams): UseAppDisp
|
|
|
57
60
|
reasoningQueue,
|
|
58
61
|
responseElapsedMs,
|
|
59
62
|
hasInteracted,
|
|
63
|
+
currentModelProvider,
|
|
60
64
|
currentModelId,
|
|
61
65
|
modelMetadata,
|
|
66
|
+
curatedModels,
|
|
67
|
+
availableModels,
|
|
62
68
|
preferencesLoaded,
|
|
63
69
|
currentSessionId,
|
|
64
70
|
sessionMenuItems,
|
|
@@ -113,8 +119,24 @@ export function useAppDisplayState(params: UseAppDisplayStateParams): UseAppDisp
|
|
|
113
119
|
if (modelMetadata?.name && modelMetadata.id === currentModelId) {
|
|
114
120
|
return modelMetadata.name;
|
|
115
121
|
}
|
|
122
|
+
const selectedModel =
|
|
123
|
+
availableModels.find((model) => model.id === currentModelId) ??
|
|
124
|
+
curatedModels.find((model) => model.id === currentModelId);
|
|
125
|
+
if (selectedModel?.name) {
|
|
126
|
+
return currentModelProvider === "copilot" ? `Copilot: ${selectedModel.name}` : selectedModel.name;
|
|
127
|
+
}
|
|
128
|
+
if (currentModelProvider === "copilot") {
|
|
129
|
+
return `Copilot: ${currentModelId}`;
|
|
130
|
+
}
|
|
116
131
|
return undefined;
|
|
117
|
-
}, [
|
|
132
|
+
}, [
|
|
133
|
+
availableModels,
|
|
134
|
+
curatedModels,
|
|
135
|
+
currentModelProvider,
|
|
136
|
+
modelMetadata,
|
|
137
|
+
currentModelId,
|
|
138
|
+
preferencesLoaded,
|
|
139
|
+
]);
|
|
118
140
|
|
|
119
141
|
const sessionTitle = useMemo(() => {
|
|
120
142
|
if (!currentSessionId) return undefined;
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import { useMemo, useState } from "react";
|
|
2
|
-
import {
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
AVAILABLE_MODELS,
|
|
4
|
+
DEFAULT_COPILOT_MODEL_ID,
|
|
5
|
+
DEFAULT_MODEL_ID,
|
|
6
|
+
DEFAULT_MODEL_PROVIDER,
|
|
7
|
+
} from "../ai/model-config";
|
|
8
|
+
import type { ProviderMenuItem } from "../components/ProviderMenu";
|
|
9
|
+
import type { LlmProvider, ModelOption } from "../types";
|
|
10
|
+
import type { OpenRouterInferenceProvider } from "../utils/openrouter-endpoints";
|
|
3
11
|
import { mergePricingAverages } from "../utils/openrouter-pricing";
|
|
12
|
+
import { useAppCopilotModelsLoader } from "./use-app-copilot-models-loader";
|
|
4
13
|
import { useAppModelPricingLoader } from "./use-app-model-pricing-loader";
|
|
5
14
|
import { useAppOpenRouterModelsLoader } from "./use-app-openrouter-models-loader";
|
|
6
15
|
import { useAppOpenRouterProviderLoader } from "./use-app-openrouter-provider-loader";
|
|
7
|
-
import type { ModelOption } from "../types";
|
|
8
|
-
import type { ProviderMenuItem } from "../components/ProviderMenu";
|
|
9
|
-
import type { OpenRouterInferenceProvider } from "../utils/openrouter-endpoints";
|
|
10
16
|
|
|
11
17
|
export interface UseAppModelParams {
|
|
12
18
|
preferencesLoaded: boolean;
|
|
@@ -14,8 +20,14 @@ export interface UseAppModelParams {
|
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
export interface UseAppModelReturn {
|
|
23
|
+
currentModelProvider: LlmProvider;
|
|
24
|
+
setCurrentModelProvider: React.Dispatch<React.SetStateAction<LlmProvider>>;
|
|
25
|
+
|
|
17
26
|
currentModelId: string;
|
|
18
|
-
|
|
27
|
+
currentModelSupportsReasoning: boolean;
|
|
28
|
+
currentModelSupportsReasoningXHigh: boolean;
|
|
29
|
+
setCurrentModelId: (modelId: string) => void;
|
|
30
|
+
setCurrentModelForProvider: (provider: LlmProvider, modelId: string) => void;
|
|
19
31
|
|
|
20
32
|
currentOpenRouterProviderTag: string | undefined;
|
|
21
33
|
setCurrentOpenRouterProviderTag: React.Dispatch<React.SetStateAction<string | undefined>>;
|
|
@@ -27,41 +39,59 @@ export interface UseAppModelReturn {
|
|
|
27
39
|
|
|
28
40
|
providerMenuItems: ProviderMenuItem[];
|
|
29
41
|
|
|
30
|
-
refreshOpenRouterModels: () => void
|
|
42
|
+
refreshOpenRouterModels: () => Promise<void>;
|
|
31
43
|
}
|
|
32
44
|
|
|
33
45
|
export function useAppModel(params: UseAppModelParams): UseAppModelReturn {
|
|
34
46
|
const { preferencesLoaded, showProviderMenu } = params;
|
|
35
47
|
|
|
36
|
-
const [
|
|
48
|
+
const [currentModelProvider, setCurrentModelProvider] = useState<LlmProvider>(DEFAULT_MODEL_PROVIDER);
|
|
49
|
+
const [openRouterModelId, setOpenRouterModelId] = useState(DEFAULT_MODEL_ID);
|
|
50
|
+
const [copilotModelId, setCopilotModelId] = useState(DEFAULT_COPILOT_MODEL_ID);
|
|
51
|
+
|
|
37
52
|
const [currentOpenRouterProviderTag, setCurrentOpenRouterProviderTag] = useState<string | undefined>(
|
|
38
53
|
undefined
|
|
39
54
|
);
|
|
40
|
-
|
|
55
|
+
|
|
56
|
+
const [openRouterModelsWithPricing, setOpenRouterModelsWithPricing] =
|
|
57
|
+
useState<ModelOption[]>(AVAILABLE_MODELS);
|
|
41
58
|
const [openRouterModels, setOpenRouterModels] = useState<ModelOption[]>([]);
|
|
42
59
|
const [openRouterModelsLoading, setOpenRouterModelsLoading] = useState(false);
|
|
43
60
|
const [openRouterModelsUpdatedAt, setOpenRouterModelsUpdatedAt] = useState<number | null>(null);
|
|
44
61
|
const [openRouterProviders, setOpenRouterProviders] = useState<OpenRouterInferenceProvider[]>([]);
|
|
45
62
|
|
|
63
|
+
const [copilotModels, setCopilotModels] = useState<ModelOption[]>([]);
|
|
64
|
+
const [copilotModelsLoading, setCopilotModelsLoading] = useState(false);
|
|
65
|
+
const [copilotModelsUpdatedAt, setCopilotModelsUpdatedAt] = useState<number | null>(null);
|
|
66
|
+
|
|
67
|
+
const currentModelId = currentModelProvider === "openrouter" ? openRouterModelId : copilotModelId;
|
|
68
|
+
|
|
46
69
|
useAppModelPricingLoader({
|
|
47
70
|
preferencesLoaded,
|
|
48
|
-
setModelsWithPricing,
|
|
71
|
+
setModelsWithPricing: setOpenRouterModelsWithPricing,
|
|
49
72
|
});
|
|
50
73
|
|
|
51
74
|
useAppOpenRouterProviderLoader({
|
|
52
75
|
preferencesLoaded,
|
|
53
|
-
showProviderMenu,
|
|
54
|
-
modelId:
|
|
76
|
+
showProviderMenu: showProviderMenu && currentModelProvider === "openrouter",
|
|
77
|
+
modelId: openRouterModelId,
|
|
55
78
|
setProviders: setOpenRouterProviders,
|
|
56
79
|
});
|
|
57
80
|
|
|
58
|
-
const { refresh:
|
|
81
|
+
const { refresh: refreshOpenRouterModelsRaw } = useAppOpenRouterModelsLoader({
|
|
59
82
|
preferencesLoaded,
|
|
60
83
|
setModels: setOpenRouterModels,
|
|
61
84
|
setLoading: setOpenRouterModelsLoading,
|
|
62
85
|
setUpdatedAt: setOpenRouterModelsUpdatedAt,
|
|
63
86
|
});
|
|
64
87
|
|
|
88
|
+
const { refresh: refreshCopilotModels } = useAppCopilotModelsLoader({
|
|
89
|
+
preferencesLoaded,
|
|
90
|
+
setModels: setCopilotModels,
|
|
91
|
+
setLoading: setCopilotModelsLoading,
|
|
92
|
+
setUpdatedAt: setCopilotModelsUpdatedAt,
|
|
93
|
+
});
|
|
94
|
+
|
|
65
95
|
const providerMenuItems: ProviderMenuItem[] = useMemo(() => {
|
|
66
96
|
const pricingCandidates = openRouterProviders
|
|
67
97
|
.map((p) => p.pricing)
|
|
@@ -108,16 +138,72 @@ export function useAppModel(params: UseAppModelParams): UseAppModelReturn {
|
|
|
108
138
|
return items;
|
|
109
139
|
}, [openRouterProviders, currentOpenRouterProviderTag]);
|
|
110
140
|
|
|
141
|
+
const modelsWithPricing =
|
|
142
|
+
currentModelProvider === "openrouter" ? openRouterModelsWithPricing : copilotModels;
|
|
143
|
+
const modelsForMenu = currentModelProvider === "openrouter" ? openRouterModels : copilotModels;
|
|
144
|
+
const modelsLoading =
|
|
145
|
+
currentModelProvider === "openrouter" ? openRouterModelsLoading : copilotModelsLoading;
|
|
146
|
+
const modelsUpdatedAt =
|
|
147
|
+
currentModelProvider === "openrouter" ? openRouterModelsUpdatedAt : copilotModelsUpdatedAt;
|
|
148
|
+
const currentModelSupportsReasoning = useMemo(() => {
|
|
149
|
+
if (currentModelProvider !== "copilot") {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
const selected = copilotModels.find((model) => model.id === copilotModelId);
|
|
153
|
+
return selected?.supportsReasoningEffort === true;
|
|
154
|
+
}, [copilotModelId, copilotModels, currentModelProvider]);
|
|
155
|
+
const currentModelSupportsReasoningXHigh = useMemo(() => {
|
|
156
|
+
if (currentModelProvider !== "copilot") {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
const selected = copilotModels.find((model) => model.id === copilotModelId);
|
|
160
|
+
return selected?.supportsReasoningEffortXHigh === true;
|
|
161
|
+
}, [copilotModelId, copilotModels, currentModelProvider]);
|
|
162
|
+
|
|
163
|
+
const setCurrentModelId = useCallback(
|
|
164
|
+
(modelId: string) => {
|
|
165
|
+
if (!modelId) return;
|
|
166
|
+
if (currentModelProvider === "openrouter") {
|
|
167
|
+
setOpenRouterModelId(modelId);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
setCopilotModelId(modelId);
|
|
171
|
+
},
|
|
172
|
+
[currentModelProvider]
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const setCurrentModelForProvider = useCallback((provider: LlmProvider, modelId: string) => {
|
|
176
|
+
if (!modelId) return;
|
|
177
|
+
if (provider === "openrouter") {
|
|
178
|
+
setOpenRouterModelId(modelId);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
setCopilotModelId(modelId);
|
|
182
|
+
}, []);
|
|
183
|
+
|
|
184
|
+
const refreshOpenRouterModels = useCallback(async () => {
|
|
185
|
+
if (currentModelProvider === "openrouter") {
|
|
186
|
+
await refreshOpenRouterModelsRaw();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
await refreshCopilotModels();
|
|
190
|
+
}, [currentModelProvider, refreshOpenRouterModelsRaw, refreshCopilotModels]);
|
|
191
|
+
|
|
111
192
|
return {
|
|
193
|
+
currentModelProvider,
|
|
194
|
+
setCurrentModelProvider,
|
|
112
195
|
currentModelId,
|
|
196
|
+
currentModelSupportsReasoning,
|
|
197
|
+
currentModelSupportsReasoningXHigh,
|
|
113
198
|
setCurrentModelId,
|
|
199
|
+
setCurrentModelForProvider,
|
|
114
200
|
currentOpenRouterProviderTag,
|
|
115
201
|
setCurrentOpenRouterProviderTag,
|
|
116
202
|
modelsWithPricing,
|
|
117
|
-
openRouterModels,
|
|
118
|
-
openRouterModelsLoading,
|
|
119
|
-
openRouterModelsUpdatedAt,
|
|
120
|
-
providerMenuItems,
|
|
203
|
+
openRouterModels: modelsForMenu,
|
|
204
|
+
openRouterModelsLoading: modelsLoading,
|
|
205
|
+
openRouterModelsUpdatedAt: modelsUpdatedAt,
|
|
206
|
+
providerMenuItems: currentModelProvider === "openrouter" ? providerMenuItems : [],
|
|
121
207
|
refreshOpenRouterModels,
|
|
122
208
|
};
|
|
123
209
|
}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef } from "react";
|
|
2
2
|
import { startMcpManager } from "../ai/mcp/mcp-manager";
|
|
3
|
-
import {
|
|
3
|
+
import { hasCopilotCliAuthSafe } from "../ai/copilot-client";
|
|
4
|
+
import {
|
|
5
|
+
getModelProvider,
|
|
6
|
+
getResponseModelForProvider,
|
|
7
|
+
setModelProvider,
|
|
8
|
+
setOpenRouterProviderTag,
|
|
9
|
+
setResponseModelForProvider,
|
|
10
|
+
} from "../ai/model-config";
|
|
11
|
+
import { invalidateDaemonToolsCache } from "../ai/tools";
|
|
12
|
+
import { invalidateSubagentToolsCache } from "../ai/tools/subagents";
|
|
4
13
|
import type {
|
|
5
14
|
AppPreferences,
|
|
6
15
|
BashApprovalLevel,
|
|
16
|
+
LlmProvider,
|
|
7
17
|
OnboardingStep,
|
|
8
18
|
ReasoningEffort,
|
|
9
19
|
SpeechSpeed,
|
|
@@ -26,7 +36,8 @@ export interface UseAppPreferencesBootstrapParams {
|
|
|
26
36
|
audioDeviceName?: string;
|
|
27
37
|
outputDeviceName?: string;
|
|
28
38
|
};
|
|
29
|
-
|
|
39
|
+
setCurrentModelProvider: (provider: LlmProvider) => void;
|
|
40
|
+
setCurrentModelForProvider: (provider: LlmProvider, modelId: string) => void;
|
|
30
41
|
setCurrentOpenRouterProviderTag: (providerTag: string | undefined) => void;
|
|
31
42
|
setCurrentDevice: (deviceName: string | undefined) => void;
|
|
32
43
|
setCurrentOutputDevice: (deviceName: string | undefined) => void;
|
|
@@ -41,6 +52,7 @@ export interface UseAppPreferencesBootstrapParams {
|
|
|
41
52
|
setLoadedPreferences: (prefs: AppPreferences | null) => void;
|
|
42
53
|
setOnboardingActive: (active: boolean) => void;
|
|
43
54
|
setOnboardingStep: (step: OnboardingStep) => void;
|
|
55
|
+
setCopilotAuthenticated: (authenticated: boolean) => void;
|
|
44
56
|
setPreferencesLoaded: (loaded: boolean) => void;
|
|
45
57
|
}
|
|
46
58
|
|
|
@@ -53,7 +65,8 @@ export function useAppPreferencesBootstrap(
|
|
|
53
65
|
): UseAppPreferencesBootstrapReturn {
|
|
54
66
|
const {
|
|
55
67
|
manager,
|
|
56
|
-
|
|
68
|
+
setCurrentModelProvider,
|
|
69
|
+
setCurrentModelForProvider,
|
|
57
70
|
setCurrentOpenRouterProviderTag,
|
|
58
71
|
setCurrentDevice,
|
|
59
72
|
setCurrentOutputDevice,
|
|
@@ -68,6 +81,7 @@ export function useAppPreferencesBootstrap(
|
|
|
68
81
|
setLoadedPreferences,
|
|
69
82
|
setOnboardingActive,
|
|
70
83
|
setOnboardingStep,
|
|
84
|
+
setCopilotAuthenticated,
|
|
71
85
|
setPreferencesLoaded,
|
|
72
86
|
} = params;
|
|
73
87
|
|
|
@@ -82,6 +96,7 @@ export function useAppPreferencesBootstrap(
|
|
|
82
96
|
|
|
83
97
|
useEffect(() => {
|
|
84
98
|
let cancelled = false;
|
|
99
|
+
let copilotAuthCheckTimer: ReturnType<typeof setTimeout> | null = null;
|
|
85
100
|
|
|
86
101
|
(async () => {
|
|
87
102
|
const prefs = await loadPreferences();
|
|
@@ -100,9 +115,18 @@ export function useAppPreferencesBootstrap(
|
|
|
100
115
|
// Start MCP discovery in the background (non-blocking)
|
|
101
116
|
startMcpManager();
|
|
102
117
|
|
|
118
|
+
const modelProvider: LlmProvider = prefs?.modelProvider ?? "openrouter";
|
|
119
|
+
setModelProvider(modelProvider);
|
|
120
|
+
invalidateDaemonToolsCache();
|
|
121
|
+
invalidateSubagentToolsCache();
|
|
122
|
+
setCurrentModelProvider(modelProvider);
|
|
123
|
+
|
|
103
124
|
if (prefs?.modelId) {
|
|
104
|
-
|
|
105
|
-
|
|
125
|
+
setResponseModelForProvider(modelProvider, prefs.modelId);
|
|
126
|
+
setCurrentModelForProvider(modelProvider, prefs.modelId);
|
|
127
|
+
} else {
|
|
128
|
+
const fallbackModelId = getResponseModelForProvider(modelProvider);
|
|
129
|
+
setCurrentModelForProvider(modelProvider, fallbackModelId);
|
|
106
130
|
}
|
|
107
131
|
|
|
108
132
|
if (prefs?.openRouterProviderTag) {
|
|
@@ -169,20 +193,35 @@ export function useAppPreferencesBootstrap(
|
|
|
169
193
|
const hasOpenRouterKey = Boolean(process.env.OPENROUTER_API_KEY);
|
|
170
194
|
const hasOpenAiKey = Boolean(process.env.OPENAI_API_KEY);
|
|
171
195
|
const hasExaKey = Boolean(process.env.EXA_API_KEY);
|
|
196
|
+
const usingCopilotProvider = modelProvider === "copilot";
|
|
197
|
+
const hasCopilotAuth = usingCopilotProvider;
|
|
198
|
+
setCopilotAuthenticated(hasCopilotAuth);
|
|
199
|
+
copilotAuthCheckTimer = setTimeout(() => {
|
|
200
|
+
void (async () => {
|
|
201
|
+
const authenticated = await hasCopilotCliAuthSafe();
|
|
202
|
+
if (cancelled) return;
|
|
203
|
+
setCopilotAuthenticated(authenticated);
|
|
204
|
+
if (!authenticated && getModelProvider() === "copilot") {
|
|
205
|
+
setOnboardingStep("copilot_auth");
|
|
206
|
+
setOnboardingActive(true);
|
|
207
|
+
}
|
|
208
|
+
})();
|
|
209
|
+
}, 0);
|
|
172
210
|
const hasCoreSettings = Boolean(prefs?.audioDeviceName && prefs?.modelId);
|
|
173
211
|
|
|
174
212
|
setLoadedPreferences(prefs);
|
|
175
213
|
|
|
176
214
|
const isFreshLaunch = prefs === null;
|
|
177
|
-
const
|
|
215
|
+
const hasProviderAuth = modelProvider === "openrouter" ? hasOpenRouterKey : hasCopilotAuth;
|
|
216
|
+
const needsOnboarding = !hasProviderAuth || !hasOpenAiKey || !hasExaKey;
|
|
178
217
|
|
|
179
218
|
if (isFreshLaunch) {
|
|
180
219
|
setOnboardingStep("intro");
|
|
181
220
|
setOnboardingActive(true);
|
|
182
221
|
} else if (needsOnboarding) {
|
|
183
|
-
let startStep: OnboardingStep = "
|
|
184
|
-
if (!
|
|
185
|
-
startStep = "openrouter_key";
|
|
222
|
+
let startStep: OnboardingStep = "provider";
|
|
223
|
+
if (!hasProviderAuth) {
|
|
224
|
+
startStep = modelProvider === "openrouter" ? "openrouter_key" : "copilot_auth";
|
|
186
225
|
} else if (!hasOpenAiKey) {
|
|
187
226
|
startStep = "openai_key";
|
|
188
227
|
} else if (!hasExaKey) {
|
|
@@ -203,10 +242,14 @@ export function useAppPreferencesBootstrap(
|
|
|
203
242
|
|
|
204
243
|
return () => {
|
|
205
244
|
cancelled = true;
|
|
245
|
+
if (copilotAuthCheckTimer) {
|
|
246
|
+
clearTimeout(copilotAuthCheckTimer);
|
|
247
|
+
}
|
|
206
248
|
};
|
|
207
249
|
}, [
|
|
208
250
|
manager,
|
|
209
|
-
|
|
251
|
+
setCurrentModelProvider,
|
|
252
|
+
setCurrentModelForProvider,
|
|
210
253
|
setCurrentOpenRouterProviderTag,
|
|
211
254
|
setCurrentDevice,
|
|
212
255
|
setCurrentOutputDevice,
|
|
@@ -220,6 +263,7 @@ export function useAppPreferencesBootstrap(
|
|
|
220
263
|
setLoadedPreferences,
|
|
221
264
|
setOnboardingActive,
|
|
222
265
|
setOnboardingStep,
|
|
266
|
+
setCopilotAuthenticated,
|
|
223
267
|
setPreferencesLoaded,
|
|
224
268
|
]);
|
|
225
269
|
|
|
@@ -15,6 +15,8 @@ export interface BootstrapControllerResult {
|
|
|
15
15
|
|
|
16
16
|
onboardingStep: OnboardingStep;
|
|
17
17
|
setOnboardingStep: (step: OnboardingStep) => void;
|
|
18
|
+
copilotAuthenticated: boolean;
|
|
19
|
+
setCopilotAuthenticated: (authenticated: boolean) => void;
|
|
18
20
|
|
|
19
21
|
loadedPreferences: AppPreferences | null;
|
|
20
22
|
setLoadedPreferences: (prefs: AppPreferences | null) => void;
|
|
@@ -50,6 +52,7 @@ export function useBootstrapController({
|
|
|
50
52
|
|
|
51
53
|
const [loadedPreferences, setLoadedPreferences] = useState<AppPreferences | null>(null);
|
|
52
54
|
const [onboardingStep, setOnboardingStep] = useState<OnboardingStep>("intro");
|
|
55
|
+
const [copilotAuthenticated, setCopilotAuthenticated] = useState(false);
|
|
53
56
|
|
|
54
57
|
const [devices, setDevices] = useState<AudioDevice[]>([]);
|
|
55
58
|
const [currentDevice, setCurrentDevice] = useState<string | undefined>(undefined);
|
|
@@ -75,6 +78,8 @@ export function useBootstrapController({
|
|
|
75
78
|
setOnboardingActive,
|
|
76
79
|
onboardingStep,
|
|
77
80
|
setOnboardingStep,
|
|
81
|
+
copilotAuthenticated,
|
|
82
|
+
setCopilotAuthenticated,
|
|
78
83
|
loadedPreferences,
|
|
79
84
|
setLoadedPreferences,
|
|
80
85
|
devices,
|
|
@@ -9,7 +9,7 @@ import { daemonEvents } from "../state/daemon-events";
|
|
|
9
9
|
import { getDaemonManager } from "../state/daemon-state";
|
|
10
10
|
import { buildModelHistoryFromConversation } from "../state/session-store";
|
|
11
11
|
import { DaemonState } from "../types";
|
|
12
|
-
import type { ContentBlock, ConversationMessage, TokenUsage, ToolCall } from "../types";
|
|
12
|
+
import type { ContentBlock, ConversationMessage, LlmProvider, TokenUsage, ToolCall } from "../types";
|
|
13
13
|
import { REASONING_COLORS, STATE_COLORS } from "../types/theme";
|
|
14
14
|
import { REASONING_ANIMATION } from "../ui/constants";
|
|
15
15
|
import { type ModelMetadata, getModelMetadata } from "../utils/model-metadata";
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
} from "./daemon-event-handlers";
|
|
42
42
|
|
|
43
43
|
export interface UseDaemonEventsParams {
|
|
44
|
+
currentModelProvider: LlmProvider;
|
|
44
45
|
currentModelId: string;
|
|
45
46
|
preferencesLoaded: boolean;
|
|
46
47
|
setReasoningQueue: (queue: string | ((prev: string) => string)) => void;
|
|
@@ -79,6 +80,7 @@ export interface UseDaemonEventsReturn {
|
|
|
79
80
|
|
|
80
81
|
export function useDaemonEvents(params: UseDaemonEventsParams): UseDaemonEventsReturn {
|
|
81
82
|
const {
|
|
83
|
+
currentModelProvider,
|
|
82
84
|
currentModelId,
|
|
83
85
|
preferencesLoaded,
|
|
84
86
|
setReasoningQueue,
|
|
@@ -117,6 +119,10 @@ export function useDaemonEvents(params: UseDaemonEventsParams): UseDaemonEventsR
|
|
|
117
119
|
|
|
118
120
|
useEffect(() => {
|
|
119
121
|
if (!preferencesLoaded) return;
|
|
122
|
+
if (currentModelProvider !== "openrouter") {
|
|
123
|
+
setModelMetadata(null);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
120
126
|
let cancelled = false;
|
|
121
127
|
getModelMetadata(currentModelId).then((metadata) => {
|
|
122
128
|
if (!cancelled) setModelMetadata(metadata);
|
|
@@ -124,7 +130,7 @@ export function useDaemonEvents(params: UseDaemonEventsParams): UseDaemonEventsR
|
|
|
124
130
|
return () => {
|
|
125
131
|
cancelled = true;
|
|
126
132
|
};
|
|
127
|
-
}, [currentModelId, preferencesLoaded]);
|
|
133
|
+
}, [currentModelId, currentModelProvider, preferencesLoaded]);
|
|
128
134
|
|
|
129
135
|
useEffect(() => {
|
|
130
136
|
clearFetchCache();
|