Merge upstream v0.4.0 into Gitea fork
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { compactionInFlight, deleteChatSession, getChat, isTurnRunning, sessionCoverage, sessionCost, stores, type ChatDeleteMode } from "@/src/server";
|
||||
import { compactionInFlight, deleteChatSession, getChat, isTurnRunning, sessionCoverage, sessionCost, stores, turnFailure, type ChatDeleteMode } from "@/src/server";
|
||||
|
||||
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
@@ -19,6 +19,8 @@ export async function GET(_req: Request, { params }: { params: Promise<{ id: str
|
||||
messages: chat.messages,
|
||||
pricing: sessionCost(chat),
|
||||
turnInFlight: isTurnRunning(id),
|
||||
// Also reaches idle views that missed the failing turn's stream.
|
||||
turnError: turnFailure(id),
|
||||
compacting: compactionInFlight(id),
|
||||
coverage: sessionCoverage(id),
|
||||
});
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
* submitter too attaches here. One SSE stream per chat: a running turn →
|
||||
* subscribe to the live turn object: FULL replay of the event log, then
|
||||
* live chunks until settle ([DONE]). No running turn → the cheap terminal
|
||||
* ({"type":"turn-settled"} + [DONE]): the record is current, the client
|
||||
* refetches it.
|
||||
* ({"type":"turn-settled"} + [DONE]): the client refetches the record.
|
||||
* A retained settlement-failure notice precedes that terminal when a
|
||||
* reply could not be saved before the submitter attached.
|
||||
*
|
||||
* 404 mirrors the POST handler's unknown-chat. Framework route is a thin
|
||||
* wrapper over the server barrel (the import law); tests drive the seams
|
||||
@@ -18,6 +19,6 @@ export async function GET(_req: Request, ctx: { params: Promise<{ id: string }>
|
||||
const { id } = await ctx.params;
|
||||
if (!getChat(id)) return Response.json({ error: "Unknown chat" }, { status: 404 });
|
||||
const handle = runningTurn(id);
|
||||
if (!handle) return settledTurnResponse();
|
||||
if (!handle) return settledTurnResponse(id);
|
||||
return piTurnResponse(handle);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { claudeCodeInstallationResponse } from "@/src/server";
|
||||
|
||||
/** Installation/version only; never reads auth status or makes a model request. */
|
||||
export async function GET(request: Request) {
|
||||
return claudeCodeInstallationResponse(request);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/** PR #62's single custom endpoint: discover/manual connect, offline pins,
|
||||
* and disconnect. Host boundaries and mutations live behind the server barrel. */
|
||||
import { customProviderGet, customProviderMutation } from "@/src/server";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const GET = customProviderGet;
|
||||
export const POST = customProviderMutation;
|
||||
export const PATCH = customProviderMutation;
|
||||
export const DELETE = customProviderMutation;
|
||||
@@ -10,7 +10,8 @@
|
||||
* tests/auth-provider-keys.test.ts) — the settings UI reads it optionally
|
||||
* with a graceful fallback.
|
||||
*
|
||||
* Masked only — last 4 + length; PLAINTEXT NEVER LEAVES THE STORE THROUGH
|
||||
* Masked only — last 4 + length for ordinary keys; custom keys are fully
|
||||
* redacted at every length (no credential suffix). PLAINTEXT NEVER LEAVES THE STORE THROUGH
|
||||
* THIS API (owner decision). `source` tells the page whether the live
|
||||
* value comes from the environment (dev .env.local / launch env) or the
|
||||
* file, and `shadowedByEnv` explains why a saved key is not the live one
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
* discipline, same as the per-chat and defaults routes); sections are
|
||||
* independent, so partial writes can never clobber each other.
|
||||
*
|
||||
* section ∈ general | chat | agent | appearance | graph | compaction
|
||||
* section ∈ general | chat | agent | appearance | graph | compaction | models | dictation
|
||||
* Body: the complete desired section object. Parse + validate strictly
|
||||
* (server/settings-body.ts): a bad shape/type is a 400 carrying the
|
||||
* supported list where one exists. The chat and models sections
|
||||
* additionally pass the catalog gate (validateChatSettings /
|
||||
* validateModelsSettings — unknown provider/model = 400). Thinking
|
||||
* validateModelsSettings — unknown provider/model = 400, except unchanged
|
||||
* model-slot selections may remain saved while unavailable). Thinking
|
||||
* levels never reject a write (owner, interlude 2026-09-22): a pinned
|
||||
* level CLAMPS to the named model's nearest supported level and the
|
||||
* clamped value is stored — model swaps just work, same as the
|
||||
@@ -63,6 +64,8 @@ export async function PUT(request: Request, ctx: { params: Promise<{ section: st
|
||||
if (!parsed.ok) {
|
||||
return Response.json({ error: parsed.error }, { status: 400 });
|
||||
}
|
||||
// Compare against the same record that this whole-section write replaces.
|
||||
const record = readSettingsRecord() ?? { version: 1 };
|
||||
let agentHealed: AgentSettings | undefined;
|
||||
|
||||
// Section-specific cross-validation (against the catalog / the prompt
|
||||
@@ -73,11 +76,11 @@ export async function PUT(request: Request, ctx: { params: Promise<{ section: st
|
||||
return Response.json({ error: verdict.error, supported: verdict.supported }, { status: 400 });
|
||||
}
|
||||
}
|
||||
// Model slots wave (2026-09-20): the models section gets the same
|
||||
// catalog gate the chat section gets — a slot must name a real
|
||||
// provider+model and a thinking level it supports.
|
||||
// New/replaced slot models pass the catalog gate. Unchanged saved model
|
||||
// references may survive disconnect, including thinking-preference edits,
|
||||
// so each unavailable slot can be repaired independently of the others.
|
||||
if (section === "models") {
|
||||
const verdict = validateModelsSettings(parsed.settings as ModelsSettings);
|
||||
const verdict = validateModelsSettings(parsed.settings as ModelsSettings, record.models);
|
||||
if (!verdict.ok) {
|
||||
return Response.json({ error: verdict.error, supported: verdict.supported }, { status: 400 });
|
||||
}
|
||||
@@ -124,7 +127,6 @@ export async function PUT(request: Request, ctx: { params: Promise<{ section: st
|
||||
|
||||
// Read-modify-write ONE section of the record; everything else rides
|
||||
// along untouched (other sections, unknown keys — sanitize preserves).
|
||||
const record = readSettingsRecord() ?? { version: 1 };
|
||||
const next = { ...record, [section]: sectionOut };
|
||||
writeSettingsRecord(next);
|
||||
return Response.json({ settings: next });
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { subscriptionBrowserResponse } from "@/src/server";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
export const GET = subscriptionBrowserResponse;
|
||||
export const POST = subscriptionBrowserResponse;
|
||||
@@ -0,0 +1,6 @@
|
||||
import { subscriptionControlsResponse } from "@/src/server";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
export const GET = subscriptionControlsResponse;
|
||||
export const POST = subscriptionControlsResponse;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { Chat } from "@/components/chat";
|
||||
import { slotsConfigured } from "@/src/core";
|
||||
import { compactionInFlight, getChat, isTurnRunning, readSettingsRecord } from "@/src/server";
|
||||
import { compactionInFlight, getChat, isTurnRunning, readSettingsRecord, turnFailure } from "@/src/server";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -29,6 +29,7 @@ export default async function ChatPage({
|
||||
// derived app-side (core/view.ts projectView; GET serves the same
|
||||
// native messages).
|
||||
initialMessages={chat.messages}
|
||||
initialError={turnFailure(chat.id)}
|
||||
initialConfigured={slotsConfigured(record?.models)}
|
||||
initialPrompt={prompt}
|
||||
// A teacher turn that outlived its client: the remounted view must
|
||||
|
||||
Reference in New Issue
Block a user