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: 2 additions & 0 deletions apps/server/scripts/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ const buildCmd = Command.make(
cwd: serverDir,
stdout: config.verbose ? "inherit" : "ignore",
stderr: "inherit",
shell: process.platform === "win32",
})`bun tsdown`,
);

Expand Down Expand Up @@ -225,6 +226,7 @@ const publishCmd = Command.make(
cwd: serverDir,
stdout: config.verbose ? "inherit" : "ignore",
stderr: "inherit",
shell: process.platform === "win32",
}),
);
}),
Expand Down
16 changes: 15 additions & 1 deletion apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type ProviderModelOptions,
type ProviderKind,
type ProviderServiceTier,
type ProviderStartOptions,
type OrchestrationSession,
ThreadId,
type ProviderSession,
Expand Down Expand Up @@ -144,6 +145,8 @@ const make = Effect.gen(function* () {
),
);

const threadProviderOptions = new Map<string, ProviderStartOptions>();

const appendProviderFailureActivity = (input: {
readonly threadId: ThreadId;
readonly kind:
Expand Down Expand Up @@ -203,6 +206,7 @@ const make = Effect.gen(function* () {
readonly model?: string;
readonly modelOptions?: ProviderModelOptions;
readonly serviceTier?: ProviderServiceTier | null;
readonly providerOptions?: ProviderStartOptions;
},
) {
const readModel = yield* orchestrationEngine.getReadModel();
Expand Down Expand Up @@ -239,6 +243,7 @@ const make = Effect.gen(function* () {
...(desiredModel ? { model: desiredModel } : {}),
...(options?.serviceTier !== undefined ? { serviceTier: options.serviceTier } : {}),
...(options?.modelOptions !== undefined ? { modelOptions: options.modelOptions } : {}),
...(options?.providerOptions !== undefined ? { providerOptions: options.providerOptions } : {}),
...(input?.resumeCursor !== undefined ? { resumeCursor: input.resumeCursor } : {}),
runtimeMode: desiredRuntimeMode,
});
Expand Down Expand Up @@ -325,18 +330,23 @@ const make = Effect.gen(function* () {
readonly model?: string;
readonly serviceTier?: ProviderServiceTier | null;
readonly modelOptions?: ProviderModelOptions;
readonly providerOptions?: ProviderStartOptions;
readonly interactionMode?: "default" | "plan";
readonly createdAt: string;
}) {
const thread = yield* resolveThread(input.threadId);
if (!thread) {
return;
}
if (input.providerOptions !== undefined) {
threadProviderOptions.set(input.threadId, input.providerOptions);
}
yield* ensureSessionForThread(input.threadId, input.createdAt, {
...(input.provider !== undefined ? { provider: input.provider } : {}),
...(input.model !== undefined ? { model: input.model } : {}),
...(input.serviceTier !== undefined ? { serviceTier: input.serviceTier } : {}),
...(input.modelOptions !== undefined ? { modelOptions: input.modelOptions } : {}),
...(input.providerOptions !== undefined ? { providerOptions: input.providerOptions } : {}),
});
const normalizedInput = toNonEmptyProviderInput(input.messageText);
const normalizedAttachments = input.attachments ?? [];
Expand Down Expand Up @@ -472,6 +482,7 @@ const make = Effect.gen(function* () {
...(event.payload.model !== undefined ? { model: event.payload.model } : {}),
...(event.payload.serviceTier !== undefined ? { serviceTier: event.payload.serviceTier } : {}),
...(event.payload.modelOptions !== undefined ? { modelOptions: event.payload.modelOptions } : {}),
...(event.payload.providerOptions !== undefined ? { providerOptions: event.payload.providerOptions } : {}),
interactionMode: event.payload.interactionMode,
createdAt: event.payload.createdAt,
});
Expand Down Expand Up @@ -627,7 +638,10 @@ const make = Effect.gen(function* () {
if (!thread?.session || thread.session.status === "stopped") {
return;
}
yield* ensureSessionForThread(event.payload.threadId, event.occurredAt);
const cachedProviderOptions = threadProviderOptions.get(event.payload.threadId);
yield* ensureSessionForThread(event.payload.threadId, event.occurredAt, {
...(cachedProviderOptions !== undefined ? { providerOptions: cachedProviderOptions } : {}),
});
return;
}
case "thread.turn-start-requested":
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/orchestration/decider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
...(command.model !== undefined ? { model: command.model } : {}),
...(command.serviceTier !== undefined ? { serviceTier: command.serviceTier } : {}),
...(command.modelOptions !== undefined ? { modelOptions: command.modelOptions } : {}),
...(command.providerOptions !== undefined ? { providerOptions: command.providerOptions } : {}),
assistantDeliveryMode: command.assistantDeliveryMode ?? DEFAULT_ASSISTANT_DELIVERY_MODE,
runtimeMode:
readModel.threads.find((entry) => entry.id === command.threadId)?.runtimeMode ??
Expand Down
26 changes: 23 additions & 3 deletions apps/server/src/provider/Layers/ProviderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,30 @@ function toRuntimeStatus(session: ProviderSession): "starting" | "running" | "st
}
}

function toRuntimePayloadFromSession(session: ProviderSession): Record<string, unknown> {
function toRuntimePayloadFromSession(
session: ProviderSession,
extra?: { readonly providerOptions?: unknown },
): Record<string, unknown> {
return {
cwd: session.cwd ?? null,
model: session.model ?? null,
activeTurnId: session.activeTurnId ?? null,
lastError: session.lastError ?? null,
...(extra?.providerOptions !== undefined ? { providerOptions: extra.providerOptions } : {}),
};
}

function readPersistedProviderOptions(
runtimePayload: ProviderRuntimeBinding["runtimePayload"],
): Record<string, unknown> | undefined {
if (!runtimePayload || typeof runtimePayload !== "object" || Array.isArray(runtimePayload)) {
return undefined;
}
const raw = "providerOptions" in runtimePayload ? runtimePayload.providerOptions : undefined;
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
return raw as Record<string, unknown>;
}

function readPersistedCwd(
runtimePayload: ProviderRuntimeBinding["runtimePayload"],
): string | undefined {
Expand Down Expand Up @@ -137,14 +152,15 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) =>
const upsertSessionBinding = (
session: ProviderSession,
threadId: ThreadId,
extra?: { readonly providerOptions?: unknown },
) =>
directory.upsert({
threadId,
provider: session.provider,
runtimeMode: session.runtimeMode,
status: toRuntimeStatus(session),
...(session.resumeCursor !== undefined ? { resumeCursor: session.resumeCursor } : {}),
runtimePayload: toRuntimePayloadFromSession(session),
runtimePayload: toRuntimePayloadFromSession(session, extra),
});

const providers = yield* registry.listProviders();
Expand Down Expand Up @@ -197,11 +213,13 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) =>
}

const persistedCwd = readPersistedCwd(input.binding.runtimePayload);
const persistedProviderOptions = readPersistedProviderOptions(input.binding.runtimePayload);

const resumed = yield* adapter.startSession({
threadId: input.binding.threadId,
provider: input.binding.provider,
...(persistedCwd ? { cwd: persistedCwd } : {}),
...(persistedProviderOptions ? { providerOptions: persistedProviderOptions } : {}),
...(hasResumeCursor ? { resumeCursor: input.binding.resumeCursor } : {}),
runtimeMode: input.binding.runtimeMode ?? "full-access",
});
Expand Down Expand Up @@ -273,7 +291,9 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) =>
);
}

yield* upsertSessionBinding(session, threadId);
yield* upsertSessionBinding(session, threadId, {
...(input.providerOptions !== undefined ? { providerOptions: input.providerOptions } : {}),
});
yield* analytics.record("provider.session.started", {
provider: session.provider,
runtimeMode: input.runtimeMode,
Expand Down
30 changes: 26 additions & 4 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,17 @@ export default function ChatView({ threadId }: ChatViewProps) {
};
return Object.keys(codexOptions).length > 0 ? { codex: codexOptions } : undefined;
}, [selectedCodexFastModeEnabled, selectedEffort, selectedProvider, supportsReasoningEffort]);
const providerOptionsForDispatch = useMemo(() => {
if (!settings.codexBinaryPath && !settings.codexHomePath) {
return undefined;
}
return {
codex: {
...(settings.codexBinaryPath ? { binaryPath: settings.codexBinaryPath } : {}),
...(settings.codexHomePath ? { homePath: settings.codexHomePath } : {}),
},
};
}, [settings.codexBinaryPath, settings.codexHomePath]);
const selectedModelForPicker = selectedModel;
const modelOptionsByProvider = useMemo(
() => getCustomModelOptionsByProvider(settings),
Expand Down Expand Up @@ -2626,6 +2637,9 @@ export default function ChatView({ threadId }: ChatViewProps) {
...(selectedModelOptionsForDispatch
? { modelOptions: selectedModelOptionsForDispatch }
: {}),
...(providerOptionsForDispatch
? { providerOptions: providerOptionsForDispatch }
: {}),
provider: selectedProvider,
assistantDeliveryMode: settings.enableAssistantStreaming ? "streaming" : "buffered",
runtimeMode,
Expand Down Expand Up @@ -2903,6 +2917,9 @@ export default function ChatView({ threadId }: ChatViewProps) {
...(selectedModelOptionsForDispatch
? { modelOptions: selectedModelOptionsForDispatch }
: {}),
...(providerOptionsForDispatch
? { providerOptions: providerOptionsForDispatch }
: {}),
assistantDeliveryMode: settings.enableAssistantStreaming ? "streaming" : "buffered",
runtimeMode,
interactionMode: nextInteractionMode,
Expand Down Expand Up @@ -2933,6 +2950,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
runtimeMode,
selectedModel,
selectedModelOptionsForDispatch,
providerOptionsForDispatch,
selectedProvider,
setComposerDraftInteractionMode,
setThreadError,
Expand Down Expand Up @@ -2987,8 +3005,8 @@ export default function ChatView({ threadId }: ChatViewProps) {
worktreePath: activeThread.worktreePath,
createdAt,
})
.then(() =>
api.orchestration.dispatchCommand({
.then(() => {
return api.orchestration.dispatchCommand({
type: "thread.turn.start",
commandId: newCommandId(),
threadId: nextThreadId,
Expand All @@ -3003,12 +3021,15 @@ export default function ChatView({ threadId }: ChatViewProps) {
...(selectedModelOptionsForDispatch
? { modelOptions: selectedModelOptionsForDispatch }
: {}),
...(providerOptionsForDispatch
? { providerOptions: providerOptionsForDispatch }
: {}),
assistantDeliveryMode: settings.enableAssistantStreaming ? "streaming" : "buffered",
runtimeMode,
interactionMode: "default",
createdAt,
}),
)
});
})
.then(() => api.orchestration.getSnapshot())
.then((snapshot) => {
syncServerReadModel(snapshot);
Expand Down Expand Up @@ -3052,6 +3073,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
runtimeMode,
selectedModel,
selectedModelOptionsForDispatch,
providerOptionsForDispatch,
selectedProvider,
settings.enableAssistantStreaming,
syncServerReadModel,
Expand Down
10 changes: 10 additions & 0 deletions packages/contracts/src/orchestration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ export type ProviderSandboxMode = typeof ProviderSandboxMode.Type;
export const ProviderServiceTier = Schema.Literals(["fast", "flex"]);
export type ProviderServiceTier = typeof ProviderServiceTier.Type;
export const DEFAULT_PROVIDER_KIND: ProviderKind = "codex";
const CodexProviderStartOptions = Schema.Struct({
binaryPath: Schema.optional(TrimmedNonEmptyString),
homePath: Schema.optional(TrimmedNonEmptyString),
});
const ProviderStartOptions = Schema.Struct({
codex: Schema.optional(CodexProviderStartOptions),
});
export const RuntimeMode = Schema.Literals(["approval-required", "full-access"]);
export type RuntimeMode = typeof RuntimeMode.Type;
export const DEFAULT_RUNTIME_MODE: RuntimeMode = "full-access";
Expand Down Expand Up @@ -366,6 +373,7 @@ export const ThreadTurnStartCommand = Schema.Struct({
model: Schema.optional(TrimmedNonEmptyString),
serviceTier: Schema.optional(Schema.NullOr(ProviderServiceTier)),
modelOptions: Schema.optional(ProviderModelOptions),
providerOptions: Schema.optional(ProviderStartOptions),
assistantDeliveryMode: Schema.optional(AssistantDeliveryMode),
runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(() => DEFAULT_RUNTIME_MODE)),
interactionMode: ProviderInteractionMode.pipe(
Expand All @@ -388,6 +396,7 @@ const ClientThreadTurnStartCommand = Schema.Struct({
model: Schema.optional(TrimmedNonEmptyString),
serviceTier: Schema.optional(Schema.NullOr(ProviderServiceTier)),
modelOptions: Schema.optional(ProviderModelOptions),
providerOptions: Schema.optional(ProviderStartOptions),
assistantDeliveryMode: Schema.optional(AssistantDeliveryMode),
runtimeMode: RuntimeMode,
interactionMode: ProviderInteractionMode,
Expand Down Expand Up @@ -668,6 +677,7 @@ export const ThreadTurnStartRequestedPayload = Schema.Struct({
model: Schema.optional(TrimmedNonEmptyString),
serviceTier: Schema.optional(Schema.NullOr(ProviderServiceTier)),
modelOptions: Schema.optional(ProviderModelOptions),
providerOptions: Schema.optional(ProviderStartOptions),
assistantDeliveryMode: Schema.optional(AssistantDeliveryMode),
runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(() => DEFAULT_RUNTIME_MODE)),
interactionMode: ProviderInteractionMode.pipe(
Expand Down
3 changes: 2 additions & 1 deletion packages/contracts/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ const CodexProviderStartOptions = Schema.Struct({
homePath: Schema.optional(TrimmedNonEmptyStringSchema),
});

const ProviderStartOptions = Schema.Struct({
export const ProviderStartOptions = Schema.Struct({
codex: Schema.optional(CodexProviderStartOptions),
});
export type ProviderStartOptions = typeof ProviderStartOptions.Type;

export const ProviderSessionStartInput = Schema.Struct({
threadId: ThreadId,
Expand Down