pi-firecode 0.8.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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -0
  3. package/config.example.jsonc +100 -0
  4. package/config.ts +474 -0
  5. package/deliver.ts +32 -0
  6. package/flame-frames.ts +460 -0
  7. package/format.ts +80 -0
  8. package/header.ts +92 -0
  9. package/herdr-client.ts +60 -0
  10. package/index.ts +62 -0
  11. package/jsonc.ts +34 -0
  12. package/master/event-card.ts +106 -0
  13. package/master/event-format.ts +52 -0
  14. package/master/index.ts +1200 -0
  15. package/master/prompt.ts +26 -0
  16. package/master/prompts/master.zh.md +17 -0
  17. package/master/prompts/worker.zh.md +1 -0
  18. package/master/role.ts +13 -0
  19. package/master/spawn.ts +193 -0
  20. package/master/state.ts +201 -0
  21. package/package.json +59 -0
  22. package/provider/claude-sub.ts +129 -0
  23. package/provider/openai-native/index.ts +8 -0
  24. package/provider/openai-native/src/compact-client.ts +362 -0
  25. package/provider/openai-native/src/config.ts +297 -0
  26. package/provider/openai-native/src/extension.ts +93 -0
  27. package/provider/openai-native/src/native-compaction.ts +151 -0
  28. package/provider/openai-native/src/native-details.ts +157 -0
  29. package/provider/openai-native/src/native-replay.ts +253 -0
  30. package/provider/openai-native/src/native-runtime.ts +144 -0
  31. package/provider/openai-native/src/options.ts +85 -0
  32. package/provider/openai-native/src/request-pipeline.ts +21 -0
  33. package/provider/openai-native/src/responses-input.ts +433 -0
  34. package/review/advisor.ts +112 -0
  35. package/review/card.ts +385 -0
  36. package/review/checkpoint.ts +384 -0
  37. package/review/evidence.ts +232 -0
  38. package/review/index.ts +1246 -0
  39. package/review/outcome.ts +74 -0
  40. package/review/progress.ts +324 -0
  41. package/review/prompt.ts +210 -0
  42. package/review/prompts/advisor.en.md +41 -0
  43. package/review/prompts/advisor.zh.md +42 -0
  44. package/review/prompts/review.en.md +75 -0
  45. package/review/prompts/review.zh.md +75 -0
  46. package/review/reviewer.ts +356 -0
  47. package/review/session.ts +129 -0
  48. package/review/state.ts +905 -0
  49. package/review/ui.ts +528 -0
  50. package/session/bark.ts +157 -0
  51. package/session/herdr-display.ts +87 -0
  52. package/session/presets.ts +257 -0
  53. package/session/rename.ts +46 -0
  54. package/session/stats.ts +310 -0
  55. package/session/working-flame.ts +116 -0
  56. package/statusbar/index.ts +118 -0
  57. package/statusbar/quota-cache.ts +60 -0
  58. package/statusbar/quota-parse.ts +85 -0
  59. package/statusbar/quota.ts +203 -0
  60. package/statusbar/render.ts +181 -0
  61. package/statusbar/tps.ts +87 -0
  62. package/theme.ts +84 -0
  63. package/tools/grouping.ts +233 -0
  64. package/tools/index.ts +183 -0
  65. package/tools/line.ts +194 -0
  66. package/tools/parts.ts +143 -0
  67. package/tools/timing.ts +22 -0
  68. package/watcher/card.ts +85 -0
  69. package/watcher/index.ts +204 -0
  70. package/watcher/observer.ts +75 -0
  71. package/watcher/prompts/watch.zh.md +46 -0
  72. package/watcher/transcript.ts +64 -0
@@ -0,0 +1,93 @@
1
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import {
3
+ loadOpenAINativeSettings,
4
+ togglePriority,
5
+ type OpenAINativeSettings,
6
+ } from "./config";
7
+ import { compactWithOpenAINative } from "./native-compaction";
8
+ import { FAST_STATUS_KEY, fastModeEnabled, supportsFastMode } from "./options";
9
+ import { rewriteOpenAIProviderRequest } from "./request-pipeline";
10
+
11
+ const VERBOSITY_FLAG = "verbosity";
12
+
13
+ function updateFastStatus(ctx: ExtensionContext, settings: OpenAINativeSettings): void {
14
+ if (!ctx.hasUI) {
15
+ return;
16
+ }
17
+ ctx.ui.setStatus(
18
+ FAST_STATUS_KEY,
19
+ fastModeEnabled(ctx.model, settings) ? ctx.ui.theme.fg("warning", "⚡ fast") : undefined,
20
+ );
21
+ }
22
+
23
+ function notifyUnsupportedFastMode(ctx: ExtensionContext): void {
24
+ ctx.ui.notify("当前模型不支持加速档", "warning");
25
+ }
26
+
27
+ export default function openAINativeExtension(
28
+ pi: ExtensionAPI,
29
+ configPath: string,
30
+ fastShortcut = "ctrl+f",
31
+ ): void {
32
+ let loadedSettings = loadOpenAINativeSettings(configPath);
33
+ let settings = loadedSettings.settings;
34
+
35
+ function toggleFastMode(ctx: ExtensionContext): void {
36
+ if (!ctx.model) {
37
+ return;
38
+ }
39
+ if (!supportsFastMode(ctx.model)) {
40
+ notifyUnsupportedFastMode(ctx);
41
+ return;
42
+ }
43
+
44
+ try {
45
+ const result = togglePriority(ctx.model.provider, configPath);
46
+ loadedSettings = result.loaded;
47
+ settings = loadedSettings.settings;
48
+ updateFastStatus(ctx, settings);
49
+ ctx.ui.notify(`加速档:${result.enabled ? "开" : "关"}`, "info");
50
+ } catch (error) {
51
+ ctx.ui.notify(`加速档保存失败:${error instanceof Error ? error.message : String(error)}`, "error");
52
+ }
53
+ }
54
+
55
+ pi.registerFlag(VERBOSITY_FLAG, {
56
+ description: "覆盖 OpenAI 回答详略:low、medium、high",
57
+ type: "string",
58
+ });
59
+ pi.registerCommand("fast", {
60
+ description: "开关当前 OpenAI 系供应商的加速档",
61
+ handler: async (_args, ctx) => {
62
+ toggleFastMode(ctx);
63
+ },
64
+ });
65
+ pi.registerShortcut(fastShortcut as never, {
66
+ description: "开关加速档",
67
+ handler: toggleFastMode,
68
+ });
69
+
70
+ pi.on("session_start", (_event, ctx) => {
71
+ if (loadedSettings.warnings.length > 0 && ctx.hasUI) {
72
+ ctx.ui.notify(`FireCode openai 配置:${loadedSettings.warnings[0]}`, "warning");
73
+ }
74
+ updateFastStatus(ctx, settings);
75
+ });
76
+
77
+ pi.on("model_select", (_event, ctx) => updateFastStatus(ctx, settings));
78
+ pi.on("session_before_compact", (event, ctx) => {
79
+ if (!settings.nativeCompaction) {
80
+ return undefined;
81
+ }
82
+ return compactWithOpenAINative(event, ctx);
83
+ });
84
+ pi.on("before_provider_request", (event, ctx) => {
85
+ const nextPayload = rewriteOpenAIProviderRequest(event.payload, ctx, settings, pi.getFlag(VERBOSITY_FLAG));
86
+ return nextPayload === event.payload ? undefined : nextPayload;
87
+ });
88
+ pi.on("session_shutdown", (_event, ctx) => {
89
+ if (ctx.hasUI) {
90
+ ctx.ui.setStatus(FAST_STATUS_KEY, undefined);
91
+ }
92
+ });
93
+ }
@@ -0,0 +1,151 @@
1
+ import { buildSessionContext } from "@earendil-works/pi-coding-agent";
2
+ import type { CompactionResult, ExtensionContext, SessionBeforeCompactEvent } from "@earendil-works/pi-coding-agent";
3
+ import { executeNativeCompaction, type NativeCompactionResult } from "./compact-client";
4
+ import {
5
+ cloneStructuredValue,
6
+ createNativeCompactionDetails,
7
+ createNativeCompactionResult,
8
+ resolveLatestNativeCompaction,
9
+ } from "./native-details";
10
+ import { rewriteNativeResponsesPayload, serializeLiveTailToResponsesInput } from "./native-replay";
11
+ import {
12
+ resolveNativeCompactionRuntime,
13
+ resolveNativeCompactionTarget,
14
+ type ResponsesRequestPayload,
15
+ } from "./native-runtime";
16
+ import {
17
+ serializeMessagesToCompactRequest,
18
+ type NativeCompactionRequest,
19
+ } from "./responses-input";
20
+
21
+ type NativeCompactionHookResult = {
22
+ cancel?: boolean;
23
+ compaction?: CompactionResult;
24
+ };
25
+
26
+ function buildCompactionInstructions(systemPrompt: string, customInstructions?: string): string {
27
+ const guidance = customInstructions?.trim();
28
+ return guidance
29
+ ? `${systemPrompt}\n\nAdditional user guidance for this manual /compact request:\n${guidance}`
30
+ : systemPrompt;
31
+ }
32
+
33
+ function reportCompactionFailure(ctx: ExtensionContext, message: string): void {
34
+ const fullMessage = `pi-openai-native: ${message}`;
35
+ if (ctx.hasUI) {
36
+ ctx.ui.notify(fullMessage, "error");
37
+ return;
38
+ }
39
+ console.error(fullMessage);
40
+ }
41
+
42
+ function cancelCompaction(ctx: ExtensionContext, message: string): NativeCompactionHookResult {
43
+ reportCompactionFailure(ctx, message);
44
+ return { cancel: true };
45
+ }
46
+
47
+ function failureMessage(result: Extract<NativeCompactionResult, { ok: false }>): string {
48
+ const status = result.status ? ` (HTTP ${result.status})` : "";
49
+ const detail = result.detail ? `: ${result.detail}` : "";
50
+ const message = `native compaction failed: ${result.reason}${status}${detail}`;
51
+ return message.endsWith(".") ? message : `${message}.`;
52
+ }
53
+
54
+ export async function compactWithOpenAINative(
55
+ event: SessionBeforeCompactEvent,
56
+ ctx: ExtensionContext,
57
+ ): Promise<NativeCompactionHookResult | undefined> {
58
+ if (event.signal.aborted) {
59
+ return { cancel: true };
60
+ }
61
+
62
+ const target = resolveNativeCompactionTarget(ctx);
63
+ if (!target.ok) {
64
+ return undefined;
65
+ }
66
+
67
+ const runtime = await resolveNativeCompactionRuntime(ctx, target.target);
68
+ if (!runtime.ok) {
69
+ return cancelCompaction(ctx, `native compaction is unavailable: ${runtime.reason}.`);
70
+ }
71
+
72
+ try {
73
+ const latestCompaction = resolveLatestNativeCompaction(event.branchEntries, runtime.runtime);
74
+ let request: NativeCompactionRequest;
75
+ if (latestCompaction.ok) {
76
+ const compactedWindow = latestCompaction.entry.details.compactedWindow.map(cloneStructuredValue);
77
+ request = {
78
+ model: runtime.runtime.model,
79
+ input: [
80
+ ...compactedWindow,
81
+ ...serializeLiveTailToResponsesInput({
82
+ model: runtime.runtime.currentModel,
83
+ entries: event.branchEntries.slice(latestCompaction.index + 1),
84
+ }),
85
+ ],
86
+ instructions: buildCompactionInstructions(ctx.getSystemPrompt(), event.customInstructions),
87
+ };
88
+ } else if (latestCompaction.reason === "no-compaction") {
89
+ request = serializeMessagesToCompactRequest({
90
+ model: runtime.runtime.currentModel,
91
+ messages: buildSessionContext(
92
+ ctx.sessionManager.getEntries(),
93
+ ctx.sessionManager.getLeafId(),
94
+ ).messages,
95
+ instructions: buildCompactionInstructions(ctx.getSystemPrompt(), event.customInstructions),
96
+ });
97
+ } else {
98
+ return undefined;
99
+ }
100
+
101
+ const result = await executeNativeCompaction({
102
+ runtime: runtime.runtime,
103
+ request,
104
+ signal: event.signal,
105
+ });
106
+ if (!result.ok) {
107
+ return result.reason === "aborted" ? { cancel: true } : cancelCompaction(ctx, failureMessage(result));
108
+ }
109
+
110
+ return {
111
+ compaction: createNativeCompactionResult({
112
+ firstKeptEntryId: event.preparation.firstKeptEntryId,
113
+ tokensBefore: event.preparation.tokensBefore,
114
+ details: createNativeCompactionDetails({
115
+ provider: runtime.runtime.provider,
116
+ api: runtime.runtime.api,
117
+ model: runtime.runtime.model,
118
+ baseUrl: runtime.runtime.baseUrl,
119
+ compactedWindow: result.compactedWindow,
120
+ createdAt: result.createdAt,
121
+ }),
122
+ }),
123
+ };
124
+ } catch (error) {
125
+ return cancelCompaction(ctx, `native compaction failed: ${error instanceof Error ? error.message : String(error)}`);
126
+ }
127
+ }
128
+
129
+ export function replayOpenAINative(
130
+ payload: unknown,
131
+ ctx: ExtensionContext,
132
+ ): ResponsesRequestPayload | undefined {
133
+ const target = resolveNativeCompactionTarget(ctx, payload);
134
+ if (!target.ok || !target.target.payload) {
135
+ return undefined;
136
+ }
137
+
138
+ const branchEntries = ctx.sessionManager.getBranch();
139
+ const latestCompaction = resolveLatestNativeCompaction(branchEntries, target.target);
140
+ if (!latestCompaction.ok) {
141
+ return undefined;
142
+ }
143
+
144
+ const rewrite = rewriteNativeResponsesPayload({
145
+ model: target.target.currentModel,
146
+ payload: target.target.payload,
147
+ branchEntries,
148
+ compactionEntry: latestCompaction.entry,
149
+ });
150
+ return rewrite.ok ? rewrite.payload : undefined;
151
+ }
@@ -0,0 +1,157 @@
1
+ import type { CompactionEntry, CompactionResult, SessionEntry } from "@earendil-works/pi-coding-agent";
2
+
3
+ export const NATIVE_COMPACTION_STRATEGY = "openai-native-compact";
4
+ export const NATIVE_COMPACTION_SUMMARY = "[OpenAI native compaction checkpoint]";
5
+
6
+ export type NativeCompactionIdentity = {
7
+ provider: string;
8
+ api: string;
9
+ model: string;
10
+ baseUrl: string;
11
+ };
12
+
13
+ export type NativeCompactionDetails = NativeCompactionIdentity & {
14
+ strategy: typeof NATIVE_COMPACTION_STRATEGY;
15
+ compactedWindow: unknown[];
16
+ createdAt: string;
17
+ };
18
+
19
+ export type NativeCompactionEntry = CompactionEntry<NativeCompactionDetails> & {
20
+ details: NativeCompactionDetails;
21
+ };
22
+
23
+ type NativeCompactionResolution =
24
+ | {
25
+ ok: true;
26
+ entry: NativeCompactionEntry;
27
+ index: number;
28
+ }
29
+ | {
30
+ ok: false;
31
+ reason: "no-compaction" | "latest-compaction-not-native" | "latest-native-compaction-mismatch";
32
+ };
33
+
34
+ function isRecord(value: unknown): value is Record<string, unknown> {
35
+ return value !== null && typeof value === "object" && !Array.isArray(value);
36
+ }
37
+
38
+ function isNonEmptyString(value: unknown): value is string {
39
+ return typeof value === "string" && value.trim().length > 0;
40
+ }
41
+
42
+ function isStructuredValue(value: unknown): boolean {
43
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
44
+ return true;
45
+ }
46
+ if (Array.isArray(value)) {
47
+ return value.every(isStructuredValue);
48
+ }
49
+ return isRecord(value) && Object.values(value).every(isStructuredValue);
50
+ }
51
+
52
+ function isCompactedWindowItem(value: unknown): value is Record<string, unknown> {
53
+ return isRecord(value) && Object.values(value).every(isStructuredValue);
54
+ }
55
+
56
+ function hasNativeCompactionIdentity(value: Record<string, unknown>): boolean {
57
+ return (
58
+ isNonEmptyString(value.provider) &&
59
+ isNonEmptyString(value.api) &&
60
+ isNonEmptyString(value.model) &&
61
+ isNonEmptyString(value.baseUrl)
62
+ );
63
+ }
64
+
65
+ export function cloneStructuredValue(value: unknown): unknown {
66
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
67
+ return value;
68
+ }
69
+ if (Array.isArray(value)) {
70
+ return value.map(cloneStructuredValue);
71
+ }
72
+ if (isRecord(value)) {
73
+ return Object.fromEntries(Object.entries(value).map(([key, nestedValue]) => [key, cloneStructuredValue(nestedValue)]));
74
+ }
75
+ throw new Error(`Unsupported structured value: ${typeof value}`);
76
+ }
77
+
78
+ export function isNativeCompactionDetails(value: unknown): value is NativeCompactionDetails {
79
+ if (!isRecord(value) || value.strategy !== NATIVE_COMPACTION_STRATEGY || !hasNativeCompactionIdentity(value)) {
80
+ return false;
81
+ }
82
+
83
+ return (
84
+ Array.isArray(value.compactedWindow) &&
85
+ value.compactedWindow.every(isCompactedWindowItem) &&
86
+ isNonEmptyString(value.createdAt)
87
+ );
88
+ }
89
+
90
+ function isNativeCompactionEntry(value: SessionEntry | undefined): value is NativeCompactionEntry {
91
+ return value?.type === "compaction" && isNativeCompactionDetails(value.details);
92
+ }
93
+
94
+ export function createNativeCompactionDetails(input: NativeCompactionIdentity & {
95
+ compactedWindow: unknown[];
96
+ createdAt?: string;
97
+ }): NativeCompactionDetails {
98
+ const identity: NativeCompactionIdentity = {
99
+ provider: input.provider.trim(),
100
+ api: input.api.trim(),
101
+ model: input.model.trim(),
102
+ baseUrl: input.baseUrl.trim(),
103
+ };
104
+ if (!identity.provider || !identity.api || !identity.model || !identity.baseUrl) {
105
+ throw new Error("Native compaction identity is incomplete");
106
+ }
107
+
108
+ if (!input.compactedWindow.every(isCompactedWindowItem)) {
109
+ throw new Error("Native compaction window contains an invalid item");
110
+ }
111
+
112
+ return {
113
+ strategy: NATIVE_COMPACTION_STRATEGY,
114
+ ...identity,
115
+ compactedWindow: input.compactedWindow.map(cloneStructuredValue),
116
+ createdAt: isNonEmptyString(input.createdAt) ? input.createdAt.trim() : new Date().toISOString(),
117
+ };
118
+ }
119
+
120
+ export function createNativeCompactionResult(input: {
121
+ firstKeptEntryId: string;
122
+ tokensBefore: number;
123
+ details: NativeCompactionDetails;
124
+ }): CompactionResult<NativeCompactionDetails> {
125
+ return {
126
+ summary: NATIVE_COMPACTION_SUMMARY,
127
+ firstKeptEntryId: input.firstKeptEntryId,
128
+ tokensBefore: input.tokensBefore,
129
+ details: input.details,
130
+ };
131
+ }
132
+
133
+ export function resolveLatestNativeCompaction(
134
+ entries: readonly SessionEntry[],
135
+ identity: NativeCompactionIdentity,
136
+ ): NativeCompactionResolution {
137
+ for (let index = entries.length - 1; index >= 0; index--) {
138
+ const entry = entries[index];
139
+ if (entry?.type !== "compaction") {
140
+ continue;
141
+ }
142
+ if (!isNativeCompactionEntry(entry)) {
143
+ return { ok: false, reason: "latest-compaction-not-native" };
144
+ }
145
+ if (
146
+ entry.details.provider !== identity.provider ||
147
+ entry.details.api !== identity.api ||
148
+ entry.details.model !== identity.model ||
149
+ entry.details.baseUrl !== identity.baseUrl
150
+ ) {
151
+ return { ok: false, reason: "latest-native-compaction-mismatch" };
152
+ }
153
+ return { ok: true, entry, index };
154
+ }
155
+
156
+ return { ok: false, reason: "no-compaction" };
157
+ }
@@ -0,0 +1,253 @@
1
+ import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
+ import type { Api, Model } from "@earendil-works/pi-ai";
3
+ import type {
4
+ BranchSummaryEntry,
5
+ CustomMessageEntry,
6
+ SessionEntry,
7
+ SessionMessageEntry,
8
+ } from "@earendil-works/pi-coding-agent";
9
+ import { cloneStructuredValue, type NativeCompactionEntry } from "./native-details";
10
+ import type { ResponsesRequestPayload } from "./native-runtime";
11
+ import {
12
+ serializeMessagesToResponsesInput,
13
+ type ResponsesInputContentItem,
14
+ type ResponsesInputItem,
15
+ type ResponsesInputMessageItem,
16
+ } from "./responses-input";
17
+
18
+ export type NativeReplayFailureReason =
19
+ | "compaction-boundary-not-found"
20
+ | "first-kept-entry-not-found"
21
+ | "unsupported-instructions"
22
+ | "invalid-compacted-window"
23
+ | "unexpected-compaction-after-boundary"
24
+ | "expected-pi-replay-mismatch";
25
+
26
+ export type NativeReplayResult =
27
+ | { ok: true; payload: ResponsesRequestPayload }
28
+ | { ok: false; reason: NativeReplayFailureReason };
29
+
30
+ type FreshPreamble = {
31
+ leadingInput: ResponsesInputMessageItem[];
32
+ trailingInput: ResponsesInputMessageItem[];
33
+ };
34
+
35
+ function isRecord(value: unknown): value is Record<string, unknown> {
36
+ return value !== null && typeof value === "object" && !Array.isArray(value);
37
+ }
38
+
39
+ function isResponsesInputContentItem(value: unknown): value is ResponsesInputContentItem {
40
+ if (!isRecord(value) || typeof value.type !== "string") {
41
+ return false;
42
+ }
43
+ if (value.type === "input_text") {
44
+ return typeof value.text === "string";
45
+ }
46
+ return value.type === "input_image" && value.detail === "auto" && typeof value.image_url === "string";
47
+ }
48
+
49
+ function isResponsesInputMessageItem(value: unknown): value is ResponsesInputMessageItem {
50
+ if (!isRecord(value) || (value.role !== "user" && value.role !== "developer" && value.role !== "system")) {
51
+ return false;
52
+ }
53
+ return typeof value.content === "string" || (Array.isArray(value.content) && value.content.every(isResponsesInputContentItem));
54
+ }
55
+
56
+ function isPromptEnvelopeItem(value: unknown): value is ResponsesInputMessageItem {
57
+ return isResponsesInputMessageItem(value) && (value.role === "developer" || value.role === "system");
58
+ }
59
+
60
+ function cloneInputContent(item: ResponsesInputContentItem): ResponsesInputContentItem {
61
+ return item.type === "input_text"
62
+ ? { type: "input_text", text: item.text }
63
+ : { type: "input_image", detail: "auto", image_url: item.image_url };
64
+ }
65
+
66
+ function cloneInputMessage(item: ResponsesInputMessageItem): ResponsesInputMessageItem {
67
+ return {
68
+ role: item.role,
69
+ content: typeof item.content === "string" ? item.content : item.content.map(cloneInputContent),
70
+ };
71
+ }
72
+
73
+ function cloneResponsesInput(items: readonly unknown[]): ResponsesInputItem[] | undefined {
74
+ try {
75
+ return items.map((item) => cloneStructuredValue(item) as ResponsesInputItem);
76
+ } catch {
77
+ return undefined;
78
+ }
79
+ }
80
+
81
+ function cloneCompactedWindow(items: readonly unknown[]): unknown[] | undefined {
82
+ try {
83
+ return items.map(cloneStructuredValue);
84
+ } catch {
85
+ return undefined;
86
+ }
87
+ }
88
+
89
+ function areEquivalentValues(left: unknown, right: unknown): boolean {
90
+ if (Object.is(left, right)) {
91
+ return true;
92
+ }
93
+ if (Array.isArray(left) || Array.isArray(right)) {
94
+ return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((item, index) => areEquivalentValues(item, right[index]));
95
+ }
96
+ if (!isRecord(left) || !isRecord(right)) {
97
+ return false;
98
+ }
99
+ const leftKeys = Object.keys(left).sort();
100
+ const rightKeys = Object.keys(right).sort();
101
+ return (
102
+ areEquivalentValues(leftKeys, rightKeys) &&
103
+ leftKeys.every((key) => areEquivalentValues(left[key], right[key]))
104
+ );
105
+ }
106
+
107
+ function toBranchSummaryMessage(entry: BranchSummaryEntry): AgentMessage {
108
+ return {
109
+ role: "branchSummary",
110
+ summary: entry.summary,
111
+ fromId: entry.fromId,
112
+ timestamp: new Date(entry.timestamp).getTime(),
113
+ } as AgentMessage;
114
+ }
115
+
116
+ function toCustomMessage(entry: CustomMessageEntry): AgentMessage {
117
+ return {
118
+ role: "custom",
119
+ customType: entry.customType,
120
+ content: entry.content,
121
+ display: entry.display,
122
+ details: entry.details,
123
+ timestamp: new Date(entry.timestamp).getTime(),
124
+ } as AgentMessage;
125
+ }
126
+
127
+ function toReplayMessage(entry: SessionEntry): AgentMessage | undefined {
128
+ if (entry.type === "message") {
129
+ return (entry as SessionMessageEntry).message;
130
+ }
131
+ if (entry.type === "custom_message") {
132
+ return toCustomMessage(entry);
133
+ }
134
+ if (entry.type === "branch_summary") {
135
+ return toBranchSummaryMessage(entry);
136
+ }
137
+ return undefined;
138
+ }
139
+
140
+ function collectReplayMessages(entries: readonly SessionEntry[]): AgentMessage[] {
141
+ const messages: AgentMessage[] = [];
142
+ for (const entry of entries) {
143
+ const message = toReplayMessage(entry);
144
+ if (message) {
145
+ messages.push(message);
146
+ }
147
+ }
148
+ return messages;
149
+ }
150
+
151
+ function createCompactionSummary(entry: NativeCompactionEntry): AgentMessage {
152
+ return {
153
+ role: "compactionSummary",
154
+ summary: entry.summary,
155
+ tokensBefore: entry.tokensBefore,
156
+ timestamp: new Date(entry.timestamp).getTime(),
157
+ } as AgentMessage;
158
+ }
159
+
160
+ function extractFreshPreamble(payload: ResponsesRequestPayload): FreshPreamble | undefined {
161
+ if (payload.instructions !== undefined && typeof payload.instructions !== "string") {
162
+ return undefined;
163
+ }
164
+
165
+ let leadingBoundary = 0;
166
+ while (leadingBoundary < payload.input.length && isPromptEnvelopeItem(payload.input[leadingBoundary])) {
167
+ leadingBoundary += 1;
168
+ }
169
+ let trailingBoundary = payload.input.length;
170
+ while (trailingBoundary > leadingBoundary && isPromptEnvelopeItem(payload.input[trailingBoundary - 1])) {
171
+ trailingBoundary -= 1;
172
+ }
173
+ for (let index = leadingBoundary; index < trailingBoundary; index++) {
174
+ if (isPromptEnvelopeItem(payload.input[index])) {
175
+ return undefined;
176
+ }
177
+ }
178
+
179
+ return {
180
+ leadingInput: payload.input.slice(0, leadingBoundary).map((item) => cloneInputMessage(item as ResponsesInputMessageItem)),
181
+ trailingInput: payload.input.slice(trailingBoundary).map((item) => cloneInputMessage(item as ResponsesInputMessageItem)),
182
+ };
183
+ }
184
+
185
+ export function serializeLiveTailToResponsesInput<TApi extends Api>(args: {
186
+ model: Model<TApi>;
187
+ entries: readonly SessionEntry[];
188
+ }): ResponsesInputItem[] {
189
+ return serializeMessagesToResponsesInput(args.model, collectReplayMessages(args.entries));
190
+ }
191
+
192
+ export function rewriteNativeResponsesPayload<TApi extends Api>(args: {
193
+ model: Model<TApi>;
194
+ payload: ResponsesRequestPayload;
195
+ branchEntries: readonly SessionEntry[];
196
+ compactionEntry: NativeCompactionEntry;
197
+ }): NativeReplayResult {
198
+ const boundaryIndex = args.branchEntries.findIndex((entry) => entry.id === args.compactionEntry.id);
199
+ if (boundaryIndex < 0) {
200
+ return { ok: false, reason: "compaction-boundary-not-found" };
201
+ }
202
+ const firstKeptEntryIndex = args.branchEntries.findIndex(
203
+ (entry, index) => index < boundaryIndex && entry.id === args.compactionEntry.firstKeptEntryId,
204
+ );
205
+ if (firstKeptEntryIndex < 0) {
206
+ return { ok: false, reason: "first-kept-entry-not-found" };
207
+ }
208
+
209
+ const preamble = extractFreshPreamble(args.payload);
210
+ if (!preamble) {
211
+ return { ok: false, reason: "unsupported-instructions" };
212
+ }
213
+ if (args.branchEntries.slice(boundaryIndex + 1).some((entry) => entry.type === "compaction")) {
214
+ return { ok: false, reason: "unexpected-compaction-after-boundary" };
215
+ }
216
+
217
+ const compactedWindow = cloneCompactedWindow(args.compactionEntry.details.compactedWindow);
218
+ if (!compactedWindow) {
219
+ return { ok: false, reason: "invalid-compacted-window" };
220
+ }
221
+
222
+ const preCompactionEntries = args.branchEntries.slice(firstKeptEntryIndex, boundaryIndex);
223
+ const postCompactionEntries = args.branchEntries.slice(boundaryIndex + 1);
224
+ const compactionSummaryInput = serializeMessagesToResponsesInput(args.model, [createCompactionSummary(args.compactionEntry)]);
225
+ const preCompactionInput = serializeMessagesToResponsesInput(args.model, collectReplayMessages(preCompactionEntries));
226
+ const postCompactionInput = serializeMessagesToResponsesInput(args.model, collectReplayMessages(postCompactionEntries));
227
+ const expectedInput = [
228
+ ...preamble.leadingInput,
229
+ ...compactionSummaryInput,
230
+ ...preCompactionInput,
231
+ ...postCompactionInput,
232
+ ...preamble.trailingInput,
233
+ ];
234
+ if (!areEquivalentValues(args.payload.input, expectedInput)) {
235
+ return { ok: false, reason: "expected-pi-replay-mismatch" };
236
+ }
237
+
238
+ const tailStart = preamble.leadingInput.length + compactionSummaryInput.length + preCompactionInput.length;
239
+ const tailEnd = args.payload.input.length - preamble.trailingInput.length;
240
+ const tail = cloneResponsesInput(args.payload.input.slice(tailStart, tailEnd));
241
+ if (!tail) {
242
+ return { ok: false, reason: "expected-pi-replay-mismatch" };
243
+ }
244
+
245
+ return {
246
+ ok: true,
247
+ payload: {
248
+ ...args.payload,
249
+ input: [...preamble.leadingInput, ...compactedWindow, ...tail, ...preamble.trailingInput],
250
+ },
251
+ };
252
+ }
253
+