birdclaw 0.8.2 → 0.8.3
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/CHANGELOG.md +16 -0
- package/package.json +2 -1
- package/src/cli/command-context.ts +17 -0
- package/src/cli/register-analysis.ts +500 -0
- package/src/cli/register-compose.ts +40 -0
- package/src/cli/register-graph.ts +132 -0
- package/src/cli/register-inbox.ts +41 -0
- package/src/cli/register-storage.ts +106 -0
- package/src/cli.ts +30 -750
- package/src/components/AccountSwitcher.tsx +7 -15
- package/src/components/AvatarChip.tsx +1 -1
- package/src/components/AvatarPreload.ts +149 -0
- package/src/components/MarkdownCitations.tsx +680 -0
- package/src/components/MarkdownViewer.tsx +8 -674
- package/src/components/ProfileAnalysisClient.ts +191 -0
- package/src/components/ProfileAnalysisStream.tsx +16 -185
- package/src/components/ProfilePreview.tsx +2 -0
- package/src/components/links-controller.ts +162 -0
- package/src/components/links-model.ts +198 -0
- package/src/components/network-map-controller.ts +84 -0
- package/src/components/network-map-model.ts +255 -0
- package/src/components/useTimelineRouteData.ts +105 -235
- package/src/lib/analysis-runtime.ts +238 -0
- package/src/lib/api-client.ts +16 -215
- package/src/lib/api-contracts.ts +328 -0
- package/src/lib/archive-import-plan.ts +102 -0
- package/src/lib/archive-import.ts +170 -239
- package/src/lib/authored-live.ts +75 -120
- package/src/lib/backup.ts +335 -424
- package/src/lib/blocks-write.ts +30 -26
- package/src/lib/blocks.ts +18 -20
- package/src/lib/database-metrics.ts +88 -0
- package/src/lib/database-migrations.ts +34 -0
- package/src/lib/database-schema.ts +312 -0
- package/src/lib/database-writer.ts +69 -0
- package/src/lib/db.ts +84 -330
- package/src/lib/dm-read-model.ts +533 -0
- package/src/lib/dms-live.ts +34 -97
- package/src/lib/follow-graph.ts +17 -27
- package/src/lib/import-repository.ts +138 -0
- package/src/lib/inbox.ts +2 -1
- package/src/lib/live-sync-engine.ts +209 -0
- package/src/lib/live-transport-gateway.ts +128 -0
- package/src/lib/mention-threads-live.ts +90 -177
- package/src/lib/mentions-export.ts +1 -1
- package/src/lib/mentions-live.ts +57 -181
- package/src/lib/moderation-target.ts +15 -4
- package/src/lib/moderation-write.ts +1 -1
- package/src/lib/mutes-write.ts +30 -26
- package/src/lib/openai-response-runtime.ts +251 -0
- package/src/lib/paginated-sync.ts +93 -0
- package/src/lib/period-digest.ts +116 -304
- package/src/lib/profile-analysis.ts +36 -110
- package/src/lib/queries.ts +6 -2381
- package/src/lib/query-actions.ts +437 -0
- package/src/lib/query-client.tsx +47 -0
- package/src/lib/query-read-model-shared.ts +52 -0
- package/src/lib/query-read-models.ts +5 -0
- package/src/lib/query-resource.ts +41 -0
- package/src/lib/query-status.ts +164 -0
- package/src/lib/research.ts +1 -1
- package/src/lib/runtime-services.ts +20 -0
- package/src/lib/search-discussion.ts +75 -279
- package/src/lib/server-runtime-services.ts +30 -0
- package/src/lib/sqlite.ts +48 -12
- package/src/lib/streaming-ingestion.ts +240 -0
- package/src/lib/sync-cache.ts +6 -1
- package/src/lib/sync-plan.ts +175 -0
- package/src/lib/timeline-collections-live.ts +83 -257
- package/src/lib/timeline-live.ts +86 -236
- package/src/lib/timeline-read-model.ts +1191 -0
- package/src/lib/tweet-repository.ts +156 -0
- package/src/lib/tweet-search-live.ts +63 -167
- package/src/lib/web-sync.ts +67 -50
- package/src/lib/whois.ts +2 -1
- package/src/routes/__root.tsx +11 -8
- package/src/routes/api/action.tsx +1 -1
- package/src/routes/api/conversation.tsx +1 -1
- package/src/routes/api/query.tsx +32 -26
- package/src/routes/api/status.tsx +6 -4
- package/src/routes/api/sync.tsx +5 -2
- package/src/routes/blocks.tsx +97 -131
- package/src/routes/data-sources.tsx +17 -25
- package/src/routes/dms.tsx +167 -184
- package/src/routes/inbox.tsx +63 -57
- package/src/routes/links.tsx +31 -394
- package/src/routes/network-map.tsx +41 -344
- package/src/routes/rate-limits.tsx +17 -21
- package/src/lib/client-cache.ts +0 -109
package/src/lib/period-digest.ts
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import {
|
|
5
|
+
createAnalysisRequestBody,
|
|
6
|
+
type HybridAnalysisResult,
|
|
7
|
+
parseHybridAnalysis,
|
|
8
|
+
readHybridAnalysisStreamEffect,
|
|
9
|
+
resolveAnalysisModelSettings,
|
|
10
|
+
streamHybridAnalysisEffect,
|
|
11
|
+
} from "./analysis-runtime";
|
|
4
12
|
import { maybeAutoSyncBackupEffect } from "./backup";
|
|
5
|
-
import { runEffectPromise
|
|
13
|
+
import { runEffectPromise } from "./effect-runtime";
|
|
6
14
|
import { getLinkInsights } from "./link-insights";
|
|
7
15
|
import { syncMentionThreadsEffect } from "./mention-threads-live";
|
|
8
16
|
import { syncMentionsEffect } from "./mentions-live";
|
|
17
|
+
import { listDmConversations } from "./dm-read-model";
|
|
18
|
+
import { getTweetsByIds, listTimelineItems } from "./timeline-read-model";
|
|
9
19
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from "./queries";
|
|
20
|
+
type OpenAIStreamState,
|
|
21
|
+
processOpenAIResponseSseChunk,
|
|
22
|
+
} from "./openai-response-runtime";
|
|
14
23
|
import { readSyncCache, writeSyncCache } from "./sync-cache";
|
|
15
24
|
import { syncHomeTimelineEffect, type HomeTimelineMode } from "./timeline-live";
|
|
16
25
|
import type { EmbeddedTweet, ProfileRecord, TweetEntities } from "./types";
|
|
@@ -206,19 +215,6 @@ export interface PeriodDigestContext {
|
|
|
206
215
|
hash: string;
|
|
207
216
|
}
|
|
208
217
|
|
|
209
|
-
interface OpenAIStreamState {
|
|
210
|
-
eventBuffer: string;
|
|
211
|
-
rawText: string;
|
|
212
|
-
pendingVisible: string;
|
|
213
|
-
jsonMode: boolean;
|
|
214
|
-
responseId?: string;
|
|
215
|
-
usage?: unknown;
|
|
216
|
-
error?: string;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const DEFAULT_MODEL = "gpt-5.5";
|
|
220
|
-
const DEFAULT_REASONING_EFFORT = "medium";
|
|
221
|
-
const DEFAULT_SERVICE_TIER = "priority";
|
|
222
218
|
const DEFAULT_MAX_TWEETS = 2_500;
|
|
223
219
|
const DEFAULT_MAX_LINKS = 12;
|
|
224
220
|
const DEFAULT_LIVE_TIMELINE_MAX_PAGES = undefined;
|
|
@@ -229,7 +225,6 @@ const DEFAULT_LIVE_THREAD_TIMEOUT_MS = 5_000;
|
|
|
229
225
|
const DEFAULT_DIGEST_FRESHNESS_MS = 5 * 60_000;
|
|
230
226
|
const MAX_PROMPT_DATA_CHARS = 1_200_000;
|
|
231
227
|
const DELIMITER_PATTERN = /\n---\s*\n/;
|
|
232
|
-
const VISIBLE_DELIMITER_HOLD = 8;
|
|
233
228
|
|
|
234
229
|
function toError(error: unknown) {
|
|
235
230
|
return error instanceof Error ? error : new Error(String(error));
|
|
@@ -242,12 +237,6 @@ function tryDigestSync<T>(try_: () => T): Effect.Effect<T, Error> {
|
|
|
242
237
|
});
|
|
243
238
|
}
|
|
244
239
|
|
|
245
|
-
function tryDigestPromise<T>(
|
|
246
|
-
try_: () => PromiseLike<T>,
|
|
247
|
-
): Effect.Effect<T, Error> {
|
|
248
|
-
return tryPromise(try_).pipe(Effect.mapError(toError));
|
|
249
|
-
}
|
|
250
|
-
|
|
251
240
|
function localDateStart(date: Date) {
|
|
252
241
|
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
253
242
|
}
|
|
@@ -622,27 +611,15 @@ function languageFromOptions(options: PeriodDigestOptions) {
|
|
|
622
611
|
}
|
|
623
612
|
|
|
624
613
|
function modelFromOptions(options: PeriodDigestOptions) {
|
|
625
|
-
return options.model
|
|
614
|
+
return resolveAnalysisModelSettings(options).model;
|
|
626
615
|
}
|
|
627
616
|
|
|
628
617
|
function reasoningEffortFromOptions(options: PeriodDigestOptions) {
|
|
629
|
-
return (
|
|
630
|
-
options.reasoningEffort ??
|
|
631
|
-
(process.env.BIRDCLAW_OPENAI_REASONING_EFFORT as
|
|
632
|
-
| PeriodDigestOptions["reasoningEffort"]
|
|
633
|
-
| undefined) ??
|
|
634
|
-
DEFAULT_REASONING_EFFORT
|
|
635
|
-
);
|
|
618
|
+
return resolveAnalysisModelSettings(options).reasoningEffort;
|
|
636
619
|
}
|
|
637
620
|
|
|
638
621
|
function serviceTierFromOptions(options: PeriodDigestOptions) {
|
|
639
|
-
return (
|
|
640
|
-
options.serviceTier ??
|
|
641
|
-
(process.env.BIRDCLAW_OPENAI_SERVICE_TIER as
|
|
642
|
-
| PeriodDigestOptions["serviceTier"]
|
|
643
|
-
| undefined) ??
|
|
644
|
-
DEFAULT_SERVICE_TIER
|
|
645
|
-
);
|
|
622
|
+
return resolveAnalysisModelSettings(options).serviceTier;
|
|
646
623
|
}
|
|
647
624
|
|
|
648
625
|
function boundedPositiveInteger(
|
|
@@ -1150,118 +1127,13 @@ function parseDigestFromHybridText(
|
|
|
1150
1127
|
rawText: string,
|
|
1151
1128
|
language?: string,
|
|
1152
1129
|
): { digest: PeriodDigest; markdown: string } {
|
|
1153
|
-
const
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
);
|
|
1159
|
-
|
|
1160
|
-
try {
|
|
1161
|
-
return {
|
|
1162
|
-
markdown,
|
|
1163
|
-
digest: PeriodDigestSchema.parse(JSON.parse(candidate)),
|
|
1164
|
-
};
|
|
1165
|
-
} catch {
|
|
1166
|
-
return {
|
|
1167
|
-
markdown,
|
|
1168
|
-
digest: fallbackDigest(context, markdown, language),
|
|
1169
|
-
};
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
return { markdown, digest: fallbackDigest(context, markdown, language) };
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
function emitVisibleDelta(
|
|
1176
|
-
state: OpenAIStreamState,
|
|
1177
|
-
delta: string,
|
|
1178
|
-
handlers: PeriodDigestStreamHandlers,
|
|
1179
|
-
) {
|
|
1180
|
-
state.rawText += delta;
|
|
1181
|
-
if (state.jsonMode) return;
|
|
1182
|
-
|
|
1183
|
-
const combined = state.pendingVisible + delta;
|
|
1184
|
-
const delimiterIndex = combined.search(DELIMITER_PATTERN);
|
|
1185
|
-
if (delimiterIndex >= 0) {
|
|
1186
|
-
const visible = combined.slice(0, delimiterIndex);
|
|
1187
|
-
if (visible) {
|
|
1188
|
-
handlers.onDelta?.(visible);
|
|
1189
|
-
handlers.onEvent?.({ type: "delta", delta: visible });
|
|
1190
|
-
}
|
|
1191
|
-
state.pendingVisible = "";
|
|
1192
|
-
state.jsonMode = true;
|
|
1193
|
-
return;
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
if (combined.length <= VISIBLE_DELIMITER_HOLD) {
|
|
1197
|
-
state.pendingVisible = combined;
|
|
1198
|
-
return;
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
const visible = combined.slice(0, -VISIBLE_DELIMITER_HOLD);
|
|
1202
|
-
state.pendingVisible = combined.slice(-VISIBLE_DELIMITER_HOLD);
|
|
1203
|
-
if (visible) {
|
|
1204
|
-
handlers.onDelta?.(visible);
|
|
1205
|
-
handlers.onEvent?.({ type: "delta", delta: visible });
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
function flushPendingVisible(
|
|
1210
|
-
state: OpenAIStreamState,
|
|
1211
|
-
handlers: PeriodDigestStreamHandlers,
|
|
1212
|
-
) {
|
|
1213
|
-
if (state.jsonMode || !state.pendingVisible) return;
|
|
1214
|
-
const delta = state.pendingVisible;
|
|
1215
|
-
state.pendingVisible = "";
|
|
1216
|
-
handlers.onDelta?.(delta);
|
|
1217
|
-
handlers.onEvent?.({ type: "delta", delta });
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
function handleOpenAIEvent(
|
|
1221
|
-
state: OpenAIStreamState,
|
|
1222
|
-
event: Record<string, unknown>,
|
|
1223
|
-
handlers: PeriodDigestStreamHandlers,
|
|
1224
|
-
) {
|
|
1225
|
-
const type = typeof event.type === "string" ? event.type : "";
|
|
1226
|
-
if (
|
|
1227
|
-
type === "response.output_text.delta" &&
|
|
1228
|
-
typeof event.delta === "string"
|
|
1229
|
-
) {
|
|
1230
|
-
emitVisibleDelta(state, event.delta, handlers);
|
|
1231
|
-
return;
|
|
1232
|
-
}
|
|
1233
|
-
if (type === "response.completed") {
|
|
1234
|
-
const response = event.response;
|
|
1235
|
-
if (response && typeof response === "object") {
|
|
1236
|
-
const record = response as Record<string, unknown>;
|
|
1237
|
-
state.responseId = typeof record.id === "string" ? record.id : undefined;
|
|
1238
|
-
state.usage = record.usage;
|
|
1239
|
-
}
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
if (type === "response.error" || type === "error") {
|
|
1243
|
-
const error = event.error;
|
|
1244
|
-
state.error =
|
|
1245
|
-
error && typeof error === "object" && "message" in error
|
|
1246
|
-
? String((error as { message?: unknown }).message)
|
|
1247
|
-
: "OpenAI stream failed";
|
|
1248
|
-
return;
|
|
1249
|
-
}
|
|
1250
|
-
if (type === "response.failed" || type === "response.incomplete") {
|
|
1251
|
-
const response = event.response;
|
|
1252
|
-
const record =
|
|
1253
|
-
response && typeof response === "object"
|
|
1254
|
-
? (response as Record<string, unknown>)
|
|
1255
|
-
: {};
|
|
1256
|
-
const error = record.error;
|
|
1257
|
-
const incomplete = record.incomplete_details;
|
|
1258
|
-
state.error =
|
|
1259
|
-
error && typeof error === "object" && "message" in error
|
|
1260
|
-
? String((error as { message?: unknown }).message)
|
|
1261
|
-
: incomplete && typeof incomplete === "object" && "reason" in incomplete
|
|
1262
|
-
? `OpenAI response incomplete: ${String((incomplete as { reason?: unknown }).reason)}`
|
|
1263
|
-
: "OpenAI stream failed";
|
|
1264
|
-
}
|
|
1130
|
+
const parsed = parseHybridAnalysis({
|
|
1131
|
+
rawText,
|
|
1132
|
+
parse: (value) => PeriodDigestSchema.parse(value),
|
|
1133
|
+
fallback: (markdown) => fallbackDigest(context, markdown, language),
|
|
1134
|
+
delimiterPattern: DELIMITER_PATTERN,
|
|
1135
|
+
});
|
|
1136
|
+
return { markdown: parsed.markdown, digest: parsed.value };
|
|
1265
1137
|
}
|
|
1266
1138
|
|
|
1267
1139
|
function processSseChunk(
|
|
@@ -1269,147 +1141,102 @@ function processSseChunk(
|
|
|
1269
1141
|
chunk: string,
|
|
1270
1142
|
handlers: PeriodDigestStreamHandlers,
|
|
1271
1143
|
) {
|
|
1272
|
-
state
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
.filter((line) => line.startsWith("data:"))
|
|
1280
|
-
.map((line) => line.slice(5).trimStart())
|
|
1281
|
-
.join("\n");
|
|
1282
|
-
if (data && data !== "[DONE]") {
|
|
1283
|
-
try {
|
|
1284
|
-
handleOpenAIEvent(
|
|
1285
|
-
state,
|
|
1286
|
-
JSON.parse(data) as Record<string, unknown>,
|
|
1287
|
-
handlers,
|
|
1288
|
-
);
|
|
1289
|
-
} catch {
|
|
1290
|
-
// Ignore malformed event frames; the final JSON parse will decide result quality.
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
boundary = state.eventBuffer.indexOf("\n\n");
|
|
1294
|
-
}
|
|
1144
|
+
processOpenAIResponseSseChunk(state, chunk, {
|
|
1145
|
+
delimiterPattern: DELIMITER_PATTERN,
|
|
1146
|
+
onDelta: (delta) => {
|
|
1147
|
+
handlers.onDelta?.(delta);
|
|
1148
|
+
handlers.onEvent?.({ type: "delta", delta });
|
|
1149
|
+
},
|
|
1150
|
+
});
|
|
1295
1151
|
}
|
|
1296
1152
|
|
|
1297
1153
|
function createOpenAIRequestBody(
|
|
1298
1154
|
context: PeriodDigestContext,
|
|
1299
1155
|
options: PeriodDigestOptions,
|
|
1300
1156
|
) {
|
|
1301
|
-
return {
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1157
|
+
return createAnalysisRequestBody({
|
|
1158
|
+
settings: resolveAnalysisModelSettings(options),
|
|
1159
|
+
system:
|
|
1160
|
+
"You are a precise local Twitter archive analyst. Stream Markdown first, then emit the requested JSON object after the delimiter. Do not invent events not present in the dataset.",
|
|
1161
|
+
prompt: buildPrompt(context, {
|
|
1162
|
+
language: languageFromOptions(options),
|
|
1163
|
+
}),
|
|
1306
1164
|
stream: true,
|
|
1307
|
-
|
|
1308
|
-
input: [
|
|
1309
|
-
{
|
|
1310
|
-
role: "system",
|
|
1311
|
-
content:
|
|
1312
|
-
"You are a precise local Twitter archive analyst. Stream Markdown first, then emit the requested JSON object after the delimiter. Do not invent events not present in the dataset.",
|
|
1313
|
-
},
|
|
1314
|
-
{
|
|
1315
|
-
role: "user",
|
|
1316
|
-
content: buildPrompt(context, {
|
|
1317
|
-
language: languageFromOptions(options),
|
|
1318
|
-
}),
|
|
1319
|
-
},
|
|
1320
|
-
],
|
|
1321
|
-
};
|
|
1165
|
+
});
|
|
1322
1166
|
}
|
|
1323
1167
|
|
|
1324
|
-
function
|
|
1325
|
-
|
|
1168
|
+
function completeOpenAIStreamEffect(
|
|
1169
|
+
stream: HybridAnalysisResult<PeriodDigest>,
|
|
1326
1170
|
context: PeriodDigestContext,
|
|
1327
1171
|
options: PeriodDigestOptions,
|
|
1328
1172
|
handlers: PeriodDigestStreamHandlers,
|
|
1329
1173
|
): Effect.Effect<PeriodDigestRunResult, Error> {
|
|
1330
|
-
const reader = response.body?.getReader();
|
|
1331
|
-
if (!reader) {
|
|
1332
|
-
return Effect.fail(new Error("OpenAI response did not include a stream"));
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
const decoder = new TextDecoder();
|
|
1336
|
-
const state: OpenAIStreamState = {
|
|
1337
|
-
eventBuffer: "",
|
|
1338
|
-
rawText: "",
|
|
1339
|
-
pendingVisible: "",
|
|
1340
|
-
jsonMode: false,
|
|
1341
|
-
};
|
|
1342
|
-
|
|
1343
1174
|
return Effect.gen(function* () {
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
continue;
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
flushPendingVisible(state, handlers);
|
|
1356
|
-
if (state.error) {
|
|
1357
|
-
return yield* Effect.fail(new Error(state.error));
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
const parsed = yield* tryDigestSync(() =>
|
|
1361
|
-
parseDigestFromHybridText(
|
|
1362
|
-
context,
|
|
1363
|
-
state.rawText,
|
|
1364
|
-
languageFromOptions(options),
|
|
1365
|
-
),
|
|
1366
|
-
);
|
|
1367
|
-
const enrichedContext = yield* tryDigestSync(() =>
|
|
1368
|
-
enrichContextWithCitedTweets(context, parsed.digest),
|
|
1369
|
-
);
|
|
1370
|
-
const cacheKey = digestCacheKey(context, options);
|
|
1371
|
-
const updatedAt = yield* tryDigestSync(() =>
|
|
1372
|
-
writeSyncCache(cacheKey, {
|
|
1373
|
-
digest: parsed.digest,
|
|
1374
|
-
markdown: parsed.markdown,
|
|
1375
|
-
model: modelFromOptions(options),
|
|
1376
|
-
reasoningEffort: reasoningEffortFromOptions(options),
|
|
1377
|
-
serviceTier: serviceTierFromOptions(options),
|
|
1378
|
-
usage: state.usage,
|
|
1379
|
-
responseId: state.responseId,
|
|
1380
|
-
}),
|
|
1381
|
-
);
|
|
1382
|
-
const result: PeriodDigestRunResult = {
|
|
1383
|
-
context: enrichedContext,
|
|
1384
|
-
digest: parsed.digest,
|
|
1385
|
-
markdown: parsed.markdown,
|
|
1175
|
+
const enrichedContext = yield* tryDigestSync(() =>
|
|
1176
|
+
enrichContextWithCitedTweets(context, stream.value),
|
|
1177
|
+
);
|
|
1178
|
+
const cacheKey = digestCacheKey(context, options);
|
|
1179
|
+
const updatedAt = yield* tryDigestSync(() =>
|
|
1180
|
+
writeSyncCache(cacheKey, {
|
|
1181
|
+
digest: stream.value,
|
|
1182
|
+
markdown: stream.markdown,
|
|
1386
1183
|
model: modelFromOptions(options),
|
|
1387
1184
|
reasoningEffort: reasoningEffortFromOptions(options),
|
|
1388
1185
|
serviceTier: serviceTierFromOptions(options),
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
};
|
|
1392
|
-
yield* tryDigestSync(() =>
|
|
1393
|
-
writeSyncCache(latestDigestCacheKey(options), {
|
|
1394
|
-
context: result.context,
|
|
1395
|
-
digest: result.digest,
|
|
1396
|
-
markdown: result.markdown,
|
|
1397
|
-
model: result.model,
|
|
1398
|
-
reasoningEffort: result.reasoningEffort,
|
|
1399
|
-
serviceTier: result.serviceTier,
|
|
1400
|
-
updatedAt: result.updatedAt,
|
|
1401
|
-
}),
|
|
1402
|
-
);
|
|
1403
|
-
handlers.onEvent?.({ type: "done", result });
|
|
1404
|
-
return result;
|
|
1405
|
-
}
|
|
1406
|
-
}).pipe(
|
|
1407
|
-
Effect.ensuring(
|
|
1408
|
-
Effect.sync(() => {
|
|
1409
|
-
reader.releaseLock();
|
|
1186
|
+
usage: stream.usage,
|
|
1187
|
+
responseId: stream.responseId,
|
|
1410
1188
|
}),
|
|
1411
|
-
)
|
|
1412
|
-
|
|
1189
|
+
);
|
|
1190
|
+
const result: PeriodDigestRunResult = {
|
|
1191
|
+
context: enrichedContext,
|
|
1192
|
+
digest: stream.value,
|
|
1193
|
+
markdown: stream.markdown,
|
|
1194
|
+
model: modelFromOptions(options),
|
|
1195
|
+
reasoningEffort: reasoningEffortFromOptions(options),
|
|
1196
|
+
serviceTier: serviceTierFromOptions(options),
|
|
1197
|
+
cached: false,
|
|
1198
|
+
updatedAt,
|
|
1199
|
+
};
|
|
1200
|
+
yield* tryDigestSync(() =>
|
|
1201
|
+
writeSyncCache(latestDigestCacheKey(options), {
|
|
1202
|
+
context: result.context,
|
|
1203
|
+
digest: result.digest,
|
|
1204
|
+
markdown: result.markdown,
|
|
1205
|
+
model: result.model,
|
|
1206
|
+
reasoningEffort: result.reasoningEffort,
|
|
1207
|
+
serviceTier: result.serviceTier,
|
|
1208
|
+
updatedAt: result.updatedAt,
|
|
1209
|
+
}),
|
|
1210
|
+
);
|
|
1211
|
+
handlers.onEvent?.({ type: "done", result });
|
|
1212
|
+
return result;
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
function readOpenAIStreamEffect(
|
|
1217
|
+
response: Response,
|
|
1218
|
+
context: PeriodDigestContext,
|
|
1219
|
+
options: PeriodDigestOptions,
|
|
1220
|
+
handlers: PeriodDigestStreamHandlers,
|
|
1221
|
+
): Effect.Effect<PeriodDigestRunResult, Error> {
|
|
1222
|
+
return Effect.gen(function* () {
|
|
1223
|
+
const stream = yield* readHybridAnalysisStreamEffect(response, {
|
|
1224
|
+
parse: (value) => PeriodDigestSchema.parse(value),
|
|
1225
|
+
fallback: (markdown) =>
|
|
1226
|
+
fallbackDigest(context, markdown, languageFromOptions(options)),
|
|
1227
|
+
delimiterPattern: DELIMITER_PATTERN,
|
|
1228
|
+
onDelta: (delta) => {
|
|
1229
|
+
handlers.onDelta?.(delta);
|
|
1230
|
+
handlers.onEvent?.({ type: "delta", delta });
|
|
1231
|
+
},
|
|
1232
|
+
});
|
|
1233
|
+
return yield* completeOpenAIStreamEffect(
|
|
1234
|
+
stream,
|
|
1235
|
+
context,
|
|
1236
|
+
options,
|
|
1237
|
+
handlers,
|
|
1238
|
+
);
|
|
1239
|
+
});
|
|
1413
1240
|
}
|
|
1414
1241
|
|
|
1415
1242
|
export function streamPeriodDigestEffect(
|
|
@@ -1494,37 +1321,22 @@ export function streamPeriodDigestEffect(
|
|
|
1494
1321
|
);
|
|
1495
1322
|
cacheKey = digestCacheKey(context, resolvedOptions);
|
|
1496
1323
|
|
|
1497
|
-
const apiKey = process.env.OPENAI_API_KEY;
|
|
1498
|
-
if (!apiKey) {
|
|
1499
|
-
return yield* Effect.fail(new Error("OPENAI_API_KEY is not set"));
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
1324
|
handlers.onEvent?.({ type: "start", context, cached: false });
|
|
1503
1325
|
emitDigestStatus(handlers, "Streaming AI summary");
|
|
1504
|
-
const
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
new Error(
|
|
1519
|
-
`OpenAI request failed: ${String(response.status)} ${text.slice(
|
|
1520
|
-
0,
|
|
1521
|
-
400,
|
|
1522
|
-
)}`,
|
|
1523
|
-
),
|
|
1524
|
-
);
|
|
1525
|
-
}
|
|
1526
|
-
return yield* readOpenAIStreamEffect(
|
|
1527
|
-
response,
|
|
1326
|
+
const stream = yield* streamHybridAnalysisEffect({
|
|
1327
|
+
body: createOpenAIRequestBody(context, resolvedOptions),
|
|
1328
|
+
signal: resolvedOptions.signal,
|
|
1329
|
+
parse: (value) => PeriodDigestSchema.parse(value),
|
|
1330
|
+
fallback: (markdown) =>
|
|
1331
|
+
fallbackDigest(context, markdown, languageFromOptions(resolvedOptions)),
|
|
1332
|
+
delimiterPattern: DELIMITER_PATTERN,
|
|
1333
|
+
onDelta: (delta) => {
|
|
1334
|
+
handlers.onDelta?.(delta);
|
|
1335
|
+
handlers.onEvent?.({ type: "delta", delta });
|
|
1336
|
+
},
|
|
1337
|
+
});
|
|
1338
|
+
return yield* completeOpenAIStreamEffect(
|
|
1339
|
+
stream,
|
|
1528
1340
|
context,
|
|
1529
1341
|
resolvedOptions,
|
|
1530
1342
|
handlers,
|