@stigmer/react 0.4.5 → 0.4.7
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/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +1 -1
- package/composer/ComposerToolbar.js.map +1 -1
- package/index.d.ts +2 -2
- package/index.d.ts.map +1 -1
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/models/ModelRegistryContext.d.ts +21 -0
- package/models/ModelRegistryContext.d.ts.map +1 -0
- package/models/ModelRegistryContext.js +22 -0
- package/models/ModelRegistryContext.js.map +1 -0
- package/models/ModelSelector.d.ts +9 -1
- package/models/ModelSelector.d.ts.map +1 -1
- package/models/ModelSelector.js +10 -5
- package/models/ModelSelector.js.map +1 -1
- package/models/__tests__/useModelRegistry.test.js +127 -32
- package/models/__tests__/useModelRegistry.test.js.map +1 -1
- package/models/index.d.ts +3 -1
- package/models/index.d.ts.map +1 -1
- package/models/index.js +2 -1
- package/models/index.js.map +1 -1
- package/models/registry.d.ts +20 -12
- package/models/registry.d.ts.map +1 -1
- package/models/registry.js +51 -27
- package/models/registry.js.map +1 -1
- package/models/useModelRegistry.d.ts +11 -3
- package/models/useModelRegistry.d.ts.map +1 -1
- package/models/useModelRegistry.js +13 -5
- package/models/useModelRegistry.js.map +1 -1
- package/package.json +4 -4
- package/provider.d.ts.map +1 -1
- package/provider.js +42 -1
- package/provider.js.map +1 -1
- package/session/__tests__/useNewSessionFlow.test.js +89 -18
- package/session/__tests__/useNewSessionFlow.test.js.map +1 -1
- package/session/__tests__/usePersistedModel.test.js +26 -12
- package/session/__tests__/usePersistedModel.test.js.map +1 -1
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +20 -6
- package/session/useNewSessionFlow.js.map +1 -1
- package/src/composer/ComposerToolbar.tsx +1 -0
- package/src/index.ts +4 -1
- package/src/models/ModelRegistryContext.ts +32 -0
- package/src/models/ModelSelector.tsx +22 -5
- package/src/models/__tests__/useModelRegistry.test.tsx +150 -41
- package/src/models/index.ts +3 -1
- package/src/models/registry.ts +51 -30
- package/src/models/useModelRegistry.ts +18 -7
- package/src/provider.tsx +58 -8
- package/src/session/__tests__/useNewSessionFlow.test.tsx +120 -18
- package/src/session/__tests__/usePersistedModel.test.tsx +33 -12
- package/src/session/useNewSessionFlow.ts +17 -6
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { renderHook, act } from "@testing-library/react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
3
4
|
import { usePersistedModel } from "../usePersistedModel";
|
|
4
|
-
import { DEFAULT_MODEL_ID, DEFAULT_CURSOR_MODEL_ID } from "../../models/registry";
|
|
5
|
+
import { DEFAULT_MODEL_ID, DEFAULT_CURSOR_MODEL_ID, parseRegistryJson } from "../../models/registry";
|
|
6
|
+
import { ModelRegistryContext } from "../../models/ModelRegistryContext";
|
|
7
|
+
import type { ModelRegistryState } from "../../models/ModelRegistryContext";
|
|
8
|
+
|
|
9
|
+
const TEST_MODELS = parseRegistryJson({
|
|
10
|
+
models: [
|
|
11
|
+
{ id: "claude-sonnet-4.6", displayName: "Claude Sonnet 4.6", shortDescription: "", speedTier: "fast", provider: "anthropic", harness: "native", costTier: "standard", featured: true, pricing: { inputPricePerMillion: 3, outputPricePerMillion: 15, cacheWritePricePerMillion: 3.75, cacheReadPricePerMillion: 0.3 } },
|
|
12
|
+
{ id: "default", displayName: "Cursor Auto", shortDescription: "", speedTier: "fast", provider: "cursor", harness: "cursor", costTier: "standard", featured: true, pricing: { inputPricePerMillion: 1.25, outputPricePerMillion: 6, cacheWritePricePerMillion: 1.25, cacheReadPricePerMillion: 0.25 } },
|
|
13
|
+
],
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function createWrapper() {
|
|
17
|
+
const state: ModelRegistryState = { models: TEST_MODELS, isLoading: false, error: null };
|
|
18
|
+
return function Wrapper({ children }: { children: ReactNode }) {
|
|
19
|
+
return (
|
|
20
|
+
<ModelRegistryContext.Provider value={state}>
|
|
21
|
+
{children}
|
|
22
|
+
</ModelRegistryContext.Provider>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
5
26
|
|
|
6
27
|
const STORAGE_KEY_NATIVE = "stigmer:session:model";
|
|
7
28
|
const STORAGE_KEY_CURSOR = "stigmer:session:model:cursor";
|
|
@@ -17,24 +38,24 @@ describe("usePersistedModel", () => {
|
|
|
17
38
|
|
|
18
39
|
describe("basic persistence", () => {
|
|
19
40
|
it("returns undefined when localStorage is empty", () => {
|
|
20
|
-
const { result } = renderHook(() => usePersistedModel({ harness: "native" }));
|
|
41
|
+
const { result } = renderHook(() => usePersistedModel({ harness: "native" }), { wrapper: createWrapper() });
|
|
21
42
|
expect(result.current[0]).toBeUndefined();
|
|
22
43
|
});
|
|
23
44
|
|
|
24
45
|
it("restores a valid model from localStorage", () => {
|
|
25
46
|
localStorage.setItem(STORAGE_KEY_NATIVE, DEFAULT_MODEL_ID);
|
|
26
|
-
const { result } = renderHook(() => usePersistedModel({ harness: "native" }));
|
|
47
|
+
const { result } = renderHook(() => usePersistedModel({ harness: "native" }), { wrapper: createWrapper() });
|
|
27
48
|
expect(result.current[0]).toBe(DEFAULT_MODEL_ID);
|
|
28
49
|
});
|
|
29
50
|
|
|
30
51
|
it("returns undefined for an invalid model in localStorage", () => {
|
|
31
52
|
localStorage.setItem(STORAGE_KEY_NATIVE, "nonexistent-model-xyz");
|
|
32
|
-
const { result } = renderHook(() => usePersistedModel({ harness: "native" }));
|
|
53
|
+
const { result } = renderHook(() => usePersistedModel({ harness: "native" }), { wrapper: createWrapper() });
|
|
33
54
|
expect(result.current[0]).toBeUndefined();
|
|
34
55
|
});
|
|
35
56
|
|
|
36
57
|
it("persists model on change", () => {
|
|
37
|
-
const { result } = renderHook(() => usePersistedModel({ harness: "native" }));
|
|
58
|
+
const { result } = renderHook(() => usePersistedModel({ harness: "native" }), { wrapper: createWrapper() });
|
|
38
59
|
|
|
39
60
|
act(() => result.current[1](DEFAULT_MODEL_ID));
|
|
40
61
|
|
|
@@ -44,7 +65,7 @@ describe("usePersistedModel", () => {
|
|
|
44
65
|
|
|
45
66
|
it("uses cursor-specific key for cursor harness", () => {
|
|
46
67
|
localStorage.setItem(STORAGE_KEY_CURSOR, DEFAULT_CURSOR_MODEL_ID);
|
|
47
|
-
const { result } = renderHook(() => usePersistedModel({ harness: "cursor" }));
|
|
68
|
+
const { result } = renderHook(() => usePersistedModel({ harness: "cursor" }), { wrapper: createWrapper() });
|
|
48
69
|
expect(result.current[0]).toBe(DEFAULT_CURSOR_MODEL_ID);
|
|
49
70
|
});
|
|
50
71
|
});
|
|
@@ -52,19 +73,19 @@ describe("usePersistedModel", () => {
|
|
|
52
73
|
describe("compound key handling", () => {
|
|
53
74
|
it("extracts plain modelId from compound key in localStorage", () => {
|
|
54
75
|
localStorage.setItem(STORAGE_KEY_CURSOR, "cursor/default");
|
|
55
|
-
const { result } = renderHook(() => usePersistedModel({ harness: "cursor" }));
|
|
76
|
+
const { result } = renderHook(() => usePersistedModel({ harness: "cursor" }), { wrapper: createWrapper() });
|
|
56
77
|
expect(result.current[0]).toBe(DEFAULT_CURSOR_MODEL_ID);
|
|
57
78
|
});
|
|
58
79
|
|
|
59
80
|
it("extracts plain modelId from native compound key", () => {
|
|
60
81
|
localStorage.setItem(STORAGE_KEY_NATIVE, `native/${DEFAULT_MODEL_ID}`);
|
|
61
|
-
const { result } = renderHook(() => usePersistedModel({ harness: "native" }));
|
|
82
|
+
const { result } = renderHook(() => usePersistedModel({ harness: "native" }), { wrapper: createWrapper() });
|
|
62
83
|
expect(result.current[0]).toBe(DEFAULT_MODEL_ID);
|
|
63
84
|
});
|
|
64
85
|
|
|
65
86
|
it("handles non-compound values unchanged", () => {
|
|
66
87
|
localStorage.setItem(STORAGE_KEY_CURSOR, DEFAULT_CURSOR_MODEL_ID);
|
|
67
|
-
const { result } = renderHook(() => usePersistedModel({ harness: "cursor" }));
|
|
88
|
+
const { result } = renderHook(() => usePersistedModel({ harness: "cursor" }), { wrapper: createWrapper() });
|
|
68
89
|
expect(result.current[0]).toBe(DEFAULT_CURSOR_MODEL_ID);
|
|
69
90
|
});
|
|
70
91
|
});
|
|
@@ -76,7 +97,7 @@ describe("usePersistedModel", () => {
|
|
|
76
97
|
|
|
77
98
|
const { result, rerender } = renderHook(
|
|
78
99
|
({ harness }: { harness: "native" | "cursor" }) => usePersistedModel({ harness }),
|
|
79
|
-
{ initialProps: { harness: "native" } },
|
|
100
|
+
{ initialProps: { harness: "native" }, wrapper: createWrapper() },
|
|
80
101
|
);
|
|
81
102
|
|
|
82
103
|
expect(result.current[0]).toBe(DEFAULT_MODEL_ID);
|
|
@@ -91,7 +112,7 @@ describe("usePersistedModel", () => {
|
|
|
91
112
|
|
|
92
113
|
const { result, rerender } = renderHook(
|
|
93
114
|
({ harness }: { harness: "native" | "cursor" }) => usePersistedModel({ harness }),
|
|
94
|
-
{ initialProps: { harness: "native" } },
|
|
115
|
+
{ initialProps: { harness: "native" }, wrapper: createWrapper() },
|
|
95
116
|
);
|
|
96
117
|
|
|
97
118
|
expect(result.current[0]).toBe(DEFAULT_MODEL_ID);
|
|
@@ -106,7 +127,7 @@ describe("usePersistedModel", () => {
|
|
|
106
127
|
|
|
107
128
|
const { result, rerender } = renderHook(
|
|
108
129
|
({ harness }: { harness: "native" | "cursor" }) => usePersistedModel({ harness }),
|
|
109
|
-
{ initialProps: { harness: "native" } },
|
|
130
|
+
{ initialProps: { harness: "native" }, wrapper: createWrapper() },
|
|
110
131
|
);
|
|
111
132
|
|
|
112
133
|
rerender({ harness: "cursor" });
|
|
@@ -10,6 +10,7 @@ import { DEFAULT_HARNESS, type HarnessOption } from "../models/harness";
|
|
|
10
10
|
import { useWorkspaceEntries, type UseWorkspaceEntriesReturn } from "../workspace";
|
|
11
11
|
import { useSessionVariables, type UseSessionVariablesReturn } from "../execution/useSessionVariables";
|
|
12
12
|
import type { SessionComposerSubmitContext } from "../composer";
|
|
13
|
+
import { useRunnerList } from "../runner/useRunnerList";
|
|
13
14
|
import { useCreateSession } from "./useCreateSession";
|
|
14
15
|
import { useCreateAgentExecution } from "../execution/useCreateAgentExecution";
|
|
15
16
|
|
|
@@ -155,7 +156,8 @@ export function useNewSessionFlow(
|
|
|
155
156
|
return stored === "cursor" ? "cursor" : DEFAULT_HARNESS;
|
|
156
157
|
});
|
|
157
158
|
|
|
158
|
-
const { getModel } = useModelRegistry({ harness });
|
|
159
|
+
const { getModel, isLoading: isModelsLoading } = useModelRegistry({ harness });
|
|
160
|
+
const { runners, isLoading: isRunnersLoading } = useRunnerList(org);
|
|
159
161
|
const { create: createSession } = useCreateSession();
|
|
160
162
|
const { create: createExecution } = useCreateAgentExecution();
|
|
161
163
|
const {
|
|
@@ -192,8 +194,10 @@ export function useNewSessionFlow(
|
|
|
192
194
|
[],
|
|
193
195
|
);
|
|
194
196
|
|
|
195
|
-
// Restore persisted model
|
|
197
|
+
// Restore persisted model — only after the registry has loaded so
|
|
198
|
+
// getModel can actually validate the stored ID against live data.
|
|
196
199
|
useEffect(() => {
|
|
200
|
+
if (isModelsLoading) return;
|
|
197
201
|
const stored = localStorage.getItem(modelStorageKey(harness));
|
|
198
202
|
if (stored) {
|
|
199
203
|
const plain = parseModelKey(stored)?.modelId ?? stored;
|
|
@@ -201,7 +205,7 @@ export function useNewSessionFlow(
|
|
|
201
205
|
setModelId(plain);
|
|
202
206
|
}
|
|
203
207
|
}
|
|
204
|
-
}, [getModel, harness]);
|
|
208
|
+
}, [getModel, harness, isModelsLoading]);
|
|
205
209
|
|
|
206
210
|
// Persist model on change (using current harness key).
|
|
207
211
|
// Strip compound keys (e.g. "cursor/default") to plain modelId before storing.
|
|
@@ -212,13 +216,20 @@ export function useNewSessionFlow(
|
|
|
212
216
|
}
|
|
213
217
|
}, [modelId, harness]);
|
|
214
218
|
|
|
215
|
-
// Restore persisted runner
|
|
219
|
+
// Restore persisted runner — validate against the live runner list.
|
|
220
|
+
// If the stored runner no longer exists, discard it and clean up localStorage.
|
|
216
221
|
useEffect(() => {
|
|
222
|
+
if (isRunnersLoading) return;
|
|
217
223
|
const stored = localStorage.getItem(STORAGE_KEY_RUNNER);
|
|
218
|
-
if (stored)
|
|
224
|
+
if (!stored) return;
|
|
225
|
+
|
|
226
|
+
const exists = runners.some((r) => r.metadata?.id === stored);
|
|
227
|
+
if (exists) {
|
|
219
228
|
setRunnerId(stored);
|
|
229
|
+
} else {
|
|
230
|
+
localStorage.removeItem(STORAGE_KEY_RUNNER);
|
|
220
231
|
}
|
|
221
|
-
}, []);
|
|
232
|
+
}, [runners, isRunnersLoading]);
|
|
222
233
|
|
|
223
234
|
// Persist runner on change
|
|
224
235
|
useEffect(() => {
|