@shanepadgett/tau-agent 0.33.1 → 0.34.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/docs/context.md +29 -20
- package/docs/extending-tau-agent.md +63 -24
- package/extensions/appshot/index.ts +112 -111
- package/extensions/aside/README.md +7 -0
- package/extensions/aside/index.ts +168 -0
- package/extensions/aside/panel.ts +83 -0
- package/extensions/branch/index.ts +38 -21
- package/extensions/cache-diagnostics/index.ts +70 -58
- package/extensions/checkpoint/README.md +9 -0
- package/extensions/checkpoint/checkpoint-budget.ts +79 -0
- package/extensions/checkpoint/checkpoint.ts +277 -0
- package/extensions/checkpoint/index.ts +102 -0
- package/extensions/checkpoint/messages.ts +169 -0
- package/extensions/checkpoint/prompt.ts +24 -0
- package/extensions/checkpoint/settings.ts +27 -0
- package/extensions/commit/commit-effort.ts +3 -3
- package/extensions/context/README.md +12 -8
- package/extensions/context/definitions.ts +178 -55
- package/extensions/context/index.ts +192 -160
- package/extensions/context/panel.ts +57 -37
- package/extensions/context/sync.ts +139 -89
- package/extensions/context/validation.ts +37 -25
- package/extensions/effort/README.md +1 -1
- package/extensions/effort/index.ts +3 -3
- package/extensions/effort/state.ts +3 -18
- package/extensions/explore/guidance.ts +6 -47
- package/extensions/explore/index.ts +38 -53
- package/extensions/explore/{ast/read → read}/hook.ts +12 -7
- package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
- package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
- package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
- package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
- package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
- package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
- package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
- package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
- package/extensions/explore/tools/target-params.ts +9 -0
- package/extensions/footer/index.ts +75 -56
- package/extensions/handoff/index.ts +8 -30
- package/extensions/image-gen/index.ts +46 -40
- package/extensions/patch/executor.ts +190 -135
- package/extensions/patch/matcher.ts +82 -48
- package/extensions/patch/parser.ts +149 -105
- package/extensions/patch/render.ts +32 -39
- package/extensions/patch/summary.ts +30 -26
- package/extensions/ready/README.md +7 -0
- package/extensions/ready/index.ts +58 -0
- package/extensions/ready/model.ts +128 -0
- package/extensions/ready/packs.ts +272 -0
- package/extensions/ready/render.ts +445 -0
- package/extensions/ready/scan.ts +760 -0
- package/extensions/reference/panel.ts +28 -16
- package/extensions/review/index.ts +63 -51
- package/extensions/review/panel.ts +5 -34
- package/extensions/runtime-context/context.ts +30 -26
- package/extensions/script-runner/README.md +2 -2
- package/extensions/script-runner/index.ts +122 -82
- package/extensions/silent-command-runner/index.ts +38 -23
- package/extensions/subagent/agents/context-sync.md +185 -33
- package/extensions/subagent/agents/scout.md +2 -2
- package/extensions/subagent/agents.ts +137 -84
- package/extensions/subagent/cmux-dashboard.ts +39 -30
- package/extensions/subagent/index.ts +79 -59
- package/extensions/subagent/resume.ts +1 -1
- package/extensions/subagent/run.ts +304 -193
- package/extensions/subagent/runtime.ts +781 -343
- package/extensions/tau/index.ts +47 -38
- package/extensions/tau-help/help.md +14 -6
- package/extensions/tool-loader/README.md +4 -2
- package/extensions/tool-loader/index.ts +77 -52
- package/extensions/web/index.ts +6 -3
- package/extensions/web/webfetch.ts +114 -86
- package/package.json +3 -3
- package/schemas/tau.schema.json +20 -33
- package/shared/bounded-text-result.ts +1 -0
- package/shared/checkpoint-visibility.ts +9 -0
- package/shared/events.ts +28 -10
- package/shared/isolated-session.ts +71 -29
- package/shared/model-effort.ts +9 -19
- package/shared/model-fallback/index.ts +66 -41
- package/shared/settings/load.ts +45 -38
- package/shared/temporary-output-store.ts +16 -10
- package/{extensions/explore → src}/ast/adapter.ts +11 -2
- package/{extensions/explore → src}/ast/engine.ts +54 -38
- package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
- package/{extensions/explore → src}/ast/format/context.ts +3 -6
- package/src/ast/format/deps.ts +94 -0
- package/{extensions/explore → src}/ast/format/discover.ts +2 -2
- package/{extensions/explore → src}/ast/format/impact.ts +9 -11
- package/{extensions/explore → src}/ast/format/outline.ts +2 -2
- package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
- package/{extensions/explore → src}/ast/format/show.ts +1 -1
- package/src/ast/format/targeted-result.ts +20 -0
- package/src/ast/grammars/c_sharp.wasm +0 -0
- package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
- package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
- package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
- package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
- package/{extensions/explore → src}/ast/ir.ts +6 -0
- package/src/ast/languages/csharp-file-deps.ts +90 -0
- package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
- package/src/ast/languages/dotted-index.ts +131 -0
- package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
- package/src/ast/languages/fixtures/sample.cs +53 -0
- package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
- package/src/ast/languages/fixtures/sample.go +37 -0
- package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
- package/src/ast/languages/fixtures/sample.java +40 -0
- package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
- package/src/ast/languages/fixtures/sample.kt +31 -0
- package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
- package/src/ast/languages/fixtures/sample.md +23 -0
- package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
- package/src/ast/languages/fixtures/sample.rs +54 -0
- package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
- package/src/ast/languages/fixtures/sample.swift +45 -0
- package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
- package/src/ast/languages/fixtures/sample.ts +45 -0
- package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
- package/src/ast/languages/fixtures/sample.tsx +36 -0
- package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
- package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
- package/{extensions/explore → src}/ast/languages/go.ts +21 -3
- package/{extensions/explore → src}/ast/languages/java.ts +5 -38
- package/src/ast/languages/jvm-file-deps.ts +135 -0
- package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
- package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
- package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
- package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
- package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
- package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
- package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
- package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
- package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
- package/{extensions/explore → src}/ast/markdown.ts +72 -47
- package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
- package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
- package/{extensions/explore → src}/ast/queries/context.ts +116 -80
- package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
- package/src/ast/queries/impact.ts +291 -0
- package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
- package/{extensions/explore → src}/ast/queries/show.ts +46 -42
- package/{extensions/explore → src}/ast/query.ts +1 -1
- package/{extensions/explore → src}/ast/registry.ts +1 -2
- package/{extensions/explore → src}/ast/scan.ts +1 -1
- package/src/ast/session.ts +50 -0
- package/{extensions/explore → src}/ast/slice.ts +8 -0
- package/{extensions/explore → src/ast}/traverse.ts +32 -20
- package/src/file-injection/index.ts +547 -0
- package/src/image-generation/client.ts +50 -31
- package/src/image-generation/index.ts +177 -134
- package/src/index.ts +2 -0
- package/src/tool-loading/index.ts +58 -0
- package/extensions/context/evidence.ts +0 -517
- package/extensions/context/projection.ts +0 -141
- package/extensions/context/state.ts +0 -30
- package/extensions/context/write-scope.ts +0 -109
- package/extensions/explore/ast/format/deps.ts +0 -86
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
- package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
- package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
- package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
- package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
- package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
- package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
- package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
- package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
- package/extensions/explore/ast/languages/odin.ts +0 -407
- package/extensions/explore/ast/queries/impact.ts +0 -250
- package/extensions/explore/outline-injection.ts +0 -151
- package/extensions/explore/read/autoread.ts +0 -118
- package/extensions/working-memory/README.md +0 -17
- package/extensions/working-memory/checkpoint.ts +0 -242
- package/extensions/working-memory/index.ts +0 -341
- package/extensions/working-memory/memory.ts +0 -235
- package/extensions/working-memory/render.ts +0 -185
- package/extensions/working-memory/settings.ts +0 -38
- package/extensions/working-memory/state.ts +0 -158
- package/shared/autoread.ts +0 -234
- package/shared/context-messages.ts +0 -19
- package/shared/outline-injection.ts +0 -56
- /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
- /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
- /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
- /package/{extensions/explore → src}/ast/identity.ts +0 -0
- /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
- /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { keyText, type ExtensionAPI, type Theme } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
6
|
+
import { Marker } from "@shanepadgett/tau-tui";
|
|
7
|
+
import { BoundedTextResultBuilder, truncateBoundedHead } from "../../shared/bounded-text-result.ts";
|
|
8
|
+
import { emitTauEvent, onTauEvent } from "../../shared/events.ts";
|
|
9
|
+
import { createCompleteFileMeta, type ReadCacheMetaV1 } from "../../shared/full-file-knowledge.ts";
|
|
10
|
+
import type { LineRange } from "../../shared/ranges.ts";
|
|
11
|
+
import type { TemporaryOutputStore } from "../../shared/temporary-output-store.ts";
|
|
12
|
+
import type { ToolRowStateStore } from "../../shared/tool-row-state.ts";
|
|
13
|
+
import { formatOutlineEmpty, formatOutlineFile } from "../ast/format/outline.ts";
|
|
14
|
+
import { outlinePath } from "../ast/queries/outline.ts";
|
|
15
|
+
import { showTargets, type ShowView } from "../ast/queries/show.ts";
|
|
16
|
+
import { formatLargeReadOutline, shouldOutlineFullRead } from "../ast/read-policy.ts";
|
|
17
|
+
import { astEngineFor } from "../ast/session.ts";
|
|
18
|
+
import { formatPathForDisplay, resolveExplorePath, stripLeadingAt } from "../ast/traverse.ts";
|
|
19
|
+
|
|
20
|
+
export const FILE_INJECTION_TYPE = "tau.file";
|
|
21
|
+
|
|
22
|
+
const OUTLINE_OPTIONS = { includePrivate: false, includeDocs: false, names: [] as readonly string[] };
|
|
23
|
+
const FILE_RENDER_CHARACTERS = 20_000;
|
|
24
|
+
const FILE_RENDER_LINES = 200;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `full` always injects complete contents, `outline` always injects structure,
|
|
28
|
+
* `show` injects one resolved declaration slice, and `auto` injects contents
|
|
29
|
+
* unless the source is larger than Explore's structural read threshold.
|
|
30
|
+
*/
|
|
31
|
+
export type FileInjectionMode = "full" | "outline" | "auto" | "show";
|
|
32
|
+
|
|
33
|
+
export interface FileInjectionDetails {
|
|
34
|
+
v: 1;
|
|
35
|
+
rowId: string;
|
|
36
|
+
path: string;
|
|
37
|
+
cwd: string;
|
|
38
|
+
source: string;
|
|
39
|
+
batchId: string;
|
|
40
|
+
/** One-based, inclusive ranges used for a partial file read or show slice. */
|
|
41
|
+
ranges?: ReadonlyArray<LineRange>;
|
|
42
|
+
/** Declaration name when kind is show. */
|
|
43
|
+
showName?: string;
|
|
44
|
+
/** Extract view when kind is show. */
|
|
45
|
+
showView?: ShowView;
|
|
46
|
+
/** What was actually injected. `auto` resolves to `full` or `outline`. */
|
|
47
|
+
kind: "full" | "outline" | "show";
|
|
48
|
+
status: "injected" | "failed";
|
|
49
|
+
error?: string;
|
|
50
|
+
readCache?: ReadCacheMetaV1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface PreparedFileInjection {
|
|
54
|
+
customType: typeof FILE_INJECTION_TYPE;
|
|
55
|
+
content: string;
|
|
56
|
+
display: boolean;
|
|
57
|
+
details: FileInjectionDetails;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface FileInjectionFile {
|
|
61
|
+
path: string;
|
|
62
|
+
mode: FileInjectionMode;
|
|
63
|
+
/** One-based, inclusive line ranges. Ranges take precedence over `auto` outlining. */
|
|
64
|
+
ranges?: ReadonlyArray<LineRange>;
|
|
65
|
+
/** Declaration name for mode `show`; may be dotted (`Type.method`). */
|
|
66
|
+
name?: string;
|
|
67
|
+
/** Show extract view; defaults to `declaration`. */
|
|
68
|
+
view?: ShowView;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface FileInjectionRequest {
|
|
72
|
+
cwd: string;
|
|
73
|
+
/** Caller identifier recorded on each message. */
|
|
74
|
+
source: string;
|
|
75
|
+
/** Groups the visible rows produced by one call. */
|
|
76
|
+
batchId: string;
|
|
77
|
+
files: ReadonlyArray<FileInjectionFile>;
|
|
78
|
+
signal?: AbortSignal;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface FileInjectionHost {
|
|
82
|
+
temporaryOutput: TemporaryOutputStore;
|
|
83
|
+
autoOutline(): { enabled: boolean; thresholdLines: number };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function requireHost(host: FileInjectionHost | undefined): FileInjectionHost {
|
|
87
|
+
if (host === undefined) throw new Error("File injection is unavailable: Explore is not loaded");
|
|
88
|
+
return host;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Explore owns the parse engine settings and temporary output store this module needs. */
|
|
92
|
+
export function registerFileInjection(pi: ExtensionAPI, rowState: ToolRowStateStore, next: FileInjectionHost): void {
|
|
93
|
+
onTauEvent(pi, "explore.file-injection", "tau:file-injection.prepare", ({ request, accept }) => {
|
|
94
|
+
accept(prepareFileInjectionWithHost(request, next));
|
|
95
|
+
});
|
|
96
|
+
pi.registerMessageRenderer<FileInjectionDetails>(FILE_INJECTION_TYPE, (message, options, theme) => {
|
|
97
|
+
const details = parseDetails(message.details);
|
|
98
|
+
if (!details) return undefined;
|
|
99
|
+
return new FileInjectionComponent(
|
|
100
|
+
rowState,
|
|
101
|
+
details,
|
|
102
|
+
typeof message.content === "string" ? message.content : "",
|
|
103
|
+
options.expanded,
|
|
104
|
+
theme,
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Builds injection messages without sending them. */
|
|
110
|
+
export async function prepareFileInjection(
|
|
111
|
+
pi: Pick<ExtensionAPI, "events">,
|
|
112
|
+
request: FileInjectionRequest,
|
|
113
|
+
): Promise<PreparedFileInjection[]> {
|
|
114
|
+
let accepted: Promise<PreparedFileInjection[]> | undefined;
|
|
115
|
+
emitTauEvent(pi, "tau:file-injection.prepare", {
|
|
116
|
+
request,
|
|
117
|
+
accept(preparation) {
|
|
118
|
+
accepted ??= preparation;
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
return accepted ?? prepareFileInjectionWithHost(request, undefined);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
type InjectionBase = Omit<FileInjectionDetails, "kind" | "status" | "error" | "readCache">;
|
|
125
|
+
type InjectionKind = FileInjectionDetails["kind"];
|
|
126
|
+
|
|
127
|
+
function injectionKind(mode: FileInjectionMode): InjectionKind {
|
|
128
|
+
if (mode === "outline") return "outline";
|
|
129
|
+
if (mode === "show") return "show";
|
|
130
|
+
return "full";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function buildInjectionBase(request: FileInjectionRequest, index: number, file: FileInjectionFile): InjectionBase {
|
|
134
|
+
const ranges = file.ranges === undefined ? undefined : normalizeLineRanges(file.ranges);
|
|
135
|
+
return {
|
|
136
|
+
v: 1,
|
|
137
|
+
rowId: `${request.batchId}:${index}`,
|
|
138
|
+
path: stripLeadingAt(file.path),
|
|
139
|
+
cwd: request.cwd,
|
|
140
|
+
source: request.source,
|
|
141
|
+
batchId: request.batchId,
|
|
142
|
+
...(ranges === undefined ? {} : { ranges }),
|
|
143
|
+
...(file.mode === "show" && file.name !== undefined ? { showName: file.name } : {}),
|
|
144
|
+
...(file.mode === "show" ? { showView: file.view ?? "declaration" } : {}),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function failedPreparedInjection(base: InjectionBase, kind: InjectionKind, message: string): PreparedFileInjection {
|
|
149
|
+
return {
|
|
150
|
+
customType: FILE_INJECTION_TYPE,
|
|
151
|
+
content: `${base.path}\nInjection failed: ${message}`,
|
|
152
|
+
display: true,
|
|
153
|
+
details: {
|
|
154
|
+
...base,
|
|
155
|
+
kind,
|
|
156
|
+
status: "failed",
|
|
157
|
+
error: message,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function prepareFileInjectionWithHost(
|
|
163
|
+
request: FileInjectionRequest,
|
|
164
|
+
host: FileInjectionHost | undefined,
|
|
165
|
+
): Promise<PreparedFileInjection[]> {
|
|
166
|
+
const prepared: PreparedFileInjection[] = [];
|
|
167
|
+
for (const [index, file] of request.files.entries()) {
|
|
168
|
+
request.signal?.throwIfAborted();
|
|
169
|
+
let base: InjectionBase = {
|
|
170
|
+
v: 1,
|
|
171
|
+
rowId: `${request.batchId}:${index}`,
|
|
172
|
+
path: stripLeadingAt(file.path),
|
|
173
|
+
cwd: request.cwd,
|
|
174
|
+
source: request.source,
|
|
175
|
+
batchId: request.batchId,
|
|
176
|
+
};
|
|
177
|
+
try {
|
|
178
|
+
base = buildInjectionBase(request, index, file);
|
|
179
|
+
prepared.push(await prepareOne(base, file, request.signal, host));
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (request.signal?.aborted) throw error;
|
|
182
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
183
|
+
prepared.push(failedPreparedInjection(base, injectionKind(file.mode), message));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return prepared;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function prepareOne(
|
|
190
|
+
base: InjectionBase,
|
|
191
|
+
file: FileInjectionFile,
|
|
192
|
+
signal: AbortSignal | undefined,
|
|
193
|
+
host: FileInjectionHost | undefined,
|
|
194
|
+
): Promise<PreparedFileInjection> {
|
|
195
|
+
if (file.mode === "show") {
|
|
196
|
+
if (base.ranges !== undefined) throw new Error("Line ranges are not supported for show injection");
|
|
197
|
+
const name = file.name?.trim();
|
|
198
|
+
if (!name) throw new Error("Show injection requires a declaration name");
|
|
199
|
+
return prepareShow(base, name, file.view ?? "declaration", signal);
|
|
200
|
+
}
|
|
201
|
+
if (base.ranges !== undefined) {
|
|
202
|
+
if (file.mode === "outline") throw new Error("Line ranges are supported only for file reads");
|
|
203
|
+
return prepareRangedRead(base, signal);
|
|
204
|
+
}
|
|
205
|
+
if (file.mode === "outline") return prepareOutline(base, signal, requireHost(host));
|
|
206
|
+
if (file.mode === "full") return prepareFull(base, signal);
|
|
207
|
+
const settings = requireHost(host).autoOutline();
|
|
208
|
+
if (!settings.enabled) return prepareFull(base, signal);
|
|
209
|
+
const engine = astEngineFor(base.cwd);
|
|
210
|
+
const absolutePath = resolveExplorePath(base.cwd, base.path);
|
|
211
|
+
if (engine.registry.adapterForPath(absolutePath) === undefined) return prepareFull(base, signal);
|
|
212
|
+
let lineCount: number;
|
|
213
|
+
try {
|
|
214
|
+
lineCount = (await engine.sourceForFile(absolutePath)).ir.lineCount;
|
|
215
|
+
} catch {
|
|
216
|
+
return prepareFull(base, signal);
|
|
217
|
+
}
|
|
218
|
+
if (!shouldOutlineFullRead(lineCount, settings.thresholdLines)) return prepareFull(base, signal);
|
|
219
|
+
return prepareLargeSourceOutline(base, lineCount, signal);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function prepareShow(
|
|
223
|
+
base: InjectionBase,
|
|
224
|
+
name: string,
|
|
225
|
+
view: ShowView,
|
|
226
|
+
signal: AbortSignal | undefined,
|
|
227
|
+
): Promise<PreparedFileInjection> {
|
|
228
|
+
const engine = astEngineFor(base.cwd);
|
|
229
|
+
const abort = signal ?? new AbortController().signal;
|
|
230
|
+
const batch = await showTargets(engine, [{ path: base.path, name }], view, undefined, abort);
|
|
231
|
+
const block = batch.blocks[0];
|
|
232
|
+
if (block === undefined) throw new Error(`No declaration matched ${name} @ ${base.path}`);
|
|
233
|
+
const rangeLabel =
|
|
234
|
+
block.startLine === block.endLine ? `L${block.startLine}` : `L${block.startLine}-${block.endLine}`;
|
|
235
|
+
const messageContent = [
|
|
236
|
+
base.path,
|
|
237
|
+
`${rangeLabel}: ${block.name}`,
|
|
238
|
+
...block.warnings.map((warning) => `warning: ${warning}`),
|
|
239
|
+
block.text,
|
|
240
|
+
].join("\n");
|
|
241
|
+
if (truncateBoundedHead(messageContent).truncated)
|
|
242
|
+
throw new Error("Show target exceeds the complete injection limit; request a narrower symbol");
|
|
243
|
+
return {
|
|
244
|
+
customType: FILE_INJECTION_TYPE,
|
|
245
|
+
content: messageContent,
|
|
246
|
+
display: true,
|
|
247
|
+
details: {
|
|
248
|
+
...base,
|
|
249
|
+
kind: "show",
|
|
250
|
+
status: "injected",
|
|
251
|
+
showName: block.name,
|
|
252
|
+
showView: view,
|
|
253
|
+
ranges: [{ startLine: block.startLine, endLine: block.endLine }],
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function prepareRangedRead(base: InjectionBase, signal: AbortSignal | undefined): Promise<PreparedFileInjection> {
|
|
259
|
+
const ranges = base.ranges;
|
|
260
|
+
if (ranges === undefined) throw new Error("Ranged injection requires line ranges");
|
|
261
|
+
const pathKey = resolve(base.cwd, base.path);
|
|
262
|
+
const bytes = signal ? await readFile(pathKey, { signal }) : await readFile(pathKey);
|
|
263
|
+
const sourceText = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
264
|
+
const lines = sourceText.split("\n");
|
|
265
|
+
const selectedLines = ranges.flatMap((range) => {
|
|
266
|
+
if (range.startLine > lines.length) return [];
|
|
267
|
+
return lines.slice(range.startLine - 1, Math.min(range.endLine, lines.length));
|
|
268
|
+
});
|
|
269
|
+
if (selectedLines.length === 0) throw new Error("Line range starts beyond end of file");
|
|
270
|
+
const selectedText = selectedLines.join("\n");
|
|
271
|
+
const messageContent = `${base.path}\n${selectedText}`;
|
|
272
|
+
if (truncateBoundedHead(messageContent).truncated)
|
|
273
|
+
throw new Error("File range exceeds the complete injection limit; request a smaller range");
|
|
274
|
+
return {
|
|
275
|
+
customType: FILE_INJECTION_TYPE,
|
|
276
|
+
content: messageContent,
|
|
277
|
+
display: true,
|
|
278
|
+
details: { ...base, kind: "full", status: "injected" },
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function prepareFull(base: InjectionBase, signal: AbortSignal | undefined): Promise<PreparedFileInjection> {
|
|
283
|
+
const pathKey = resolve(base.cwd, base.path);
|
|
284
|
+
const bytes = signal ? await readFile(pathKey, { signal }) : await readFile(pathKey);
|
|
285
|
+
const content = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
286
|
+
const messageContent = `${base.path}\n${content}`;
|
|
287
|
+
if (truncateBoundedHead(messageContent).truncated)
|
|
288
|
+
throw new Error("File exceeds the complete injection limit; outline or read a range instead");
|
|
289
|
+
const totalLines = content.split("\n").length;
|
|
290
|
+
return {
|
|
291
|
+
customType: FILE_INJECTION_TYPE,
|
|
292
|
+
content: messageContent,
|
|
293
|
+
display: true,
|
|
294
|
+
details: {
|
|
295
|
+
...base,
|
|
296
|
+
kind: "full",
|
|
297
|
+
status: "injected",
|
|
298
|
+
readCache: createCompleteFileMeta({
|
|
299
|
+
pathKey,
|
|
300
|
+
presentation: "plain",
|
|
301
|
+
servedHash: createHash("sha256").update(content, "utf8").digest("hex"),
|
|
302
|
+
mode: "baseline",
|
|
303
|
+
sourceText: content,
|
|
304
|
+
returnedText: messageContent,
|
|
305
|
+
totalLines,
|
|
306
|
+
summary: `${totalLines} lines`,
|
|
307
|
+
}),
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function outlineFile(base: InjectionBase, signal: AbortSignal | undefined) {
|
|
313
|
+
const engine = astEngineFor(base.cwd);
|
|
314
|
+
const result = await outlinePath(engine, base.path, OUTLINE_OPTIONS, signal ?? new AbortController().signal);
|
|
315
|
+
return { engine, file: result.mode === "file" ? result.file : undefined };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function prepareOutline(
|
|
319
|
+
base: InjectionBase,
|
|
320
|
+
signal: AbortSignal | undefined,
|
|
321
|
+
host: FileInjectionHost,
|
|
322
|
+
): Promise<PreparedFileInjection> {
|
|
323
|
+
const { engine, file } = await outlineFile(base, signal);
|
|
324
|
+
if (!file) throw new Error("Outline injection requires a file path");
|
|
325
|
+
const body =
|
|
326
|
+
file.rows.length === 0 ? formatOutlineEmpty(OUTLINE_OPTIONS.names) : formatOutlineFile(file, engine.cwd, false);
|
|
327
|
+
const builder = new BoundedTextResultBuilder(host.temporaryOutput, "completeBlocks");
|
|
328
|
+
let content: string;
|
|
329
|
+
try {
|
|
330
|
+
await builder.appendBlock(file.path, formatPathForDisplay(file.path, engine.cwd), `${base.path}\n${body}`);
|
|
331
|
+
content = (await builder.finish()).content;
|
|
332
|
+
} catch (error) {
|
|
333
|
+
await builder.abort();
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
customType: FILE_INJECTION_TYPE,
|
|
338
|
+
content,
|
|
339
|
+
display: true,
|
|
340
|
+
details: { ...base, kind: "outline", status: "injected" },
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function prepareLargeSourceOutline(
|
|
345
|
+
base: InjectionBase,
|
|
346
|
+
lineCount: number,
|
|
347
|
+
signal: AbortSignal | undefined,
|
|
348
|
+
): Promise<PreparedFileInjection> {
|
|
349
|
+
const { engine, file } = await outlineFile(base, signal);
|
|
350
|
+
if (!file) return prepareFull(base, signal);
|
|
351
|
+
const outlineText = formatLargeReadOutline(
|
|
352
|
+
file.rows.length === 0 ? "No declarations" : formatOutlineFile(file, engine.cwd, false),
|
|
353
|
+
);
|
|
354
|
+
const messageContent = `${base.path}\n${outlineText}`;
|
|
355
|
+
return {
|
|
356
|
+
customType: FILE_INJECTION_TYPE,
|
|
357
|
+
content: messageContent,
|
|
358
|
+
display: true,
|
|
359
|
+
details: {
|
|
360
|
+
...base,
|
|
361
|
+
kind: "outline",
|
|
362
|
+
status: "injected",
|
|
363
|
+
readCache: createCompleteFileMeta({
|
|
364
|
+
pathKey: resolve(base.cwd, base.path),
|
|
365
|
+
presentation: "plain",
|
|
366
|
+
servedHash: createHash("sha256").update(outlineText, "utf8").digest("hex"),
|
|
367
|
+
mode: "baseline",
|
|
368
|
+
sourceText: outlineText,
|
|
369
|
+
returnedText: messageContent,
|
|
370
|
+
totalLines: lineCount,
|
|
371
|
+
summary: `outline (${lineCount} lines)`,
|
|
372
|
+
}),
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function isInjectionKind(value: unknown): value is InjectionKind {
|
|
378
|
+
return value === "full" || value === "outline" || value === "show";
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function isInjectionStatus(value: unknown): value is FileInjectionDetails["status"] {
|
|
382
|
+
return value === "injected" || value === "failed";
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function isShowView(value: unknown): value is ShowView {
|
|
386
|
+
return (
|
|
387
|
+
value === "signature" ||
|
|
388
|
+
value === "signatureWithDocs" ||
|
|
389
|
+
value === "declaration" ||
|
|
390
|
+
value === "declarationWithImports"
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function parseRequiredBase(details: Record<string, unknown>): InjectionBase | undefined {
|
|
395
|
+
if (
|
|
396
|
+
details.v !== 1 ||
|
|
397
|
+
typeof details.rowId !== "string" ||
|
|
398
|
+
typeof details.path !== "string" ||
|
|
399
|
+
typeof details.cwd !== "string" ||
|
|
400
|
+
typeof details.source !== "string" ||
|
|
401
|
+
typeof details.batchId !== "string"
|
|
402
|
+
) {
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
405
|
+
const ranges = details.ranges === undefined ? undefined : parseRanges(details.ranges);
|
|
406
|
+
if (details.ranges !== undefined && ranges === undefined) return undefined;
|
|
407
|
+
return {
|
|
408
|
+
v: 1,
|
|
409
|
+
rowId: details.rowId,
|
|
410
|
+
path: details.path,
|
|
411
|
+
cwd: details.cwd,
|
|
412
|
+
source: details.source,
|
|
413
|
+
batchId: details.batchId,
|
|
414
|
+
...(ranges === undefined ? {} : { ranges }),
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function parseDetails(value: unknown): FileInjectionDetails | undefined {
|
|
419
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
420
|
+
const details = value as Record<string, unknown>;
|
|
421
|
+
const base = parseRequiredBase(details);
|
|
422
|
+
if (!base || !isInjectionKind(details.kind) || !isInjectionStatus(details.status)) return undefined;
|
|
423
|
+
if (details.kind === "show") {
|
|
424
|
+
if (typeof details.showName !== "string" || details.showName.length === 0) return undefined;
|
|
425
|
+
if (!isShowView(details.showView)) return undefined;
|
|
426
|
+
return {
|
|
427
|
+
...base,
|
|
428
|
+
showName: details.showName,
|
|
429
|
+
showView: details.showView,
|
|
430
|
+
kind: "show",
|
|
431
|
+
status: details.status,
|
|
432
|
+
...(typeof details.error === "string" ? { error: details.error } : {}),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
...base,
|
|
437
|
+
kind: details.kind,
|
|
438
|
+
status: details.status,
|
|
439
|
+
...(typeof details.error === "string" ? { error: details.error } : {}),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function normalizeLineRanges(ranges: ReadonlyArray<LineRange>): LineRange[] {
|
|
444
|
+
if (ranges.length === 0) throw new Error("At least one line range is required");
|
|
445
|
+
const sorted = ranges
|
|
446
|
+
.map((range) => {
|
|
447
|
+
if (
|
|
448
|
+
!Number.isSafeInteger(range.startLine) ||
|
|
449
|
+
!Number.isSafeInteger(range.endLine) ||
|
|
450
|
+
range.startLine < 1 ||
|
|
451
|
+
range.endLine < range.startLine
|
|
452
|
+
)
|
|
453
|
+
throw new Error("Line ranges must use positive, ordered line numbers");
|
|
454
|
+
return { startLine: range.startLine, endLine: range.endLine };
|
|
455
|
+
})
|
|
456
|
+
.sort((left, right) => left.startLine - right.startLine || left.endLine - right.endLine);
|
|
457
|
+
const normalized: LineRange[] = [];
|
|
458
|
+
for (const range of sorted) {
|
|
459
|
+
const previous = normalized[normalized.length - 1];
|
|
460
|
+
if (previous && range.startLine <= previous.endLine + 1) {
|
|
461
|
+
previous.endLine = Math.max(previous.endLine, range.endLine);
|
|
462
|
+
} else {
|
|
463
|
+
normalized.push(range);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return normalized;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function parseRanges(value: unknown): LineRange[] | undefined {
|
|
470
|
+
if (!Array.isArray(value)) return undefined;
|
|
471
|
+
const ranges: LineRange[] = [];
|
|
472
|
+
for (const item of value) {
|
|
473
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return undefined;
|
|
474
|
+
const range = item as Record<string, unknown>;
|
|
475
|
+
const startLine = range.startLine;
|
|
476
|
+
const endLine = range.endLine;
|
|
477
|
+
if (
|
|
478
|
+
typeof startLine !== "number" ||
|
|
479
|
+
typeof endLine !== "number" ||
|
|
480
|
+
!Number.isSafeInteger(startLine) ||
|
|
481
|
+
!Number.isSafeInteger(endLine) ||
|
|
482
|
+
startLine < 1 ||
|
|
483
|
+
endLine < startLine
|
|
484
|
+
)
|
|
485
|
+
return undefined;
|
|
486
|
+
ranges.push({ startLine, endLine });
|
|
487
|
+
}
|
|
488
|
+
return ranges;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
class FileInjectionComponent {
|
|
492
|
+
private readonly rowState: ToolRowStateStore;
|
|
493
|
+
private readonly details: FileInjectionDetails;
|
|
494
|
+
private readonly content: string;
|
|
495
|
+
private readonly expanded: boolean;
|
|
496
|
+
private readonly theme: Theme;
|
|
497
|
+
|
|
498
|
+
constructor(
|
|
499
|
+
rowState: ToolRowStateStore,
|
|
500
|
+
details: FileInjectionDetails,
|
|
501
|
+
content: string,
|
|
502
|
+
expanded: boolean,
|
|
503
|
+
theme: Theme,
|
|
504
|
+
) {
|
|
505
|
+
this.rowState = rowState;
|
|
506
|
+
this.details = details;
|
|
507
|
+
this.content = content;
|
|
508
|
+
this.expanded = expanded;
|
|
509
|
+
this.theme = theme;
|
|
510
|
+
this.rowState.watch(details.rowId, () => this.invalidate());
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
render(width: number): string[] {
|
|
514
|
+
const state =
|
|
515
|
+
this.details.status === "failed"
|
|
516
|
+
? "error"
|
|
517
|
+
: this.rowState.get(this.details.rowId) === "pruned"
|
|
518
|
+
? "warning"
|
|
519
|
+
: "complete";
|
|
520
|
+
const marker = new Marker({
|
|
521
|
+
theme: this.theme,
|
|
522
|
+
state,
|
|
523
|
+
label: this.details.kind === "outline" ? "outline" : this.details.kind === "show" ? "show" : "read",
|
|
524
|
+
parts:
|
|
525
|
+
this.details.kind === "show" && this.details.showName
|
|
526
|
+
? [`${this.details.showName} · ${this.details.path}`]
|
|
527
|
+
: [this.details.path],
|
|
528
|
+
}).render(width);
|
|
529
|
+
if (!this.expanded || this.details.status === "failed") {
|
|
530
|
+
if (this.details.status === "failed") return marker;
|
|
531
|
+
const hint = this.theme.fg("muted", `(${keyText("app.tools.expand")} to expand)`);
|
|
532
|
+
const last = marker.length - 1;
|
|
533
|
+
return marker.map((line, index) => (index === last ? truncateToWidth(`${line} ${hint}`, width, "…") : line));
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const bounded =
|
|
537
|
+
this.content.length > FILE_RENDER_CHARACTERS
|
|
538
|
+
? `${this.content.slice(0, FILE_RENDER_CHARACTERS)}\n…`
|
|
539
|
+
: this.content;
|
|
540
|
+
const content = wrapTextWithAnsi(this.theme.fg("dim", bounded), Math.max(1, width - 1))
|
|
541
|
+
.slice(0, FILE_RENDER_LINES)
|
|
542
|
+
.map((line) => truncateToWidth(` ${line}`, width, "…"));
|
|
543
|
+
return [...marker, ...content];
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
invalidate(): void {}
|
|
547
|
+
}
|
|
@@ -12,7 +12,7 @@ const REQUEST_TIMEOUT_MS = 60_000;
|
|
|
12
12
|
const MAX_ATTEMPTS = 3;
|
|
13
13
|
const RETRY_BASE_DELAY_MS = 500;
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
interface CodexAuth {
|
|
16
16
|
token: string;
|
|
17
17
|
accountId: string;
|
|
18
18
|
}
|
|
@@ -38,7 +38,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
38
38
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
function resolveCodexAuth(token: string): CodexAuth {
|
|
42
42
|
const invalidCredential = () =>
|
|
43
43
|
new Error("The OpenAI Codex credential does not contain a usable ChatGPT account ID. Run /login again.");
|
|
44
44
|
const segments = token.split(".");
|
|
@@ -204,6 +204,51 @@ async function delay(milliseconds: number, signal?: AbortSignal): Promise<void>
|
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
async function postXaiImage(
|
|
208
|
+
route: string,
|
|
209
|
+
body: Record<string, unknown>,
|
|
210
|
+
token: string,
|
|
211
|
+
signal?: AbortSignal,
|
|
212
|
+
): Promise<HttpResponse> {
|
|
213
|
+
const requestSignal = signal
|
|
214
|
+
? AbortSignal.any([signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)])
|
|
215
|
+
: AbortSignal.timeout(REQUEST_TIMEOUT_MS);
|
|
216
|
+
return (await fetch(`${XAI_API_BASE_URL}/images/${route}`, {
|
|
217
|
+
method: "POST",
|
|
218
|
+
headers: {
|
|
219
|
+
Accept: "application/json",
|
|
220
|
+
Authorization: `Bearer ${token}`,
|
|
221
|
+
"Content-Type": "application/json",
|
|
222
|
+
},
|
|
223
|
+
body: JSON.stringify(body),
|
|
224
|
+
signal: requestSignal,
|
|
225
|
+
})) as HttpResponse;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function decodeOkXaiImage(response: HttpResponse): Promise<GeneratedImage> {
|
|
229
|
+
let value: unknown;
|
|
230
|
+
try {
|
|
231
|
+
value = await response.json();
|
|
232
|
+
} catch {
|
|
233
|
+
throw new Error("xAI returned a non-JSON image response");
|
|
234
|
+
}
|
|
235
|
+
return decodeImageResponse(value, "xAI", false);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function throwOrRetryXaiFailure(
|
|
239
|
+
operation: "generation" | "edit",
|
|
240
|
+
response: HttpResponse,
|
|
241
|
+
token: string,
|
|
242
|
+
attempt: number,
|
|
243
|
+
signal?: AbortSignal,
|
|
244
|
+
): Promise<void> {
|
|
245
|
+
const message = serverErrorMessage(await boundedError(response), [token]);
|
|
246
|
+
if (!retryable(response.status) || attempt === MAX_ATTEMPTS) {
|
|
247
|
+
throw new Error(`xAI image ${operation} failed with status ${response.status}${message ? `: ${message}` : ""}`);
|
|
248
|
+
}
|
|
249
|
+
await delay(RETRY_BASE_DELAY_MS * 2 ** (attempt - 1), signal);
|
|
250
|
+
}
|
|
251
|
+
|
|
207
252
|
async function requestXaiImage(
|
|
208
253
|
operation: "generation" | "edit",
|
|
209
254
|
body: Record<string, unknown>,
|
|
@@ -212,43 +257,17 @@ async function requestXaiImage(
|
|
|
212
257
|
): Promise<GeneratedImage> {
|
|
213
258
|
const route = operation === "generation" ? "generations" : "edits";
|
|
214
259
|
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
215
|
-
const requestSignal = signal
|
|
216
|
-
? AbortSignal.any([signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)])
|
|
217
|
-
: AbortSignal.timeout(REQUEST_TIMEOUT_MS);
|
|
218
260
|
let response: HttpResponse;
|
|
219
261
|
try {
|
|
220
|
-
response =
|
|
221
|
-
method: "POST",
|
|
222
|
-
headers: {
|
|
223
|
-
Accept: "application/json",
|
|
224
|
-
Authorization: `Bearer ${token}`,
|
|
225
|
-
"Content-Type": "application/json",
|
|
226
|
-
},
|
|
227
|
-
body: JSON.stringify(body),
|
|
228
|
-
signal: requestSignal,
|
|
229
|
-
})) as HttpResponse;
|
|
262
|
+
response = await postXaiImage(route, body, token, signal);
|
|
230
263
|
} catch (error) {
|
|
231
264
|
if (signal?.aborted) throw signal.reason;
|
|
232
265
|
if (attempt === MAX_ATTEMPTS) throw error;
|
|
233
266
|
await delay(RETRY_BASE_DELAY_MS * 2 ** (attempt - 1), signal);
|
|
234
267
|
continue;
|
|
235
268
|
}
|
|
236
|
-
if (response.ok)
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
value = await response.json();
|
|
240
|
-
} catch {
|
|
241
|
-
throw new Error("xAI returned a non-JSON image response");
|
|
242
|
-
}
|
|
243
|
-
return decodeImageResponse(value, "xAI", false);
|
|
244
|
-
}
|
|
245
|
-
const message = serverErrorMessage(await boundedError(response), [token]);
|
|
246
|
-
if (!retryable(response.status) || attempt === MAX_ATTEMPTS) {
|
|
247
|
-
throw new Error(
|
|
248
|
-
`xAI image ${operation} failed with status ${response.status}${message ? `: ${message}` : ""}`,
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
await delay(RETRY_BASE_DELAY_MS * 2 ** (attempt - 1), signal);
|
|
269
|
+
if (response.ok) return decodeOkXaiImage(response);
|
|
270
|
+
await throwOrRetryXaiFailure(operation, response, token, attempt, signal);
|
|
252
271
|
}
|
|
253
272
|
throw new Error(`xAI image ${operation} failed`);
|
|
254
273
|
}
|