Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/desktop",
"version": "0.0.23",
"version": "0.0.24",
"private": true,
"type": "module",
"main": "dist-electron/main.cjs",
Expand Down
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "t3",
"version": "0.0.23",
"version": "0.0.24",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/web",
"version": "0.0.23",
"version": "0.0.24",
"private": true,
"type": "module",
"scripts": {
Expand Down
17 changes: 13 additions & 4 deletions apps/web/src/components/chat/ChatComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -727,9 +727,16 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
model: selectedModel,
models: selectedProviderModels,
prompt,
modelOptions: composerModelOptions?.[selectedProvider],
modelOptions: composerModelOptions?.[selectedInstanceId],
}),
[composerModelOptions, prompt, selectedModel, selectedProvider, selectedProviderModels],
[
composerModelOptions,
prompt,
selectedInstanceId,
selectedModel,
selectedProvider,
selectedProviderModels,
],
);

const selectedPromptEffort = composerProviderState.promptEffort;
Expand Down Expand Up @@ -1018,21 +1025,23 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)

const providerTraitsMenuContent = renderProviderTraitsMenuContent({
provider: selectedProvider,
instanceId: selectedInstanceId,
...(routeKind === "server" ? { threadRef: routeThreadRef } : {}),
...(routeKind === "draft" && draftId ? { draftId } : {}),
model: selectedModel,
models: selectedProviderModels,
modelOptions: composerModelOptions?.[selectedProvider],
modelOptions: composerModelOptions?.[selectedInstanceId],
prompt,
onPromptChange: setPromptFromTraits,
});
const providerTraitsPicker = renderProviderTraitsPicker({
provider: selectedProvider,
instanceId: selectedInstanceId,
...(routeKind === "server" ? { threadRef: routeThreadRef } : {}),
...(routeKind === "draft" && draftId ? { draftId } : {}),
model: selectedModel,
models: selectedProviderModels,
modelOptions: composerModelOptions?.[selectedProvider],
modelOptions: composerModelOptions?.[selectedInstanceId],
prompt,
onPromptChange: setPromptFromTraits,
});
Expand Down
8 changes: 7 additions & 1 deletion apps/web/src/components/chat/TraitsPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
type ProviderDriverKind,
type ProviderInstanceId,
type ProviderOptionDescriptor,
type ProviderOptionSelection,
type ScopedThreadRef,
Expand Down Expand Up @@ -196,6 +197,7 @@ export function shouldRenderTraitsControls(input: {

export interface TraitsMenuContentProps {
provider: ProviderDriverKind;
instanceId?: ProviderInstanceId;
models: ReadonlyArray<ServerProviderModel>;
model: string | null | undefined;
prompt: string;
Expand All @@ -208,6 +210,7 @@ export interface TraitsMenuContentProps {

export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
provider,
instanceId,
models,
model,
prompt,
Expand All @@ -228,11 +231,12 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
return;
}
setProviderModelOptions(threadTarget, provider, nextOptions, {
...(instanceId ? { instanceId } : {}),
model,
persistSticky: true,
});
},
[model, persistence, provider, setProviderModelOptions],
[instanceId, model, persistence, provider, setProviderModelOptions],
);
const {
descriptors,
Expand Down Expand Up @@ -343,6 +347,7 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({

export const TraitsPicker = memo(function TraitsPicker({
provider,
instanceId,
models,
model,
prompt,
Expand Down Expand Up @@ -431,6 +436,7 @@ export const TraitsPicker = memo(function TraitsPicker({
<MenuPopup align="start">
<TraitsMenuContent
provider={provider}
{...(instanceId ? { instanceId } : {})}
models={models}
model={model}
prompt={prompt}
Expand Down
16 changes: 14 additions & 2 deletions apps/web/src/components/chat/composerProviderState.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
type ProviderDriverKind,
type ProviderInstanceId,
type ProviderOptionSelection,
type ScopedThreadRef,
type ServerProviderModel,
Expand Down Expand Up @@ -35,6 +36,7 @@ export type ComposerProviderState = {

type TraitsRenderInput = {
provider: ProviderDriverKind;
instanceId?: ProviderInstanceId;
threadRef?: ScopedThreadRef;
draftId?: DraftId;
model: string;
Expand Down Expand Up @@ -76,8 +78,17 @@ function renderTraitsControl(
Component: typeof TraitsMenuContent | typeof TraitsPicker,
input: TraitsRenderInput,
): ReactNode {
const { provider, threadRef, draftId, model, models, modelOptions, prompt, onPromptChange } =
input;
const {
provider,
instanceId,
threadRef,
draftId,
model,
models,
modelOptions,
prompt,
onPromptChange,
} = input;
const hasTarget = threadRef !== undefined || draftId !== undefined;
if (
!hasTarget ||
Expand All @@ -88,6 +99,7 @@ function renderTraitsControl(
return (
<Component
provider={provider}
{...(instanceId ? { instanceId } : {})}
models={models}
{...(threadRef ? { threadRef } : {})}
{...(draftId ? { draftId } : {})}
Expand Down
38 changes: 38 additions & 0 deletions apps/web/src/composerDraftStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { createModelSelection } from "@t3tools/shared/model";
// `stickyModelSelectionByProvider` maps are keyed by `ProviderInstanceId`
// in production; these aliases keep the legacy-key migration tests concise.
const CODEX_INSTANCE = ProviderInstanceId.make("codex");
const CODEX_SECONDARY_INSTANCE = ProviderInstanceId.make("codex_secondary");
const CLAUDE_AGENT_INSTANCE = ProviderInstanceId.make("claudeAgent");
const CURSOR_INSTANCE = ProviderInstanceId.make("cursor");
const CODEX_DRIVER = ProviderDriverKind.make("codex");
Expand Down Expand Up @@ -1201,6 +1202,43 @@ describe("composerDraftStore modelSelection", () => {
);
});

it("stores provider option changes on a selected custom instance", () => {
const store = useComposerDraftStore.getState();

store.setProviderModelOptions(
threadRef,
CODEX_DRIVER,
toSelections({ reasoningEffort: "low" }),
{
instanceId: CODEX_SECONDARY_INSTANCE,
model: "gpt-5-codex",
persistSticky: true,
},
);

expect(
draftFor(threadId, TEST_ENVIRONMENT_ID)?.modelSelectionByProvider[CODEX_SECONDARY_INSTANCE],
).toEqual(
expect.objectContaining({
instanceId: CODEX_SECONDARY_INSTANCE,
options: [{ id: "reasoningEffort", value: "low" }],
}),
);
expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.activeProvider).toBe(CODEX_SECONDARY_INSTANCE);
expect(useComposerDraftStore.getState().stickyActiveProvider).toBe(CODEX_SECONDARY_INSTANCE);
expect(useComposerDraftStore.getState().stickyModelSelectionByProvider[CODEX_INSTANCE]).toBe(
undefined,
);
expect(
useComposerDraftStore.getState().stickyModelSelectionByProvider[CODEX_SECONDARY_INSTANCE],
).toEqual(
expect.objectContaining({
instanceId: CODEX_SECONDARY_INSTANCE,
options: [{ id: "reasoningEffort", value: "low" }],
}),
);
});

it("updates only the draft when sticky persistence is disabled", () => {
const store = useComposerDraftStore.getState();

Expand Down
8 changes: 6 additions & 2 deletions apps/web/src/composerDraftStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ interface ComposerDraftStoreState {
provider: ProviderDriverKind,
nextProviderOptions: ReadonlyArray<ProviderOptionSelection> | null | undefined,
options?: {
instanceId?: ProviderInstanceId | null | undefined;
model?: string | null | undefined;
persistSticky?: boolean;
},
Expand Down Expand Up @@ -2456,7 +2457,7 @@ const composerDraftStore = create<ComposerDraftStoreState>()(
if (normalizedProvider === null) {
return;
}
const instanceKey = defaultInstanceIdForDriver(normalizedProvider);
const instanceKey = options?.instanceId ?? defaultInstanceIdForDriver(normalizedProvider);
const fallbackModel =
normalizeModelSlug(options?.model, normalizedProvider) ??
DEFAULT_MODEL_BY_PROVIDER[normalizedProvider] ??
Expand Down Expand Up @@ -2501,7 +2502,9 @@ const composerDraftStore = create<ComposerDraftStoreState>()(
const { options: _, ...rest } = stickyBase;
nextStickyMap[instanceKey] = rest as ModelSelection;
}
nextStickyActiveProvider = base.activeProvider ?? instanceKey;
nextStickyActiveProvider = options.instanceId
? instanceKey
: (base.activeProvider ?? instanceKey);
}

if (
Expand All @@ -2514,6 +2517,7 @@ const composerDraftStore = create<ComposerDraftStoreState>()(

const nextDraft: ComposerThreadDraftState = {
...base,
...(options?.instanceId ? { activeProvider: instanceKey } : {}),
modelSelectionByProvider: nextMap,
};
const nextDraftsByThreadKey = { ...state.draftsByThreadKey };
Expand Down
4 changes: 4 additions & 0 deletions docs/upstream/patches/0002-workspace-shell-reframe.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ composition, which are likely to continue evolving upstream.

- `working tree`: initial workspace shell, AI pane framing, and hosted-shell onboarding updates
- `21ed1770`: explicit AI pane extraction through a reusable workspace pane frame and shared chat action bar
- `ccd33a35`: upstream sync merge against `upstream/main` through `4f0f24f0`

## Sync Notes

Expand All @@ -83,3 +84,6 @@ composition, which are likely to continue evolving upstream.
- split future implementation into smaller patch files when the shell reframe
turns into concrete workstreams such as sidebar mapping, pane extraction, or
layout persistence
- Verified during the 2026-05-26 upstream sync against upstream commit
`4f0f24f0`; upstream composer reasoning-selection updates touched shared chat
state and UI but did not require local workspace-shell code changes.
2 changes: 1 addition & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/contracts",
"version": "0.0.23",
"version": "0.0.24",
"private": true,
"files": [
"dist"
Expand Down