@bitkyc08/opencodex 2.48.0-preview.20260908 → 2.49.0
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/README.md +8 -5
- package/SPONSORS.md +1 -1
- package/assets/sponsors/orcarouter.png +0 -0
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/index-BoBRSehJ.css +1 -0
- package/gui/dist/assets/index-BtyONQrZ.js +115 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/packycode.svg +19 -0
- package/gui/dist/provider-icons/qoder.svg +5 -0
- package/package.json +5 -3
- package/src/adapters/anthropic.ts +31 -16
- package/src/adapters/codebuddy/adapter.ts +85 -0
- package/src/adapters/codebuddy/profiles.ts +52 -0
- package/src/adapters/coding-agent/profile.ts +100 -0
- package/src/adapters/coding-agent/protocol.ts +463 -0
- package/src/adapters/coding-agent/turn.ts +353 -0
- package/src/adapters/google.ts +15 -11
- package/src/adapters/mimo-free.ts +3 -0
- package/src/adapters/openai-chat.ts +2 -2
- package/src/adapters/openai-responses.ts +18 -11
- package/src/adapters/qoder/adapter.ts +70 -0
- package/src/adapters/qoder/live-models.ts +89 -0
- package/src/adapters/qoder/profiles.ts +36 -0
- package/src/adapters/registry.ts +12 -0
- package/src/adapters/responses-tool-schema.ts +113 -8
- package/src/cli/dispatch.ts +2 -0
- package/src/cli/doctor.ts +40 -0
- package/src/cli/effort.ts +24 -8
- package/src/cli/help.ts +2 -0
- package/src/cli/index.ts +24 -1
- package/src/cli/provider-runtime.ts +2 -1
- package/src/cli/registry.ts +2 -2
- package/src/cli/system-command.ts +10 -3
- package/src/cli/usage-report.ts +9 -5
- package/src/codex/account-lifecycle.ts +35 -2
- package/src/codex/auth-api.ts +6 -3
- package/src/codex/auth-collision.ts +12 -2
- package/src/codex/auth-context.ts +75 -7
- package/src/codex/catalog/provider-fetch.ts +73 -9
- package/src/codex/inject.ts +127 -30
- package/src/codex/internal/catalog-writer.ts +3 -0
- package/src/codex/journal.ts +61 -12
- package/src/codex/model-cache.ts +11 -4
- package/src/codex/native-profile-startup.ts +72 -5
- package/src/codex/native-profile-store.ts +2 -2
- package/src/codex/ocx-compaction-history.ts +226 -0
- package/src/codex/project-config-warnings.ts +3 -1
- package/src/codex/quota.ts +18 -8
- package/src/codex/reserve-availability.ts +21 -5
- package/src/codex/runtime.ts +45 -1
- package/src/codex/sync.ts +5 -0
- package/src/config.ts +1 -0
- package/src/generated/compatibility-version.json +126 -74
- package/src/images/loop.ts +1 -0
- package/src/images/xai-video-client.ts +2 -0
- package/src/integrations/registry.ts +1 -0
- package/src/lib/process-control.ts +52 -8
- package/src/lib/upstream-retry.ts +1 -0
- package/src/oauth/chatgpt.ts +83 -0
- package/src/oauth/xai.ts +151 -8
- package/src/providers/api-key-selection-capture.ts +10 -0
- package/src/providers/api-key-selection.ts +2 -7
- package/src/providers/caller-authorization.ts +36 -0
- package/src/providers/codebuddy-models.ts +184 -0
- package/src/providers/derive.ts +5 -0
- package/src/providers/free-directory.ts +26 -2
- package/src/providers/openai-sidecar.ts +35 -11
- package/src/providers/qoder-models.ts +25 -0
- package/src/providers/registry.ts +139 -0
- package/src/responses/compaction.ts +4 -0
- package/src/responses/task-input.ts +21 -1
- package/src/router.ts +1 -1
- package/src/server/chat-completions.ts +26 -9
- package/src/server/claude-messages.ts +12 -2
- package/src/server/images.ts +1 -0
- package/src/server/management/account-selection-stream.ts +13 -4
- package/src/server/management/config-routes.ts +24 -5
- package/src/server/management/logs-usage-routes.ts +1 -0
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/provider-routes.ts +24 -0
- package/src/server/management-api.ts +14 -0
- package/src/server/port-reclaim.ts +11 -26
- package/src/server/request-log.ts +6 -0
- package/src/server/responses/collaboration.ts +4 -9
- package/src/server/responses/core.ts +141 -19
- package/src/server/responses/fetch-helpers.ts +18 -7
- package/src/server/responses/policy-fallback.ts +12 -0
- package/src/service.ts +36 -2
- package/src/storage/cleanup.ts +77 -45
- package/src/types/config.ts +7 -1
- package/src/usage/log.ts +24 -0
- package/src/vision/anthropic-describe.ts +1 -0
- package/src/web-search/anthropic-executor.ts +1 -0
- package/src/web-search/loop.ts +1 -0
- package/src/web-search/progress-stream.ts +4 -0
- package/gui/dist/assets/index-B6EFoatP.js +0 -115
- package/gui/dist/assets/index-D5SiRo8X.css +0 -1
package/src/storage/cleanup.ts
CHANGED
|
@@ -96,6 +96,7 @@ export interface CleanupResult {
|
|
|
96
96
|
trashDir?: string;
|
|
97
97
|
error?: CleanupErrorCode;
|
|
98
98
|
removedPaths: string[];
|
|
99
|
+
skippedReferencedPaths?: string[];
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
const STATE_DB_FILE = /^state_(\d+)\.sqlite$/;
|
|
@@ -684,52 +685,59 @@ function loadMatchingThreads(db: Database, candidates: ArchivedCandidate[], code
|
|
|
684
685
|
}
|
|
685
686
|
|
|
686
687
|
/**
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
* Throws real DB errors (busy/corruption) so callers can refuse cleanup.
|
|
688
|
+
* Partition matched threads into deletable and referenced snapshots. Linked spawn/fork
|
|
689
|
+
* history and paginated histories stay in the skipped set. Throws real DB errors.
|
|
690
690
|
*/
|
|
691
|
-
function
|
|
691
|
+
function filterReferencedHistory(
|
|
692
692
|
db: Database,
|
|
693
693
|
threads: ThreadSnapshot[],
|
|
694
|
-
):
|
|
695
|
-
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
694
|
+
): { safe: ThreadSnapshot[]; skipped: ThreadSnapshot[] } {
|
|
695
|
+
let safe = threads.filter(t => (t.history_mode ?? "").toLowerCase() !== "paginated");
|
|
696
|
+
const skipped = new Map(threads
|
|
697
|
+
.filter(t => (t.history_mode ?? "").toLowerCase() === "paginated")
|
|
698
|
+
.map(t => [t.id, t]));
|
|
699
|
+
|
|
700
|
+
while (safe.length > 0) {
|
|
701
|
+
const idSet = new Set(safe.map(t => t.id));
|
|
702
|
+
const unsafeIds = new Set<string>();
|
|
703
|
+
|
|
704
|
+
// Spawn edges that cross the delete boundary keep history reachable.
|
|
705
|
+
if (tableExists(db, "thread_spawn_edges")) {
|
|
706
|
+
for (const chunk of chunkIds([...idSet], SQLITE_ID_CHUNK)) {
|
|
707
707
|
const placeholders = chunk.map(() => "?").join(",");
|
|
708
708
|
const edges = db.query<{ parent_thread_id: string; child_thread_id: string }, string[]>(
|
|
709
709
|
`SELECT parent_thread_id, child_thread_id FROM thread_spawn_edges
|
|
710
710
|
WHERE parent_thread_id IN (${placeholders}) OR child_thread_id IN (${placeholders})`,
|
|
711
711
|
).all(...chunk, ...chunk);
|
|
712
712
|
for (const edge of edges) {
|
|
713
|
-
if (!idSet.has(edge.parent_thread_id)
|
|
714
|
-
|
|
713
|
+
if (!idSet.has(edge.parent_thread_id)) unsafeIds.add(edge.child_thread_id);
|
|
714
|
+
if (!idSet.has(edge.child_thread_id)) unsafeIds.add(edge.parent_thread_id);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Other threads that list one of ours as forked_from / parent (when columns exist).
|
|
720
|
+
for (const column of ["forked_from_id", "parent_thread_id", "source_thread_id"] as const) {
|
|
721
|
+
if (!columnExists(db, "threads", column)) continue;
|
|
722
|
+
for (const chunk of chunkIds([...idSet], SQLITE_ID_CHUNK * 2)) {
|
|
723
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
724
|
+
const rows = db.query<{ id: string; ref: string }, string[]>(
|
|
725
|
+
`SELECT id, ${column} AS ref FROM threads WHERE ${column} IN (${placeholders})`,
|
|
726
|
+
).all(...chunk);
|
|
727
|
+
for (const row of rows) {
|
|
728
|
+
if (!idSet.has(row.id)) unsafeIds.add(row.ref);
|
|
715
729
|
}
|
|
716
730
|
}
|
|
717
731
|
}
|
|
718
|
-
}
|
|
719
732
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
for (const chunk of chunkIds(ids, SQLITE_ID_CHUNK * 2)) {
|
|
724
|
-
const placeholders = chunk.map(() => "?").join(",");
|
|
725
|
-
const rows = db.query<{ id: string }, string[]>(
|
|
726
|
-
`SELECT id FROM threads WHERE ${column} IN (${placeholders})`,
|
|
727
|
-
).all(...chunk);
|
|
728
|
-
if (rows.some(r => !idSet.has(r.id))) return true;
|
|
733
|
+
if (unsafeIds.size === 0) break;
|
|
734
|
+
for (const thread of safe) {
|
|
735
|
+
if (unsafeIds.has(thread.id)) skipped.set(thread.id, thread);
|
|
729
736
|
}
|
|
737
|
+
safe = safe.filter(thread => !unsafeIds.has(thread.id));
|
|
730
738
|
}
|
|
731
739
|
|
|
732
|
-
return
|
|
740
|
+
return { safe, skipped: [...skipped.values()] };
|
|
733
741
|
}
|
|
734
742
|
|
|
735
743
|
function tableExists(db: Database, name: string): boolean {
|
|
@@ -778,6 +786,7 @@ function deleteThreadsAndDependents(db: Database, threadIds: string[]): void {
|
|
|
778
786
|
interface ReconcileOk {
|
|
779
787
|
ok: true;
|
|
780
788
|
threads: ThreadSnapshot[];
|
|
789
|
+
skipped: ThreadSnapshot[];
|
|
781
790
|
}
|
|
782
791
|
interface ReconcileErr {
|
|
783
792
|
ok: false;
|
|
@@ -1463,14 +1472,14 @@ function withWritableDb(
|
|
|
1463
1472
|
}
|
|
1464
1473
|
}
|
|
1465
1474
|
|
|
1466
|
-
/** Load matching archived threads and
|
|
1475
|
+
/** Load matching archived threads and retain referenced history — no deletes yet. */
|
|
1467
1476
|
function loadThreadsForCleanup(
|
|
1468
1477
|
stateDbPath: string,
|
|
1469
1478
|
candidates: ArchivedCandidate[],
|
|
1470
1479
|
codexHome: string,
|
|
1471
1480
|
busyTimeoutMs: number,
|
|
1472
1481
|
): ReconcileOk | ReconcileErr {
|
|
1473
|
-
if (!stateDbPath || !existsSync(stateDbPath)) return { ok: true, threads: [] };
|
|
1482
|
+
if (!stateDbPath || !existsSync(stateDbPath)) return { ok: true, threads: [], skipped: [] };
|
|
1474
1483
|
let db: Database | undefined;
|
|
1475
1484
|
try {
|
|
1476
1485
|
db = openDbWritable(stateDbPath, busyTimeoutMs);
|
|
@@ -1478,10 +1487,8 @@ function loadThreadsForCleanup(
|
|
|
1478
1487
|
if (threads.some(t => Number(t.is_pinned ?? 0) === 1)) {
|
|
1479
1488
|
return { ok: false, error: "pinned_thread" };
|
|
1480
1489
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
}
|
|
1484
|
-
return { ok: true, threads };
|
|
1490
|
+
const filtered = filterReferencedHistory(db, threads);
|
|
1491
|
+
return { ok: true, threads: filtered.safe, skipped: filtered.skipped };
|
|
1485
1492
|
} catch (error) {
|
|
1486
1493
|
return { ok: false, error: mapDbError(error) };
|
|
1487
1494
|
} finally {
|
|
@@ -1504,7 +1511,7 @@ function reconcileDeletedThreads(
|
|
|
1504
1511
|
stageDir: string,
|
|
1505
1512
|
hooks?: ReconcileTestHooks,
|
|
1506
1513
|
): ReconcileOk | ReconcileErr {
|
|
1507
|
-
if (!paths.state || !existsSync(paths.state)) return { ok: true, threads: [] };
|
|
1514
|
+
if (!paths.state || !existsSync(paths.state)) return { ok: true, threads: [], skipped: [] };
|
|
1508
1515
|
|
|
1509
1516
|
if (hooks?.beforeReconcileLock) hooks.beforeReconcileLock();
|
|
1510
1517
|
|
|
@@ -1546,7 +1553,7 @@ function reconcileDeletedThreads(
|
|
|
1546
1553
|
stateDb.exec("ROLLBACK");
|
|
1547
1554
|
return { ok: false, error: "pinned_thread" };
|
|
1548
1555
|
}
|
|
1549
|
-
if (
|
|
1556
|
+
if (filterReferencedHistory(stateDb, threads).safe.length !== threads.length) {
|
|
1550
1557
|
stateDb.exec("ROLLBACK");
|
|
1551
1558
|
return { ok: false, error: "referenced_history" };
|
|
1552
1559
|
}
|
|
@@ -1582,7 +1589,7 @@ function reconcileDeletedThreads(
|
|
|
1582
1589
|
if (hooks?.afterSatelliteMutations) hooks.afterSatelliteMutations();
|
|
1583
1590
|
|
|
1584
1591
|
// Re-check under the same lock before committing state deletes.
|
|
1585
|
-
if (
|
|
1592
|
+
if (filterReferencedHistory(stateDb, threads).safe.length !== threads.length) {
|
|
1586
1593
|
stateDb.exec("ROLLBACK");
|
|
1587
1594
|
return failWithRestore("referenced_history");
|
|
1588
1595
|
}
|
|
@@ -1590,7 +1597,7 @@ function reconcileDeletedThreads(
|
|
|
1590
1597
|
if (hooks?.failBeforeStateCommit) throw new Error("test_fail_before_state_commit");
|
|
1591
1598
|
stateDb.exec("COMMIT");
|
|
1592
1599
|
// Keep satellite-backup.json for quarantine restore; permanent purge removes the stage.
|
|
1593
|
-
return { ok: true, threads };
|
|
1600
|
+
return { ok: true, threads, skipped: [] };
|
|
1594
1601
|
} catch (error) {
|
|
1595
1602
|
if (satelliteLocks) rollbackAllSatelliteLocks(satelliteLocks);
|
|
1596
1603
|
throw error;
|
|
@@ -1895,7 +1902,30 @@ export function executeArchivedCleanup(options: ExecuteCleanupOptions): CleanupR
|
|
|
1895
1902
|
const normalized = normalizeArchivedRolloutPath(thread.rollout_path, codexHome);
|
|
1896
1903
|
if (normalized) threadByRelPath.set(normalized, thread);
|
|
1897
1904
|
}
|
|
1898
|
-
const
|
|
1905
|
+
const skippedReferencedPaths = loaded.skipped
|
|
1906
|
+
.map(thread => normalizeArchivedRolloutPath(thread.rollout_path, codexHome))
|
|
1907
|
+
.filter((path): path is string => path !== null);
|
|
1908
|
+
const matchedPaths = new Set([
|
|
1909
|
+
...threadByRelPath.keys(),
|
|
1910
|
+
...skippedReferencedPaths,
|
|
1911
|
+
]);
|
|
1912
|
+
const candidates = preview.candidates.filter(candidate => {
|
|
1913
|
+
return !matchedPaths.has(candidate.relPath) || threadByRelPath.has(candidate.relPath);
|
|
1914
|
+
});
|
|
1915
|
+
if (candidates.length === 0) {
|
|
1916
|
+
removeStageIfEmpty(stageDir, []);
|
|
1917
|
+
removeEmptyTrashRoot(codexHome);
|
|
1918
|
+
return {
|
|
1919
|
+
ok: true,
|
|
1920
|
+
mode,
|
|
1921
|
+
percent,
|
|
1922
|
+
count: 0,
|
|
1923
|
+
bytes: 0,
|
|
1924
|
+
removedPaths: [],
|
|
1925
|
+
...(skippedReferencedPaths.length ? { skippedReferencedPaths } : {}),
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
const manifestEntries: CleanupManifestEntry[] = candidates.map(candidate => {
|
|
1899
1929
|
const thread = threadByRelPath.get(candidate.relPath);
|
|
1900
1930
|
return {
|
|
1901
1931
|
relPath: candidate.relPath,
|
|
@@ -1936,7 +1966,7 @@ export function executeArchivedCleanup(options: ExecuteCleanupOptions): CleanupR
|
|
|
1936
1966
|
return fail(mode, percent, "fs_failed");
|
|
1937
1967
|
}
|
|
1938
1968
|
|
|
1939
|
-
const stageResult = stageCandidates(codexHome,
|
|
1969
|
+
const stageResult = stageCandidates(codexHome, candidates, stageDir, {
|
|
1940
1970
|
blockDestBasenames: blockStageDest.size > 0 ? blockStageDest : undefined,
|
|
1941
1971
|
});
|
|
1942
1972
|
if (!stageResult.ok) {
|
|
@@ -1956,7 +1986,7 @@ export function executeArchivedCleanup(options: ExecuteCleanupOptions): CleanupR
|
|
|
1956
1986
|
|
|
1957
1987
|
const deleted = reconcileDeletedThreads(
|
|
1958
1988
|
paths,
|
|
1959
|
-
|
|
1989
|
+
candidates,
|
|
1960
1990
|
codexHome,
|
|
1961
1991
|
busyTimeoutMs,
|
|
1962
1992
|
stageDir,
|
|
@@ -1973,8 +2003,8 @@ export function executeArchivedCleanup(options: ExecuteCleanupOptions): CleanupR
|
|
|
1973
2003
|
return fail(mode, percent, deleted.error, keepTrash ? { trashDir } : undefined);
|
|
1974
2004
|
}
|
|
1975
2005
|
|
|
1976
|
-
const removedPaths =
|
|
1977
|
-
const bytes =
|
|
2006
|
+
const removedPaths = candidates.map(c => c.relPath);
|
|
2007
|
+
const bytes = candidates.reduce((sum, c) => sum + c.bytes, 0);
|
|
1978
2008
|
|
|
1979
2009
|
if (mode === "quarantine") {
|
|
1980
2010
|
return {
|
|
@@ -1985,6 +2015,7 @@ export function executeArchivedCleanup(options: ExecuteCleanupOptions): CleanupR
|
|
|
1985
2015
|
bytes,
|
|
1986
2016
|
trashDir,
|
|
1987
2017
|
removedPaths,
|
|
2018
|
+
...(skippedReferencedPaths.length ? { skippedReferencedPaths } : {}),
|
|
1988
2019
|
};
|
|
1989
2020
|
}
|
|
1990
2021
|
|
|
@@ -2038,6 +2069,7 @@ export function executeArchivedCleanup(options: ExecuteCleanupOptions): CleanupR
|
|
|
2038
2069
|
count: removedPaths.length,
|
|
2039
2070
|
bytes,
|
|
2040
2071
|
removedPaths,
|
|
2072
|
+
...(skippedReferencedPaths.length ? { skippedReferencedPaths } : {}),
|
|
2041
2073
|
};
|
|
2042
2074
|
}
|
|
2043
2075
|
|
package/src/types/config.ts
CHANGED
|
@@ -693,6 +693,12 @@ export interface OcxConfig {
|
|
|
693
693
|
* non-loopback binds, whose admission token contract is unchanged.
|
|
694
694
|
*/
|
|
695
695
|
codexDesktopAuthless?: boolean;
|
|
696
|
+
/**
|
|
697
|
+
* Opt into Codex-owned client compaction while keeping OpenCodex routing. On an authenticated
|
|
698
|
+
* loopback bind, inject the dedicated `opencodex` model provider instead of overriding the
|
|
699
|
+
* built-in `openai` provider, so Codex does not select native remote compaction. Default off.
|
|
700
|
+
*/
|
|
701
|
+
codexClientCompaction?: boolean;
|
|
696
702
|
/**
|
|
697
703
|
* Compatibility mode: temporarily rewrite Codex resume-history metadata while the proxy is active
|
|
698
704
|
* so Codex App can show old OpenAI chats and opencodex-created exec chats under its default
|
|
@@ -753,7 +759,7 @@ export interface OcxConfig {
|
|
|
753
759
|
*/
|
|
754
760
|
codexAccountPickerEnabled?: boolean;
|
|
755
761
|
/**
|
|
756
|
-
* Show the GPT-5.3-Codex-Spark weekly
|
|
762
|
+
* Show the GPT-5.3-Codex-Spark 5-hour and weekly windows on Codex quota surfaces. Default false.
|
|
757
763
|
*
|
|
758
764
|
* Spark is a single-model window that reads 0% for most operators, and on a multi-account
|
|
759
765
|
* pool it doubles the bar count for information almost nobody acts on. Hidden by default and
|
package/src/usage/log.ts
CHANGED
|
@@ -172,6 +172,10 @@ export interface PersistedUsageEntry {
|
|
|
172
172
|
closeReason?: "terminal" | "client_cancel" | "non_stream" | "body_stall" | "body_overflow";
|
|
173
173
|
/** Already redacted + capped at capture (request-log.ts redactSecretString().slice(0,500)). */
|
|
174
174
|
upstreamError?: string;
|
|
175
|
+
/** Where the terminal/failure was observed; absent on historic rows. */
|
|
176
|
+
transportPhase?: "pre_headers" | "mid_stream" | "terminal_sse";
|
|
177
|
+
/** Whether the terminal came from upstream or a proxy-generated tail. */
|
|
178
|
+
terminalSource?: "upstream" | "synthetic";
|
|
175
179
|
/**
|
|
176
180
|
* Bounded route-decision trace (RI-01): why this provider/model/account was
|
|
177
181
|
* selected. Additive field; old rows without it parse unchanged. Never
|
|
@@ -217,6 +221,22 @@ export function isKnownInboundProtocol(value: unknown): value is NonNullable<Per
|
|
|
217
221
|
return typeof value === "string" && KNOWN_INBOUND_PROTOCOLS.has(value as NonNullable<PersistedUsageEntry["inboundProtocol"]>);
|
|
218
222
|
}
|
|
219
223
|
|
|
224
|
+
const KNOWN_TRANSPORT_PHASES = new Set<NonNullable<PersistedUsageEntry["transportPhase"]>>([
|
|
225
|
+
"pre_headers", "mid_stream", "terminal_sse",
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
export function isKnownTransportPhase(value: unknown): value is NonNullable<PersistedUsageEntry["transportPhase"]> {
|
|
229
|
+
return typeof value === "string" && KNOWN_TRANSPORT_PHASES.has(value as NonNullable<PersistedUsageEntry["transportPhase"]>);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const KNOWN_TERMINAL_SOURCES = new Set<NonNullable<PersistedUsageEntry["terminalSource"]>>([
|
|
233
|
+
"upstream", "synthetic",
|
|
234
|
+
]);
|
|
235
|
+
|
|
236
|
+
export function isKnownTerminalSource(value: unknown): value is NonNullable<PersistedUsageEntry["terminalSource"]> {
|
|
237
|
+
return typeof value === "string" && KNOWN_TERMINAL_SOURCES.has(value as NonNullable<PersistedUsageEntry["terminalSource"]>);
|
|
238
|
+
}
|
|
239
|
+
|
|
220
240
|
export function usageLogPath(configDir?: string): string {
|
|
221
241
|
return join(configDir ?? getConfigDir(), "usage.jsonl");
|
|
222
242
|
}
|
|
@@ -511,6 +531,8 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
|
|
|
511
531
|
const responseServiceTier = sanitizeLogMetadataString(entry.responseServiceTier);
|
|
512
532
|
const shadowCallRewrittenFrom = sanitizeLogMetadataString(entry.shadowCallRewrittenFrom);
|
|
513
533
|
const claudeCompatibility = normalizeClaudeCompatibilityUsageLog(entry.claudeCompatibility);
|
|
534
|
+
const transportPhase = isKnownTransportPhase(entry.transportPhase) ? entry.transportPhase : undefined;
|
|
535
|
+
const terminalSource = isKnownTerminalSource(entry.terminalSource) ? entry.terminalSource : undefined;
|
|
514
536
|
const routeDecision = entry.routeDecision
|
|
515
537
|
? normalizeRouteDecisionTrace(entry.routeDecision)
|
|
516
538
|
: undefined;
|
|
@@ -579,6 +601,8 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
|
|
|
579
601
|
...(entry.usage ? { usage: normalizeUsageValue(entry.usage) } : {}),
|
|
580
602
|
...(typeof entry.totalTokens === "number" ? { totalTokens: entry.totalTokens } : {}),
|
|
581
603
|
...(Array.isArray(entry.attempts) ? { attempts } : {}),
|
|
604
|
+
...(transportPhase ? { transportPhase } : {}),
|
|
605
|
+
...(terminalSource ? { terminalSource } : {}),
|
|
582
606
|
...(entry.errorCode ? { errorCode: entry.errorCode } : {}),
|
|
583
607
|
...(entry.terminalStatus ? { terminalStatus: entry.terminalStatus } : {}),
|
|
584
608
|
...(entry.closeReason ? { closeReason: entry.closeReason } : {}),
|
|
@@ -200,6 +200,7 @@ export async function describeImageAnthropic(
|
|
|
200
200
|
const res = await fetchWithResetRetry(
|
|
201
201
|
recovery => fetch(`${base}/v1/messages`, applyUpstreamRecoveryInit({
|
|
202
202
|
method: "POST",
|
|
203
|
+
redirect: "manual",
|
|
203
204
|
headers,
|
|
204
205
|
body: JSON.stringify(body),
|
|
205
206
|
signal: linkedSignal.signal,
|
|
@@ -210,6 +210,7 @@ export async function runAnthropicWebSearch(
|
|
|
210
210
|
// ignored a bare `Connection: close` (oven-sh/bun#20492).
|
|
211
211
|
recovery => fetch(url, applyUpstreamRecoveryInit({
|
|
212
212
|
method: "POST",
|
|
213
|
+
redirect: "manual",
|
|
213
214
|
headers,
|
|
214
215
|
body: JSON.stringify(body),
|
|
215
216
|
signal: linkedSignal.signal,
|
package/src/web-search/loop.ts
CHANGED
|
@@ -478,6 +478,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
478
478
|
// replay on this leg eligible for the same dead socket the reset came from.
|
|
479
479
|
return requestFetch(request.url, applyUpstreamRecoveryInit({
|
|
480
480
|
method: request.method,
|
|
481
|
+
redirect: "manual",
|
|
481
482
|
headers: h,
|
|
482
483
|
body: request.body,
|
|
483
484
|
signal: headerDeadline.signal,
|
|
@@ -303,6 +303,10 @@ export async function* parseStreamWithProgress(
|
|
|
303
303
|
}
|
|
304
304
|
if (event.type === "done" || event.type === "incomplete") {
|
|
305
305
|
heldTerminal = event;
|
|
306
|
+
// Response-byte inactivity ends once the adapter has produced a terminal event.
|
|
307
|
+
// From here the separate post-terminal drain guard owns the bounded wait for
|
|
308
|
+
// iterator cleanup, so leaving the inactivity timer armed creates a false timeout.
|
|
309
|
+
clearInactivity();
|
|
306
310
|
continue;
|
|
307
311
|
}
|
|
308
312
|
await handoff.deliver(event);
|