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
9 changes: 9 additions & 0 deletions app/curiositypedia-experience.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -729,13 +729,22 @@ export function CuriosityPediaExperience() {

async function retryActiveQuestion() {
if (!activeJourney || !activeTurn) return;
const previewMedia = activeTurn.media[0];
await create({
seed: activeTurn.question,
performerId: nextPerformerId ?? activeJourney.performerId,
modelId: nextModelId ?? activeJourney.modelId,
researchPreset: "standard",
answerDensity: preferences.answerDensity,
outputLocale: preferences.defaultOutputLocale,
preview: previewMedia ? {
question: activeTurn.question,
teaser: previewMedia.caption,
imageUrl: previewMedia.imageUrl,
imageAlt: previewMedia.alt,
sourceLabel: previewMedia.title ?? activeTurn.topicLabel,
sourceUrl: previewMedia.sourcePageUrl,
} : undefined,
});
}

Expand Down
25 changes: 14 additions & 11 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,6 @@ html[data-theme="dark"] :is(input, textarea, select) { color-scheme: dark; }
opacity: .75;
position: absolute;
top: 29%;
transform: rotate(-11deg);
width: 94%;
z-index: 1;
}
Expand Down Expand Up @@ -1074,9 +1073,12 @@ html[data-theme="dark"] :is(input, textarea, select) { color-scheme: dark; }
inset calc(var(--celestial-size) * -.018) calc(var(--celestial-size) * -.018) calc(var(--celestial-size) * .03) rgba(122,74,0,.35),
0 0 calc(var(--celestial-size) * .06) rgba(255,212,59,.45);
height: 16%;
left: 4%;
left: 0;
offset-anchor: center;
offset-path: ellipse(46% 21% at 50% 50%);
offset-rotate: 0deg;
position: absolute;
top: 48%;
top: 0;
width: 16%;
z-index: 0;
}
Expand All @@ -1087,6 +1089,7 @@ html[data-theme="dark"] :is(input, textarea, select) { color-scheme: dark; }
animation: none;
background: linear-gradient(145deg, #ffffff, var(--sky));
left: 79%;
offset-path: none;
top: 24%;
transform: scale(.9);
z-index: 4;
Expand All @@ -1095,6 +1098,7 @@ html[data-theme="dark"] :is(input, textarea, select) { color-scheme: dark; }
animation: none;
background: var(--coral);
left: 79%;
offset-path: none;
top: 24%;
transform: scale(.9);
z-index: 4;
Expand All @@ -1103,14 +1107,13 @@ html[data-theme="dark"] :is(input, textarea, select) { color-scheme: dark; }
to { transform: translateX(-50%); }
}
@keyframes celestial-moon-orbit {
0% { left: 4%; top: 48%; transform: translate(-50%, -50%) scale(.72); z-index: 0; }
24.9% { z-index: 0; }
25% { left: 50%; top: 14%; transform: translate(-50%, -50%) scale(.86); z-index: 0; }
49.9% { z-index: 0; }
50% { left: 96%; top: 48%; transform: translate(-50%, -50%) scale(1.08); z-index: 4; }
75% { left: 50%; top: 82%; transform: translate(-50%, -50%) scale(.94); z-index: 4; }
99.9% { z-index: 4; }
100% { left: 4%; top: 48%; transform: translate(-50%, -50%) scale(.72); z-index: 0; }
0% { offset-distance: 0%; transform: scale(1.08); z-index: 4; }
25% { offset-distance: 25%; transform: scale(.94); z-index: 4; }
49.9% { offset-distance: 49.9%; transform: scale(.72); z-index: 4; }
50% { offset-distance: 50%; transform: scale(.72); z-index: 0; }
75% { offset-distance: 75%; transform: scale(.86); z-index: 0; }
99.9% { offset-distance: 99.9%; transform: scale(1.08); z-index: 0; }
100% { offset-distance: 100%; transform: scale(1.08); z-index: 4; }
}

/* Empty / loading / footer */
Expand Down
7 changes: 4 additions & 3 deletions docs/code-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Read [architecture.md](architecture.md) for system boundaries and change rules.
| [`lib/api.ts`](../lib/api.ts) | [`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/errors.ts`](../lib/errors.ts)<br>[`lib/viewer.ts`](../lib/viewer.ts) |
| [`lib/background-research.ts`](../lib/background-research.ts) | [`db/index.ts`](../db/index.ts)<br>[`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/errors.ts`](../lib/errors.ts)<br>[`lib/live-repository.ts`](../lib/live-repository.ts)<br>[`lib/live-research.ts`](../lib/live-research.ts)<br>[`lib/openai.ts`](../lib/openai.ts)<br>[`lib/provider-cost-control.ts`](../lib/provider-cost-control.ts)<br>[`lib/provider-usage.ts`](../lib/provider-usage.ts)<br>[`lib/research/provider-response.ts`](../lib/research/provider-response.ts)<br>[`lib/viewer.ts`](../lib/viewer.ts) |
| [`lib/bookmarks-repository.ts`](../lib/bookmarks-repository.ts) | [`db/index.ts`](../db/index.ts)<br>[`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/errors.ts`](../lib/errors.ts)<br>[`lib/request.ts`](../lib/request.ts)<br>[`lib/viewer.ts`](../lib/viewer.ts) |
| [`lib/catalog.ts`](../lib/catalog.ts) | [`lib/contracts.ts`](../lib/contracts.ts) |
| [`lib/catalog.ts`](../lib/catalog.ts) | [`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/research-config.ts`](../lib/research-config.ts) |
| [`lib/contracts.ts`](../lib/contracts.ts) | — |
| [`lib/diagnostics.ts`](../lib/diagnostics.ts) | [`db/index.ts`](../db/index.ts)<br>[`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/errors.ts`](../lib/errors.ts)<br>[`lib/viewer.ts`](../lib/viewer.ts) |
| [`lib/errors.ts`](../lib/errors.ts) | [`lib/contracts.ts`](../lib/contracts.ts) |
Expand All @@ -87,13 +87,14 @@ Read [architecture.md](architecture.md) for system boundaries and change rules.
| [`lib/landing-recommendations-repository.ts`](../lib/landing-recommendations-repository.ts) | [`db/index.ts`](../db/index.ts)<br>[`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/errors.ts`](../lib/errors.ts) |
| [`lib/live-redraw.ts`](../lib/live-redraw.ts) | [`lib/catalog.ts`](../lib/catalog.ts)<br>[`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/errors.ts`](../lib/errors.ts)<br>[`lib/i18n.ts`](../lib/i18n.ts)<br>[`lib/openai.ts`](../lib/openai.ts)<br>[`lib/provider-cost-control.ts`](../lib/provider-cost-control.ts)<br>[`lib/provider-usage.ts`](../lib/provider-usage.ts) |
| [`lib/live-repository.ts`](../lib/live-repository.ts) | [`db/index.ts`](../db/index.ts)<br>[`lib/catalog.ts`](../lib/catalog.ts)<br>[`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/errors.ts`](../lib/errors.ts)<br>[`lib/i18n.ts`](../lib/i18n.ts)<br>[`lib/live-research.ts`](../lib/live-research.ts)<br>[`lib/repository.ts`](../lib/repository.ts)<br>[`lib/request.ts`](../lib/request.ts)<br>[`lib/turn-options.ts`](../lib/turn-options.ts)<br>[`lib/usage-policy.ts`](../lib/usage-policy.ts)<br>[`lib/viewer.ts`](../lib/viewer.ts) |
| [`lib/live-research.ts`](../lib/live-research.ts) | [`lib/catalog.ts`](../lib/catalog.ts)<br>[`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/errors.ts`](../lib/errors.ts)<br>[`lib/fixtures.ts`](../lib/fixtures.ts)<br>[`lib/i18n.ts`](../lib/i18n.ts)<br>[`lib/openai.ts`](../lib/openai.ts)<br>[`lib/provider-cost-control.ts`](../lib/provider-cost-control.ts)<br>[`lib/provider-usage.ts`](../lib/provider-usage.ts)<br>[`lib/research/prompt-policy.ts`](../lib/research/prompt-policy.ts)<br>[`lib/research/provider-response.ts`](../lib/research/provider-response.ts)<br>[`lib/research/provider-stream.ts`](../lib/research/provider-stream.ts)<br>[`lib/research/turn-validation.ts`](../lib/research/turn-validation.ts) |
| [`lib/openai.ts`](../lib/openai.ts) | [`lib/errors.ts`](../lib/errors.ts) |
| [`lib/live-research.ts`](../lib/live-research.ts) | [`lib/catalog.ts`](../lib/catalog.ts)<br>[`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/errors.ts`](../lib/errors.ts)<br>[`lib/fixtures.ts`](../lib/fixtures.ts)<br>[`lib/i18n.ts`](../lib/i18n.ts)<br>[`lib/openai.ts`](../lib/openai.ts)<br>[`lib/provider-cost-control.ts`](../lib/provider-cost-control.ts)<br>[`lib/provider-usage.ts`](../lib/provider-usage.ts)<br>[`lib/research-config.ts`](../lib/research-config.ts)<br>[`lib/research/prompt-policy.ts`](../lib/research/prompt-policy.ts)<br>[`lib/research/provider-response.ts`](../lib/research/provider-response.ts)<br>[`lib/research/provider-stream.ts`](../lib/research/provider-stream.ts)<br>[`lib/research/turn-validation.ts`](../lib/research/turn-validation.ts) |
| [`lib/openai.ts`](../lib/openai.ts) | [`lib/errors.ts`](../lib/errors.ts)<br>[`lib/research-config.ts`](../lib/research-config.ts) |
| [`lib/preferences-repository.ts`](../lib/preferences-repository.ts) | [`db/index.ts`](../db/index.ts)<br>[`lib/catalog.ts`](../lib/catalog.ts)<br>[`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/errors.ts`](../lib/errors.ts)<br>[`lib/i18n.ts`](../lib/i18n.ts)<br>[`lib/request.ts`](../lib/request.ts)<br>[`lib/viewer.ts`](../lib/viewer.ts) |
| [`lib/provider-cost-control.ts`](../lib/provider-cost-control.ts) | [`db/index.ts`](../db/index.ts)<br>[`lib/catalog.ts`](../lib/catalog.ts)<br>[`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/errors.ts`](../lib/errors.ts)<br>[`lib/openai.ts`](../lib/openai.ts)<br>[`lib/usage-policy.ts`](../lib/usage-policy.ts) |
| [`lib/provider-usage.ts`](../lib/provider-usage.ts) | [`db/index.ts`](../db/index.ts)<br>[`lib/catalog.ts`](../lib/catalog.ts)<br>[`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/openai.ts`](../lib/openai.ts)<br>[`lib/provider-cost-control.ts`](../lib/provider-cost-control.ts) |
| [`lib/repository.ts`](../lib/repository.ts) | [`db/index.ts`](../db/index.ts)<br>[`lib/catalog.ts`](../lib/catalog.ts)<br>[`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/errors.ts`](../lib/errors.ts)<br>[`lib/fixtures.ts`](../lib/fixtures.ts)<br>[`lib/journeys/read-model.ts`](../lib/journeys/read-model.ts)<br>[`lib/request.ts`](../lib/request.ts)<br>[`lib/turn-options.ts`](../lib/turn-options.ts)<br>[`lib/usage-policy.ts`](../lib/usage-policy.ts)<br>[`lib/viewer.ts`](../lib/viewer.ts) |
| [`lib/request.ts`](../lib/request.ts) | [`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/errors.ts`](../lib/errors.ts) |
| [`lib/research-config.ts`](../lib/research-config.ts) | — |
| [`lib/research-status-repository.ts`](../lib/research-status-repository.ts) | [`db/index.ts`](../db/index.ts)<br>[`lib/errors.ts`](../lib/errors.ts)<br>[`lib/viewer.ts`](../lib/viewer.ts) |
| [`lib/research/prompt-policy.ts`](../lib/research/prompt-policy.ts) | [`lib/catalog.ts`](../lib/catalog.ts)<br>[`lib/contracts.ts`](../lib/contracts.ts)<br>[`lib/i18n.ts`](../lib/i18n.ts) |
| [`lib/research/provider-response.ts`](../lib/research/provider-response.ts) | — |
Expand Down
25 changes: 13 additions & 12 deletions lib/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
PresetConfig,
UserPreferences,
} from "./contracts";
import { OPENAI_PROMPT_LIMITS } from "./research-config";

export const PROMPT_VERSION = "wonder-research-turn@4.1.0";

Expand Down Expand Up @@ -188,33 +189,33 @@ export const PRESETS: PresetConfig[] = [
name: "Spark",
description: "A quick sourced orientation for simple or stable curiosity.",
sourceRange: "2–4 useful sources",
waitBand: "about 25 seconds",
waitBand: "up to 180 seconds",
costBand: "lowest live cost",
maxToolCalls: 2,
maxOutputTokens: 4000,
deadlineMs: 25_000,
maxToolCalls: OPENAI_PROMPT_LIMITS.liveResearch.spark.maxToolCalls,
maxOutputTokens: OPENAI_PROMPT_LIMITS.liveResearch.spark.maxOutputTokens,
deadlineMs: OPENAI_PROMPT_LIMITS.liveResearch.spark.timeoutMs,
},
{
id: "standard",
name: "Standard",
description: "Enough homework for a thoughtful, balanced performance.",
sourceRange: "3–8 useful sources",
waitBand: "about 60 seconds",
waitBand: "up to 240 seconds",
costBand: "balanced live cost",
maxToolCalls: 5,
maxOutputTokens: 8000,
deadlineMs: 60_000,
maxToolCalls: OPENAI_PROMPT_LIMITS.liveResearch.standard.maxToolCalls,
maxOutputTokens: OPENAI_PROMPT_LIMITS.liveResearch.standard.maxOutputTokens,
deadlineMs: OPENAI_PROMPT_LIMITS.liveResearch.standard.timeoutMs,
},
{
id: "deep",
name: "Deep",
description: "Broader evidence for niche, current, or contested questions.",
sourceRange: "6–15 useful sources",
waitBand: "up to 120 seconds",
waitBand: "up to 300 seconds",
costBand: "highest live cost",
maxToolCalls: 10,
maxOutputTokens: 16000,
deadlineMs: 120_000,
maxToolCalls: OPENAI_PROMPT_LIMITS.liveResearch.deep.maxToolCalls,
maxOutputTokens: OPENAI_PROMPT_LIMITS.liveResearch.deep.maxOutputTokens,
deadlineMs: OPENAI_PROMPT_LIMITS.liveResearch.deep.timeoutMs,
},
];

Expand Down
3 changes: 2 additions & 1 deletion lib/live-research.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
responseIncompleteReason,
structuredOutput,
} from "./openai";
import { OPENAI_IMAGE_SEARCH_MAX_RESULTS } from "./research-config";
import { recordOpenAIUsage } from "./provider-usage";
import { reserveProviderCost } from "./provider-cost-control";
import { localeName } from "./i18n";
Expand Down Expand Up @@ -218,7 +219,7 @@ export function liveResearchRequestBody(
: {
type: "web_search",
search_content_types: ["image", "text"],
image_settings: { max_results: 10, caption: true },
image_settings: { max_results: OPENAI_IMAGE_SEARCH_MAX_RESULTS, caption: true },
}],
tool_choice: "auto",
include: prepared.imagePreference === "avoid"
Expand Down
15 changes: 1 addition & 14 deletions lib/openai.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { env } from "cloudflare:workers";
import { RepositoryError } from "./errors";
export { OPENAI_PROMPT_LIMITS } from "./research-config";

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace -- Cloudflare augments this namespace.
Expand All @@ -13,20 +14,6 @@ declare global {

const RESPONSES_URL = "https://api.openai.com/v1/responses";

export const OPENAI_PROMPT_LIMITS = {
liveResearch: {
spark: { maxToolCalls: 3, maxOutputTokens: 10_000, reasoning: "low", timeoutMs: 60_000 },
standard: { maxToolCalls: 8, maxOutputTokens: 18_000, reasoning: "medium", timeoutMs: 150_000 },
deep: { maxToolCalls: 12, maxOutputTokens: 26_000, reasoning: "high", timeoutMs: 180_000 },
},
starterGeneration: { maxOutputTokens: 6_000, reasoning: "high" },
questionRedraw: { maxOutputTokens: 4_000, reasoning: "high" },
visualCuration: { maxToolCalls: 12, maxOutputTokens: 14_000, reasoning: "medium", timeoutMs: 120_000 },
imageNoteRepair: { maxOutputTokens: 3_000, reasoning: "medium", timeoutMs: 30_000 },
citationRepair: { maxOutputTokens: 2_000, reasoning: "medium", timeoutMs: 30_000 },
citationRecovery: { maxOutputTokens: 6_000, reasoning: "high", timeoutMs: 60_000 },
} as const;

export function openAIConfigured(): boolean {
return openAIEnabled() && Boolean(env.OPENAI_API_KEY?.trim());
}
Expand Down
5 changes: 5 additions & 0 deletions lib/provider-usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { ModelId } from "./contracts";
import { isRecord } from "./openai";
import {
markProviderCostUncertain,
releaseProviderCost,
settleProviderCost,
} from "./provider-cost-control";
import type { ProviderCostOperation } from "./provider-cost-control";
Expand Down Expand Up @@ -97,6 +98,10 @@ export async function recordOpenAIUsage(input: RecordProviderUsageInput): Promis
Math.round(usage.estimatedCostUsd * 1_000_000),
input.providerRequestId ?? usage.providerResponseId,
);
} else if (input.outcome === "http_error") {
// A rejected HTTP request never started model work, so it must not
// retain a full model-priced hold or poison automatic retries.
await releaseProviderCost(input.costReservationId);
} else {
await markProviderCostUncertain(input.costReservationId, input.providerRequestId);
}
Expand Down
55 changes: 55 additions & 0 deletions lib/research-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* The canonical generation profile for every selectable OpenAI model.
*
* Model routing may choose only `model`; prompts, reasoning, token budgets,
* tool budgets, and deadlines must come from this shared profile.
*/
export const GENERATION_CONFIG_VERSION = "high-quality@1";

export const OPENAI_PROMPT_LIMITS = Object.freeze({
liveResearch: Object.freeze({
spark: Object.freeze({
maxToolCalls: 8,
maxOutputTokens: 20_000,
reasoning: "high",
timeoutMs: 180_000,
}),
standard: Object.freeze({
maxToolCalls: 12,
maxOutputTokens: 30_000,
reasoning: "high",
timeoutMs: 240_000,
}),
deep: Object.freeze({
maxToolCalls: 16,
maxOutputTokens: 40_000,
reasoning: "high",
timeoutMs: 300_000,
}),
}),
starterGeneration: Object.freeze({ maxOutputTokens: 10_000, reasoning: "high" }),
questionRedraw: Object.freeze({ maxOutputTokens: 8_000, reasoning: "high" }),
visualCuration: Object.freeze({
maxToolCalls: 12,
maxOutputTokens: 20_000,
reasoning: "high",
timeoutMs: 180_000,
}),
imageNoteRepair: Object.freeze({
maxOutputTokens: 6_000,
reasoning: "high",
timeoutMs: 60_000,
}),
citationRepair: Object.freeze({
maxOutputTokens: 5_000,
reasoning: "high",
timeoutMs: 60_000,
}),
citationRecovery: Object.freeze({
maxOutputTokens: 10_000,
reasoning: "high",
timeoutMs: 90_000,
}),
} as const);

export const OPENAI_IMAGE_SEARCH_MAX_RESULTS = 10;
16 changes: 12 additions & 4 deletions lib/research/provider-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ export async function runProviderStream({
providerRequestId = response.headers.get("x-request-id");

if (!response.ok) {
const retryable = response.status === 408
|| response.status === 409
|| response.status === 429
|| response.status >= 500;
usageRecorded = true;
await recordOutcome({
...usageContext,
Expand All @@ -132,11 +136,15 @@ export async function runProviderStream({
response.status === 401 || response.status === 403
? "PROVIDER_UNAVAILABLE"
: "PROVIDER_ERROR",
response.status === 429
? "Live research is busy or has reached its provider limit. Please try again shortly."
: "Live research could not reach the provider. The journey was not committed; it is safe to retry.",
response.status === 401 || response.status === 403
? "This deployment cannot access the selected OpenAI model. Choose another model or verify the production OpenAI project access."
: response.status === 429
? "Live research is busy or has reached its provider limit. Please try again shortly."
: retryable
? "Live research could not reach the provider. The journey was not committed; it is safe to retry."
: "The provider rejected this research request. Verify the selected model and shared generation configuration.",
response.status === 429 ? 429 : 502,
true,
retryable,
);
}

Expand Down
4 changes: 2 additions & 2 deletions lib/usage-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ export const CURRENT_USER_POLICIES: Readonly<Record<Viewer["mode"], CurrentUserP
funding: "application",
allowedModelIds: CURRENT_LIVE_MODEL_IDS,
liveResearchLimit: 25,
identitySpendLimitUsd: 1,
identitySpendLimitUsd: 3,
journeyLimit: 50,
}),
chatgpt: Object.freeze({
mode: "chatgpt",
funding: "application",
allowedModelIds: CURRENT_LIVE_MODEL_IDS,
liveResearchLimit: 100,
identitySpendLimitUsd: 5,
identitySpendLimitUsd: 15,
journeyLimit: 25,
}),
});
Expand Down
Loading
Loading