@vibes.diy/api-svc 14.3.94 → 14.3.96

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 (106) hide show
  1. package/analytics.d.ts +8 -0
  2. package/analytics.js +12 -0
  3. package/analytics.js.map +1 -1
  4. package/billing.d.ts +1 -0
  5. package/billing.js +5 -1
  6. package/billing.js.map +1 -1
  7. package/create-handler.js +8 -0
  8. package/create-handler.js.map +1 -1
  9. package/evento-handler-manifest.js +2 -0
  10. package/evento-handler-manifest.js.map +1 -1
  11. package/guest-claim-handoff-token.d.ts +21 -0
  12. package/guest-claim-handoff-token.js +55 -0
  13. package/guest-claim-handoff-token.js.map +1 -0
  14. package/intern/agent-session.d.ts +24 -24
  15. package/intern/backend-egress.d.ts +1 -0
  16. package/intern/backend-egress.js +7 -1
  17. package/intern/backend-egress.js.map +1 -1
  18. package/intern/builder-close-overlay.d.ts +2 -0
  19. package/intern/builder-close-overlay.js +16 -0
  20. package/intern/builder-close-overlay.js.map +1 -0
  21. package/intern/builder-refusal-fallback.d.ts +21 -0
  22. package/intern/builder-refusal-fallback.js +23 -0
  23. package/intern/builder-refusal-fallback.js.map +1 -0
  24. package/intern/components/vibe-page.js +9 -1
  25. package/intern/components/vibe-page.js.map +1 -1
  26. package/intern/fetch-url-tool.d.ts +2 -1
  27. package/intern/fetch-url-tool.js +1 -0
  28. package/intern/fetch-url-tool.js.map +1 -1
  29. package/intern/get-model-defaults.d.ts +2 -1
  30. package/intern/get-model-defaults.js +15 -6
  31. package/intern/get-model-defaults.js.map +1 -1
  32. package/intern/get-vibe-route-hints.d.ts +2 -2
  33. package/intern/get-vibe-route-hints.js +3 -7
  34. package/intern/get-vibe-route-hints.js.map +1 -1
  35. package/intern/guest-mask.d.ts +6 -0
  36. package/intern/guest-mask.js +12 -0
  37. package/intern/guest-mask.js.map +1 -1
  38. package/intern/in-flight-turns.js.map +1 -1
  39. package/intern/instant-build.d.ts +41 -5
  40. package/intern/instant-build.js +233 -31
  41. package/intern/instant-build.js.map +1 -1
  42. package/intern/load-selected-backend.d.ts +1 -0
  43. package/intern/load-selected-backend.js +1 -1
  44. package/intern/load-selected-backend.js.map +1 -1
  45. package/intern/opens-check.js +3 -0
  46. package/intern/opens-check.js.map +1 -1
  47. package/intern/prompt-assembly.js +3 -0
  48. package/intern/prompt-assembly.js.map +1 -1
  49. package/intern/save-repair-io.d.ts +1 -0
  50. package/intern/save-repair-io.js +1 -1
  51. package/intern/save-repair-io.js.map +1 -1
  52. package/intern/turn-error-kind.d.ts +2 -0
  53. package/intern/turn-error-kind.js +18 -0
  54. package/intern/turn-error-kind.js.map +1 -0
  55. package/intern/turn-size.d.ts +7 -0
  56. package/intern/turn-size.js +45 -0
  57. package/intern/turn-size.js.map +1 -0
  58. package/intern/work-tools.js +3 -0
  59. package/intern/work-tools.js.map +1 -1
  60. package/models.json +42 -20
  61. package/package.json +13 -13
  62. package/public/agent-turn-action.js +3 -0
  63. package/public/agent-turn-action.js.map +1 -1
  64. package/public/app-settings-snapshot.js +2 -13
  65. package/public/app-settings-snapshot.js.map +1 -1
  66. package/public/claim-guest-principal.js +13 -6
  67. package/public/claim-guest-principal.js.map +1 -1
  68. package/public/codegen-turn-route.d.ts +2 -1
  69. package/public/codegen-turn-route.js +1 -0
  70. package/public/codegen-turn-route.js.map +1 -1
  71. package/public/ensure-user-settings.js +6 -3
  72. package/public/ensure-user-settings.js.map +1 -1
  73. package/public/fork-app.js +1 -4
  74. package/public/fork-app.js.map +1 -1
  75. package/public/get-app-by-fsid.js +8 -1
  76. package/public/get-app-by-fsid.js.map +1 -1
  77. package/public/handle-prompt-context.d.ts +3 -1
  78. package/public/handle-prompt-context.js +10 -1
  79. package/public/handle-prompt-context.js.map +1 -1
  80. package/public/handler-llm-request.d.ts +3 -1
  81. package/public/handler-llm-request.js +2 -1
  82. package/public/handler-llm-request.js.map +1 -1
  83. package/public/list-app-models.js +7 -6
  84. package/public/list-app-models.js.map +1 -1
  85. package/public/llm-dispatch-fallback.d.ts +1 -1
  86. package/public/mint-guest-claim-handoff.d.ts +3 -0
  87. package/public/mint-guest-claim-handoff.js +37 -0
  88. package/public/mint-guest-claim-handoff.js.map +1 -0
  89. package/public/prompt-chat-section.js +1 -0
  90. package/public/prompt-chat-section.js.map +1 -1
  91. package/public/prompt-section-actions.js +4 -0
  92. package/public/prompt-section-actions.js.map +1 -1
  93. package/public/prompt-turn-settled.js +8 -0
  94. package/public/prompt-turn-settled.js.map +1 -1
  95. package/public/prompt-turn-types.d.ts +2 -0
  96. package/public/request-flow.js.map +1 -1
  97. package/public/stream-assembly.d.ts +5 -2
  98. package/public/stream-assembly.js +104 -5
  99. package/public/stream-assembly.js.map +1 -1
  100. package/types.d.ts +2 -0
  101. package/types.js.map +1 -1
  102. package/usage-report/codegen-delivery-alert-util.d.ts +37 -1
  103. package/usage-report/codegen-delivery-alert-util.js +157 -28
  104. package/usage-report/codegen-delivery-alert-util.js.map +1 -1
  105. package/usage-report/codegen-delivery-alert.js +17 -3
  106. package/usage-report/codegen-delivery-alert.js.map +1 -1
@@ -1,5 +1,6 @@
1
1
  import { Result } from "@adviser/cement";
2
2
  import { type LLMMessage, type SseUsage } from "@vibes.diy/call-ai-v2";
3
+ import { type InstantRepairModelName } from "@vibes.diy/api-types";
3
4
  import type { VibesApiSQLCtx } from "../types.js";
4
5
  import { type SynthLook } from "./import-synth.js";
5
6
  import { type OpensFinding } from "./opens-check.js";
@@ -7,7 +8,7 @@ import { type IntactnessDefect, type LineBounds } from "./repair-ladder.js";
7
8
  import { MercuryCircuitBreaker } from "./mercury-resilience.js";
8
9
  export type InstantCandidateName = "mercury" | "haiku";
9
10
  export declare const INSTANT_MERCURY_FANOUT = 4;
10
- export type InstantRepairMode = "router" | "ladder" | "reemit";
11
+ export type InstantRepairMode = "sr" | "router" | "ladder" | "reemit";
11
12
  export declare const INSTANT_REPAIR_MODE: InstantRepairMode;
12
13
  export declare const INSTANT_LADDER_ROUNDS = 4;
13
14
  export declare const INSTANT_THROTTLED_ROUNDS_MAX = 6;
@@ -23,8 +24,8 @@ export declare const INSTANT_HAIKU_CALL_MS = 90000;
23
24
  export declare const INSTANT_STAGE_BUDGET_MS = 150000;
24
25
  export declare const INSTANT_MAX_ATTEMPTS = 5;
25
26
  export declare const INSTANT_MERCURY_BREAKER: MercuryCircuitBreaker;
26
- export interface InstantModelChoice {
27
- readonly name: InstantCandidateName;
27
+ export interface InstantCallModel {
28
+ readonly name: string;
28
29
  readonly id: string;
29
30
  readonly reasoning: {
30
31
  readonly effort: "none";
@@ -33,8 +34,18 @@ export interface InstantModelChoice {
33
34
  };
34
35
  readonly reasoningEffort?: string;
35
36
  readonly callMs: number;
37
+ readonly maxTokens?: number;
38
+ }
39
+ export interface InstantModelChoice extends InstantCallModel {
40
+ readonly name: InstantCandidateName;
36
41
  readonly maxAttempts: number;
37
42
  }
43
+ export interface InstantRepairModel extends InstantCallModel {
44
+ readonly choice: InstantRepairModelName;
45
+ readonly cacheSystem: boolean;
46
+ }
47
+ export declare const INSTANT_REPAIR_MODELS: Readonly<Record<InstantRepairModelName, InstantRepairModel>>;
48
+ export declare function instantRepairModel(setting: unknown): InstantRepairModel;
38
49
  export declare function instantModelChoice(name: InstantCandidateName): InstantModelChoice;
39
50
  export declare const INSTANT_PARAGRAPH: string;
40
51
  export declare function composeInstantBrief(sentence: string): string;
@@ -70,7 +81,7 @@ export declare function autofixAndCheck(args: {
70
81
  readonly baseline?: ReadonlySet<string>;
71
82
  }): InstantGateResult;
72
83
  export interface InstantModelRequest {
73
- readonly model: InstantModelChoice;
84
+ readonly model: InstantCallModel;
74
85
  readonly messages: readonly LLMMessage[];
75
86
  readonly signal: AbortSignal;
76
87
  }
@@ -83,6 +94,10 @@ export interface InstantModelReply {
83
94
  readonly usage?: InstantRoundUsage;
84
95
  }
85
96
  export type InstantModelCall = (req: InstantModelRequest) => Promise<Result<InstantModelReply>>;
97
+ export declare class InstantPaidReplyError extends Error {
98
+ readonly usage: InstantRoundUsage | undefined;
99
+ constructor(message: string, usage: InstantRoundUsage | undefined);
100
+ }
86
101
  export declare function instantModelCallFor(vctx: Pick<VibesApiSQLCtx, "llmRequest" | "params">): InstantModelCall;
87
102
  export interface InstantCandidateSpec {
88
103
  readonly model: InstantModelChoice;
@@ -109,6 +124,7 @@ export interface InstantCandidateOutcome {
109
124
  readonly error?: string;
110
125
  readonly repairs: readonly InstantRepairRound[];
111
126
  readonly repairsDropped?: number;
127
+ readonly repairModel?: string;
112
128
  }
113
129
  export interface InstantRepairGap {
114
130
  readonly rung: string;
@@ -116,9 +132,10 @@ export interface InstantRepairGap {
116
132
  readonly result: string;
117
133
  }
118
134
  export interface InstantRepairRound {
119
- readonly kind: "ladder" | "reemit";
135
+ readonly kind: "ladder" | "reemit" | "sr";
120
136
  readonly mode: string;
121
137
  readonly ms: number;
138
+ readonly model?: string;
122
139
  readonly result?: string;
123
140
  readonly gaps?: readonly InstantRepairGap[];
124
141
  }
@@ -133,6 +150,22 @@ export interface IntactRefusal {
133
150
  }
134
151
  export declare function repairIntactness(baseline: IntactBaseline, after: ReadonlyMap<string, string>, exempt?: ReadonlySet<string>): IntactRefusal | undefined;
135
152
  export declare function intactnessReask(defect: IntactnessDefect): string;
153
+ export declare const SR_NOTE_NO_MATCH = "Your last SEARCH blocks did not match the file; copy the SEARCH lines exactly from the file above.";
154
+ export declare const SR_NOTE_NO_EDITS = "Your last reply had no SEARCH/REPLACE blocks.";
155
+ export declare const SR_NOTE_NO_CHANGE = "Your last edits left the file exactly as it was; change the lines the findings name.";
156
+ export declare function srIntactnessNote(defect: IntactnessDefect): string;
157
+ export declare function composeSrRepairAsk(args: {
158
+ readonly findings: readonly string[];
159
+ readonly paths: readonly string[];
160
+ readonly note?: string;
161
+ }): string;
162
+ export declare function srRepairMessages(args: {
163
+ readonly system: readonly LLMMessage[];
164
+ readonly files: ReadonlyMap<string, string>;
165
+ readonly findings: readonly string[];
166
+ readonly note?: string;
167
+ readonly model: InstantRepairModel;
168
+ }): LLMMessage[];
136
169
  export declare function runInstantCandidate(args: {
137
170
  readonly call: InstantModelCall;
138
171
  readonly spec: InstantCandidateSpec;
@@ -148,6 +181,7 @@ export declare function runInstantCandidate(args: {
148
181
  readonly baseline?: ReadonlySet<string>;
149
182
  readonly repairMode?: InstantRepairMode;
150
183
  readonly ladderRounds?: number;
184
+ readonly repairModel?: InstantRepairModel;
151
185
  }): Promise<InstantCandidateOutcome>;
152
186
  export interface InstantRaceResult {
153
187
  readonly winner?: InstantCandidateOutcome;
@@ -177,6 +211,7 @@ export interface InstantReceipt {
177
211
  readonly abortedInFlight?: true;
178
212
  readonly repairs?: readonly InstantRepairRound[];
179
213
  readonly repairsDropped?: number;
214
+ readonly repairModel?: string;
180
215
  }[];
181
216
  readonly winner?: string;
182
217
  readonly winnerCandidate?: string;
@@ -192,6 +227,7 @@ export interface InstantAssembly {
192
227
  readonly instruction: string;
193
228
  readonly look: SynthLook;
194
229
  readonly base: ReadonlyMap<string, string>;
230
+ readonly repairModel?: InstantRepairModelName;
195
231
  }
196
232
  export interface InstantBuildOutcome {
197
233
  readonly response: Response;
@@ -3,17 +3,18 @@ import { type } from "arktype";
3
3
  import { makeBaseSystemPrompt } from "@vibes.diy/prompts";
4
4
  import { createBlockStream, isCodeEnd, } from "@vibes.diy/call-ai-v2";
5
5
  import { lazyType } from "@vibes.diy/api-types/lazy-type";
6
+ import { instantRepairModelName } from "@vibes.diy/api-types";
6
7
  import { createBlockAccumulator, createStreamingResolver, resolveCodeBlocks } from "./prompt-streaming.js";
7
8
  import { synthesizeImports } from "./import-synth.js";
8
9
  import { checkOpens, collapseOpensFindings, newOpensFindings, opensBaseline, opensFindingKey, OPENS_ADVISORY_CLASSES, } from "./opens-check.js";
9
10
  import { fillDefect, gapLadder, identFill, intactnessDefect, mergeGaps, parseModule, regionFill, regionMessages, splice, } from "./repair-ladder.js";
10
11
  import { saveRepairFindings } from "./save-repair.js";
11
- import { composeRepairAsk, renderRepairFiles } from "./save-repair-io.js";
12
+ import { composeRepairAsk, fenceLang, renderRepairFiles } from "./save-repair-io.js";
12
13
  import { createPromptAssetFetch, promptsPkgBaseUrl } from "./prompt-asset-fetch.js";
13
14
  import { loadActiveSettings, lookOptionForTurn } from "./prompt-assembly.js";
14
15
  import { MercuryCircuitBreaker } from "./mercury-resilience.js";
15
16
  export const INSTANT_MERCURY_FANOUT = 4;
16
- export const INSTANT_REPAIR_MODE = "router";
17
+ export const INSTANT_REPAIR_MODE = "sr";
17
18
  export const INSTANT_LADDER_ROUNDS = 4;
18
19
  export const INSTANT_THROTTLED_ROUNDS_MAX = 6;
19
20
  export const INSTANT_REPAIR_ROWS_MAX = 24;
@@ -30,6 +31,30 @@ export const INSTANT_HAIKU_CALL_MS = 90_000;
30
31
  export const INSTANT_STAGE_BUDGET_MS = 150_000;
31
32
  export const INSTANT_MAX_ATTEMPTS = 5;
32
33
  export const INSTANT_MERCURY_BREAKER = new MercuryCircuitBreaker();
34
+ export const INSTANT_REPAIR_MODELS = {
35
+ deepseek: {
36
+ choice: "deepseek",
37
+ name: "repair:deepseek",
38
+ id: "deepseek/deepseek-v4.1-flash:nitro",
39
+ reasoning: { enabled: false },
40
+ cacheSystem: false,
41
+ callMs: 30_000,
42
+ maxTokens: 16_000,
43
+ },
44
+ haiku: {
45
+ choice: "haiku",
46
+ name: "repair:haiku",
47
+ id: "anthropic/claude-haiku-5.5:nitro",
48
+ reasoning: { effort: "none" },
49
+ reasoningEffort: "none",
50
+ cacheSystem: true,
51
+ callMs: 30_000,
52
+ maxTokens: 16_000,
53
+ },
54
+ };
55
+ export function instantRepairModel(setting) {
56
+ return INSTANT_REPAIR_MODELS[instantRepairModelName(setting)];
57
+ }
33
58
  export function instantModelChoice(name) {
34
59
  switch (name) {
35
60
  case "mercury":
@@ -187,6 +212,20 @@ export function autofixAndCheck(args) {
187
212
  notes: fixed.notes,
188
213
  };
189
214
  }
215
+ export class InstantPaidReplyError extends Error {
216
+ usage;
217
+ constructor(message, usage) {
218
+ super(message);
219
+ this.usage = usage;
220
+ this.name = "InstantPaidReplyError";
221
+ }
222
+ }
223
+ function settledUsage(reply) {
224
+ if (reply.isOk())
225
+ return reply.Ok().usage;
226
+ const error = reply.Err();
227
+ return error instanceof InstantPaidReplyError ? error.usage : undefined;
228
+ }
190
229
  const completionBody = lazyType(() => type({
191
230
  "model?": "string",
192
231
  choices: type({ message: { content: "string | null" }, "finish_reason?": "string | null" }).array(),
@@ -202,7 +241,7 @@ export function instantModelCallFor(vctx) {
202
241
  const body = {
203
242
  model: req.model.id,
204
243
  stream: false,
205
- max_tokens: INSTANT_MAX_OUTPUT_TOKENS,
244
+ max_tokens: req.model.maxTokens ?? INSTANT_MAX_OUTPUT_TOKENS,
206
245
  reasoning: { ...req.model.reasoning },
207
246
  messages: [...req.messages],
208
247
  };
@@ -220,28 +259,23 @@ export function instantModelCallFor(vctx) {
220
259
  const parsed = completionBody(rJson.Ok());
221
260
  if (parsed instanceof type.errors)
222
261
  return Result.Err(`instant ${req.model.name}: reply: ${parsed.summary}`);
262
+ const u = parsed.usage;
263
+ const servedModel = parsed.model ?? req.model.id;
264
+ const usage = u === undefined
265
+ ? undefined
266
+ : {
267
+ prompt_tokens: u.prompt_tokens ?? 0,
268
+ completion_tokens: u.completion_tokens ?? 0,
269
+ total_tokens: u.total_tokens ?? (u.prompt_tokens ?? 0) + (u.completion_tokens ?? 0),
270
+ model: servedModel,
271
+ ...(req.model.reasoningEffort === undefined ? {} : { reasoningEffort: req.model.reasoningEffort }),
272
+ ...(u.cost === undefined ? {} : { cost: u.cost }),
273
+ };
223
274
  const content = parsed.choices[0]?.message.content;
224
275
  if (content === undefined || content === null || content.trim().length === 0) {
225
- return Result.Err(`instant ${req.model.name}: empty reply (finish ${parsed.choices[0]?.finish_reason ?? "unknown"})`);
276
+ return Result.Err(new InstantPaidReplyError(`instant ${req.model.name}: empty reply (finish ${parsed.choices[0]?.finish_reason ?? "unknown"})`, usage));
226
277
  }
227
- const u = parsed.usage;
228
- const servedModel = parsed.model ?? req.model.id;
229
- return Result.Ok({
230
- text: content,
231
- servedModel,
232
- ...(u === undefined
233
- ? {}
234
- : {
235
- usage: {
236
- prompt_tokens: u.prompt_tokens ?? 0,
237
- completion_tokens: u.completion_tokens ?? 0,
238
- total_tokens: u.total_tokens ?? (u.prompt_tokens ?? 0) + (u.completion_tokens ?? 0),
239
- model: servedModel,
240
- ...(req.model.reasoningEffort === undefined ? {} : { reasoningEffort: req.model.reasoningEffort }),
241
- ...(u.cost === undefined ? {} : { cost: u.cost }),
242
- },
243
- }),
244
- });
278
+ return Result.Ok({ text: content, servedModel, ...(usage === undefined ? {} : { usage }) });
245
279
  };
246
280
  }
247
281
  function isThrottle(error) {
@@ -275,11 +309,13 @@ async function callWithin(call, req, cancel) {
275
309
  clearTimeout(timer);
276
310
  cancel.removeEventListener("abort", cancelled);
277
311
  });
312
+ const usage = settledUsage(reply);
313
+ const paid = usage === undefined ? {} : { usage };
278
314
  if (cancel.aborted)
279
- return { kind: "cancelled" };
315
+ return { kind: "cancelled", ...paid };
280
316
  if (abort.signal.aborted)
281
- return { kind: "deadline" };
282
- return { kind: "reply", reply };
317
+ return { kind: "deadline", ...paid };
318
+ return { kind: "reply", reply, ...paid };
283
319
  }
284
320
  function pauseUnlessCancelled(ms, cancel) {
285
321
  return new Promise((resolve) => {
@@ -339,6 +375,63 @@ export function intactnessReask(defect) {
339
375
  function isSourcePath(path) {
340
376
  return /\.(jsx?|tsx?|mjs)$/.test(path);
341
377
  }
378
+ export const SR_NOTE_NO_MATCH = "Your last SEARCH blocks did not match the file; copy the SEARCH lines exactly from the file above.";
379
+ export const SR_NOTE_NO_EDITS = "Your last reply had no SEARCH/REPLACE blocks.";
380
+ export const SR_NOTE_NO_CHANGE = "Your last edits left the file exactly as it was; change the lines the findings name.";
381
+ export function srIntactnessNote(defect) {
382
+ switch (defect) {
383
+ case "shrank":
384
+ return "Your last edits left part of the app out; keep every line that already worked.";
385
+ case "grew":
386
+ return "Your last edits rewrote far more than the fix needed; change only what the findings require.";
387
+ case "names":
388
+ return "Your last edits dropped functions, components or variables the app still uses; keep every one that already worked.";
389
+ case "loop":
390
+ return "Your last edits repeat the same lines over and over; write each line once.";
391
+ }
392
+ }
393
+ export function composeSrRepairAsk(args) {
394
+ const names = args.paths.map((path) => path.replace(/^\/+/, ""));
395
+ const first = names[0] ?? "App.jsx";
396
+ const target = names.length > 1 ? `the files above (${names.join(", ")})` : first;
397
+ return (`Opening the app in the browser reported:\n\n${args.findings.slice(0, 6).join("\n")}\n\n` +
398
+ (args.note === undefined ? "" : `${args.note}\n\n`) +
399
+ `Reply with SEARCH/REPLACE edits to ${target}, the same shape as an edit turn, and nothing else:\n\n` +
400
+ `### \`${first}\`\n\`\`\`${fenceLang(first)}\n<<<<<<< SEARCH\n(exact lines from the current file)\n=======\n(the replacement)\n>>>>>>> REPLACE\n\`\`\`\n\n` +
401
+ (names.length > 1 ? "Edit each other file the same way, under its own `### `path`` heading.\n" : "") +
402
+ "Each SEARCH must match the current file exactly, once. Change only what the fix needs; keep everything that already worked.");
403
+ }
404
+ function withoutCache(part) {
405
+ if (part.type === "text")
406
+ return { type: "text", text: part.text };
407
+ if (part.type === "image_url")
408
+ return { type: "image_url", image_url: part.image_url };
409
+ return part;
410
+ }
411
+ export function srRepairMessages(args) {
412
+ const system = args.system.flatMap((message) => message.role === "system" && Array.isArray(message.content)
413
+ ? [{ role: "system", content: message.content.map(withoutCache) }]
414
+ : []);
415
+ const last = system[system.length - 1];
416
+ if (args.model.cacheSystem && last !== undefined) {
417
+ const at = last.content.map((part) => part.type).lastIndexOf("text");
418
+ const part = last.content[at];
419
+ if (part !== undefined && part.type === "text") {
420
+ last.content[at] = { type: "text", text: part.text, cache_control: { type: "ephemeral" } };
421
+ }
422
+ }
423
+ const files = renderRepairFiles({ vfs: args.files, intro: "Here is the app." });
424
+ const ask = composeSrRepairAsk({
425
+ findings: args.findings,
426
+ paths: [...args.files.keys()].sort((a, b) => Number(b === "/App.jsx") - Number(a === "/App.jsx")),
427
+ ...(args.note === undefined ? {} : { note: args.note }),
428
+ });
429
+ return [
430
+ ...system,
431
+ { role: "assistant", content: [{ type: "text", text: files }] },
432
+ { role: "user", content: [{ type: "text", text: ask }] },
433
+ ];
434
+ }
342
435
  async function ladderRound(args) {
343
436
  const t0 = args.now();
344
437
  const ids = findingIds(args.findings);
@@ -376,6 +469,8 @@ async function ladderRound(args) {
376
469
  userFirst: args.model.name === "haiku",
377
470
  });
378
471
  const answer = await callWithin(args.call, { model: args.model, messages, timeoutMs: args.timeoutMs }, args.cancel);
472
+ if (answer.usage !== undefined)
473
+ usage.push(answer.usage);
379
474
  if (answer.kind === "cancelled")
380
475
  return { result: "rejected:cancelled", climbs: false };
381
476
  if (answer.kind === "deadline")
@@ -386,8 +481,6 @@ async function ladderRound(args) {
386
481
  : { result: "rejected:error", climbs: false };
387
482
  }
388
483
  const reply = answer.reply.Ok();
389
- if (reply.usage !== undefined)
390
- usage.push(reply.usage);
391
484
  const raw = regionFill(reply.text, group.src.slice(group.gap.start, group.gap.end));
392
485
  if (raw === undefined)
393
486
  return { result: "rejected:no-fill", climbs: true };
@@ -465,7 +558,9 @@ export async function runInstantCandidate(args) {
465
558
  const now = args.now ?? Date.now;
466
559
  const random = args.random ?? Math.random;
467
560
  const repairMode = args.repairMode ?? INSTANT_REPAIR_MODE;
468
- let route = repairMode === "router" ? undefined : repairMode;
561
+ const repairModel = args.repairModel ?? instantRepairModel(undefined);
562
+ let repairModelCalled = false;
563
+ let route = repairMode === "router" ? undefined : repairMode === "sr" ? "reemit" : repairMode;
469
564
  const ladderBudget = () => (route === "ladder" ? (args.ladderRounds ?? INSTANT_LADDER_ROUNDS) : 0);
470
565
  const modeLabel = () => (repairMode === "router" ? `router:${route ?? "none"}` : repairMode);
471
566
  const { model } = args.spec;
@@ -512,6 +607,7 @@ export async function runInstantCandidate(args) {
512
607
  findings: firstFindings,
513
608
  repairs,
514
609
  ...(repairsDropped > 0 ? { repairsDropped } : {}),
610
+ ...(repairModelCalled ? { repairModel: repairModel.id } : {}),
515
611
  ...extra,
516
612
  };
517
613
  }
@@ -522,7 +618,13 @@ export async function runInstantCandidate(args) {
522
618
  function exhausted(error) {
523
619
  if (overridable === undefined)
524
620
  return end("fail", { error });
525
- record({ kind: overridable.kind, mode: modeLabel(), ms: 0, result: "accepted:gate-overridden" });
621
+ record({
622
+ kind: overridable.kind,
623
+ mode: overridable.kind === "sr" ? "sr" : modeLabel(),
624
+ ms: 0,
625
+ ...(overridable.kind === "sr" ? { model: repairModel.id } : {}),
626
+ result: "accepted:gate-overridden",
627
+ });
526
628
  return end("pass", { vfs: overridable.gated.vfs, changed: [...new Set([...changed, ...overridable.touched])] });
527
629
  }
528
630
  function waitWithin(ms) {
@@ -542,6 +644,101 @@ export async function runInstantCandidate(args) {
542
644
  messages.push({ role: "user", content: [{ type: "text", text: composeRepairAsk(findings) }] });
543
645
  reemitStartedAt = now();
544
646
  }
647
+ async function srRepair() {
648
+ repairModelCalled = true;
649
+ let note;
650
+ for (;;) {
651
+ if (args.cancel.aborted)
652
+ return end("cancelled", {});
653
+ const left = args.deadlineAt - now();
654
+ if (left <= 0)
655
+ return exhausted(`stage budget spent after ${attempts} call(s): ${lastError}`);
656
+ if (paidLeft() <= 0)
657
+ return exhausted(`${attempts} calls without a passing answer: ${lastError}`);
658
+ attempts++;
659
+ const startedAt = now();
660
+ const row = (result) => record({ kind: "sr", mode: "sr", model: repairModel.id, ms: now() - startedAt, result });
661
+ const request = srRepairMessages({
662
+ system,
663
+ files: sourceFiles(current),
664
+ findings: liveFindings,
665
+ ...(note === undefined ? {} : { note }),
666
+ model: repairModel,
667
+ });
668
+ const answer = await callWithin(args.call, { model: repairModel, messages: request, timeoutMs: Math.min(repairModel.callMs, left) }, args.cancel);
669
+ if (answer.usage !== undefined)
670
+ usage.push(answer.usage);
671
+ if (answer.kind === "cancelled")
672
+ return end("cancelled", { abortedInFlight: true });
673
+ if (answer.kind === "deadline") {
674
+ lastError = `repair call ${attempts} passed its deadline`;
675
+ row("rejected:deadline");
676
+ continue;
677
+ }
678
+ if (answer.reply.isErr()) {
679
+ lastError = answer.reply.Err().message;
680
+ providerErrors++;
681
+ if (isThrottle(answer.reply.Err())) {
682
+ throttled++;
683
+ row("rejected:throttled");
684
+ await waitWithin(throttleWaitMs(providerErrors, random));
685
+ continue;
686
+ }
687
+ row("rejected:error");
688
+ await waitWithin(Math.min(8_000, 1_000 * 2 ** (providerErrors - 1)));
689
+ continue;
690
+ }
691
+ providerErrors = 0;
692
+ const reply = answer.reply.Ok();
693
+ const parsed = await parseCandidateText(reply.text, current);
694
+ if (parsed.isErr()) {
695
+ lastError = "the repair reply held no SEARCH/REPLACE block";
696
+ note = SR_NOTE_NO_EDITS;
697
+ row("rejected:no-edit");
698
+ continue;
699
+ }
700
+ const edit = parsed.Ok();
701
+ if (edit.changed.every((path) => edit.vfs.get(path) === current.get(path))) {
702
+ if (edit.applyErrors.length > 0) {
703
+ lastError = edit.applyErrors.slice(0, 3).join("; ");
704
+ note = SR_NOTE_NO_MATCH;
705
+ row("rejected:no-match");
706
+ }
707
+ else {
708
+ note = SR_NOTE_NO_CHANGE;
709
+ row("rejected:no-change");
710
+ }
711
+ continue;
712
+ }
713
+ const nextChanged = new Set([...changed, ...edit.changed]);
714
+ const checked = autofixAndCheck({ vfs: edit.vfs, changed: [...nextChanged], look: args.look, baseline });
715
+ const gated = edit.applyErrors.length === 0
716
+ ? checked
717
+ : { ...checked, opens: false, findings: [...edit.applyErrors, ...checked.findings] };
718
+ const gutted = preRepair === undefined ? undefined : repairIntactness(preRepair, gated.vfs, repairNames);
719
+ if (gutted !== undefined) {
720
+ if (gated.opens)
721
+ noteOverridable({ gated, ratio: gutted.ratio, touched: [...nextChanged], kind: "sr" });
722
+ lastError = `the repair failed the intactness gate (${gutted.path}: ${gutted.defect})`;
723
+ note = srIntactnessNote(gutted.defect);
724
+ row(`rejected:gutted:${gutted.defect}`);
725
+ continue;
726
+ }
727
+ if (gated.opens) {
728
+ row("accepted");
729
+ return end("pass", { vfs: gated.vfs, changed: [...nextChanged] });
730
+ }
731
+ row("rejected:still");
732
+ note = undefined;
733
+ changed = nextChanged;
734
+ current = gated.vfs;
735
+ liveFindings = gated.findings;
736
+ lastError = gated.findings.slice(0, 3).join("; ");
737
+ for (const finding of gated.blocking)
738
+ if (finding.name !== undefined)
739
+ repairNames.add(finding.name);
740
+ }
741
+ }
545
742
  for (;;) {
546
743
  if (args.cancel.aborted)
547
744
  return end("cancelled", {});
@@ -557,6 +754,8 @@ export async function runInstantCandidate(args) {
557
754
  }
558
755
  attempts++;
559
756
  const answer = await callWithin(args.call, { model, messages, timeoutMs: Math.min(model.callMs, left) }, args.cancel);
757
+ if (answer.usage !== undefined)
758
+ usage.push(answer.usage);
560
759
  if (answer.kind === "cancelled")
561
760
  return end("cancelled", { abortedInFlight: true });
562
761
  if (answer.kind === "deadline") {
@@ -580,8 +779,6 @@ export async function runInstantCandidate(args) {
580
779
  }
581
780
  providerErrors = 0;
582
781
  const reply = answer.reply.Ok();
583
- if (reply.usage !== undefined)
584
- usage.push(reply.usage);
585
782
  const reemitMs = reemitStartedAt === undefined ? undefined : now() - reemitStartedAt;
586
783
  reemitStartedAt = undefined;
587
784
  const parsed = await parseCandidateText(reply.text, current);
@@ -629,6 +826,8 @@ export async function runInstantCandidate(args) {
629
826
  if (finding.name !== undefined)
630
827
  repairNames.add(finding.name);
631
828
  current = gated.vfs;
829
+ if (repairMode === "sr" && applyErrors === 0)
830
+ return srRepair();
632
831
  route ??=
633
832
  gated.blocking.some((finding) => INSTANT_REEMIT_CLASSES.has(finding.class)) ||
634
833
  gated.unlocated > 0 ||
@@ -737,6 +936,7 @@ export function instantReceipt(race) {
737
936
  ...(outcome.throttled > 0 ? { throttled: outcome.throttled } : {}),
738
937
  ...(outcome.repairs.length > 0 ? { repairs: outcome.repairs } : {}),
739
938
  ...(outcome.repairsDropped === undefined ? {} : { repairsDropped: outcome.repairsDropped }),
939
+ ...(outcome.repairModel === undefined ? {} : { repairModel: outcome.repairModel }),
740
940
  ...(outcome.abortedInFlight ? { abortedInFlight: true } : {}),
741
941
  })),
742
942
  ...(race.winner === undefined ? {} : { winner: race.winner.model, winnerCandidate: race.winner.candidate }),
@@ -826,6 +1026,7 @@ export async function runInstantBuild(args) {
826
1026
  const specs = instantSpecs({ assembly, haikuMessages: args.haikuMessages, fanout });
827
1027
  const mercuryThrottle = { holdUntil: 0 };
828
1028
  const baseline = opensBaseline(sourceFiles(assembly.base));
1029
+ const repairModel = instantRepairModel(assembly.repairModel);
829
1030
  let race;
830
1031
  try {
831
1032
  race = await raceInstantCandidates({
@@ -841,6 +1042,7 @@ export async function runInstantBuild(args) {
841
1042
  startedAt,
842
1043
  deadlineAt,
843
1044
  baseline,
1045
+ repairModel,
844
1046
  ...(spec.model.name === "mercury" ? { throttle: mercuryThrottle } : {}),
845
1047
  }).catch((e) => crashedCandidate(spec, startedAt, e)),
846
1048
  });