@vibes.diy/api-svc 14.3.16 → 14.3.17

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 (92) hide show
  1. package/intern/agent-dispatcher.d.ts +1 -0
  2. package/intern/agent-dispatcher.js +2 -0
  3. package/intern/agent-dispatcher.js.map +1 -1
  4. package/intern/agent-session.d.ts +22 -1
  5. package/intern/agent-session.js +19 -1
  6. package/intern/agent-session.js.map +1 -1
  7. package/intern/agent-wake.d.ts +1 -0
  8. package/intern/agent-wake.js +1 -0
  9. package/intern/agent-wake.js.map +1 -1
  10. package/intern/builder-model.d.ts +4 -1
  11. package/intern/builder-model.js +12 -6
  12. package/intern/builder-model.js.map +1 -1
  13. package/intern/eval-handles.d.ts +1 -1
  14. package/intern/eval-handles.js +3 -3
  15. package/intern/eval-handles.js.map +1 -1
  16. package/intern/get-model-defaults.d.ts +1 -0
  17. package/intern/get-model-defaults.js +13 -0
  18. package/intern/get-model-defaults.js.map +1 -1
  19. package/intern/in-flight-turns.d.ts +1 -1
  20. package/intern/in-flight-turns.js +1 -1
  21. package/intern/in-flight-turns.js.map +1 -1
  22. package/intern/mint-fire-core.d.ts +2 -0
  23. package/intern/mint-fire-core.js +2 -0
  24. package/intern/mint-fire-core.js.map +1 -1
  25. package/intern/process-access-bindings.js +1 -1
  26. package/intern/process-access-bindings.js.map +1 -1
  27. package/intern/prompt-usage.js +20 -0
  28. package/intern/prompt-usage.js.map +1 -1
  29. package/intern/subagent-model.d.ts +20 -0
  30. package/intern/subagent-model.js +30 -0
  31. package/intern/subagent-model.js.map +1 -0
  32. package/intern/usage-rounds.d.ts +7 -0
  33. package/intern/usage-rounds.js +20 -0
  34. package/intern/usage-rounds.js.map +1 -0
  35. package/models.json +63 -1
  36. package/package.json +12 -12
  37. package/public/agent-turn-action.d.ts +30 -0
  38. package/public/agent-turn-action.js +374 -0
  39. package/public/agent-turn-action.js.map +1 -0
  40. package/public/agent-turn-assembly.d.ts +34 -0
  41. package/public/agent-turn-assembly.js +199 -0
  42. package/public/agent-turn-assembly.js.map +1 -0
  43. package/public/append-block-event.d.ts +31 -0
  44. package/public/append-block-event.js +121 -0
  45. package/public/append-block-event.js.map +1 -0
  46. package/public/auto-publish-on-persist.d.ts +10 -0
  47. package/public/auto-publish-on-persist.js +40 -0
  48. package/public/auto-publish-on-persist.js.map +1 -0
  49. package/public/codegen-turn-route.d.ts +40 -0
  50. package/public/codegen-turn-route.js +177 -0
  51. package/public/codegen-turn-route.js.map +1 -0
  52. package/public/draft-runtime.js +12 -2
  53. package/public/draft-runtime.js.map +1 -1
  54. package/public/guest-build-generation.js +2 -1
  55. package/public/guest-build-generation.js.map +1 -1
  56. package/public/handle-prompt-context.d.ts +41 -0
  57. package/public/handle-prompt-context.js +397 -0
  58. package/public/handle-prompt-context.js.map +1 -0
  59. package/public/handler-llm-request.d.ts +48 -0
  60. package/public/handler-llm-request.js +363 -0
  61. package/public/handler-llm-request.js.map +1 -0
  62. package/public/image-gen.js +1 -1
  63. package/public/image-gen.js.map +1 -1
  64. package/public/llm-dispatch-fallback.d.ts +46 -0
  65. package/public/llm-dispatch-fallback.js +293 -0
  66. package/public/llm-dispatch-fallback.js.map +1 -0
  67. package/public/llm-dispatch.d.ts +5 -180
  68. package/public/llm-dispatch.js +5 -1158
  69. package/public/llm-dispatch.js.map +1 -1
  70. package/public/open-chat.d.ts +2 -0
  71. package/public/open-chat.js +51 -7
  72. package/public/open-chat.js.map +1 -1
  73. package/public/prompt-chat-section.js +119 -1167
  74. package/public/prompt-chat-section.js.map +1 -1
  75. package/public/prompt-section-actions.d.ts +41 -0
  76. package/public/prompt-section-actions.js +161 -0
  77. package/public/prompt-section-actions.js.map +1 -0
  78. package/public/prompt-turn-envelope.d.ts +26 -0
  79. package/public/prompt-turn-envelope.js +120 -0
  80. package/public/prompt-turn-envelope.js.map +1 -0
  81. package/public/prompt-turn-settled.d.ts +32 -0
  82. package/public/prompt-turn-settled.js +157 -0
  83. package/public/prompt-turn-settled.js.map +1 -0
  84. package/public/prompt-turn-terminal.d.ts +22 -0
  85. package/public/prompt-turn-terminal.js +135 -0
  86. package/public/prompt-turn-terminal.js.map +1 -0
  87. package/public/prompt-turn-types.d.ts +37 -0
  88. package/public/prompt-turn-types.js +2 -0
  89. package/public/prompt-turn-types.js.map +1 -0
  90. package/public/social-store.js.map +1 -1
  91. package/public/stream-assembly.js +33 -51
  92. package/public/stream-assembly.js.map +1 -1
@@ -1,1159 +1,6 @@
1
- import { persistGuestUsage } from "../intern/guest-build-usage.js";
2
- import { Result, Option, exception2Result, chunkyAsync } from "@adviser/cement";
3
- import { convertImageEvtToFileRef } from "./convert-image-evt.js";
4
- import { canonicalModelUsage, isDraftOwnerHandle, isPromptFSStyle, isReqPromptFSUpdateChatSection, buildUrlResearchJob, isResError, } from "@vibes.diy/api-types";
5
- import { type } from "arktype";
6
- import { and, eq } from "drizzle-orm/sql/expressions";
7
- import { publishApp } from "./publish-app.js";
8
- import { costCentsFromUsage } from "../billing.js";
9
- import { wrapMsgBase } from "../unwrap-msg-base.js";
10
- import { isChatMessage, isUserChatMessage, FileSystemRef, isBlockStreamMsg, isBlockImage, isForcedToolChoice, } from "@vibes.diy/call-ai-v2";
11
- import { applyImgGenDocWrite } from "./img-doc-write.js";
12
- import { clientRefStamp } from "../intern/client-attribution.js";
13
- import { postMakerFact } from "../intern/post-maker-fact.js";
14
- import { ensureAppSlugItem } from "./ensure-app-slug-item.js";
15
- import { getModelDefaults } from "../intern/get-model-defaults.js";
16
- import { loadVersionTimeline, resolveEditBaseSeed } from "../intern/version-timeline.js";
17
- import { resolveCodeBlocks, resolveCodeBlockChangedPaths, createBlockAccumulator, } from "../intern/prompt-streaming.js";
18
- import { persistCodegenThemeMarker, persistCodegenVarietyMarker } from "../intern/prompt-assembly.js";
19
- import { stripVibesFileBlocks } from "../intern/inflate-file-attachments.js";
20
- import { loadModels } from "./list-models.js";
21
- import { messagesCarryImages, modelSupportsImageInput } from "../intern/model-vision.js";
22
- import { createLlmToolLoopBudget } from "../intern/llm-tool-loop.js";
23
- import { activateUrlCandidates } from "../intern/url-activation.js";
24
- import { reconcileResearchPointer } from "../intern/persist-url-research.js";
25
- import { claimDraftState } from "../intern/draft-chat-store.js";
26
- import { settleLightMintAtProvisionalAddress, settleMintJobIfLive } from "../intern/mint-job-store.js";
27
- import { openTurn } from "../intern/open-turn-store.js";
28
- import { fanOutSectionToChat, publishChatAppClaimed } from "../intern/section-fan-out.js";
29
- import { captureImgGenOutcome, classifyImgGenError } from "../analytics.js";
30
- import { delayForAttempt, extractEdgeError, extractProviderErrorFields, formatLlmAttemptFailures, isInvalidModelError, parseRetryAfterMs, pickDiagnosticHeaders, readFailureBody, waitMs, } from "./provider-errors.js";
31
- const TRANSIENT_LLM_STATUSES = new Set([429, 502, 503, 504]);
32
- const PRIMARY_RETRY_DELAY_MS = 100;
33
- const FALLBACK_RETRY_DELAY_MS = 250;
34
- const DISPATCH_FIRST_TOKEN_TIMEOUT_MS = 45_000;
35
- export const DISPATCH_TOTAL_BUDGET_MS = 120_000;
36
- const DISPATCH_MAX_ATTEMPTS = 3;
37
- export function currentUserMessageText(messages) {
38
- for (let index = messages.length - 1; index >= 0; index--) {
39
- const message = messages[index];
40
- if (message !== undefined && isUserChatMessage(message)) {
41
- return message.content
42
- .map((part) => (part.type === "text" ? part.text : ""))
43
- .join("")
44
- .trim();
45
- }
46
- }
47
- return "";
48
- }
49
- function systemMessageText(messages) {
50
- const partsText = (message) => {
51
- if (message.content === null)
52
- return "";
53
- if (typeof message.content === "string")
54
- return message.content;
55
- return message.content.map((part) => (part.type === "text" ? part.text : "")).join("");
56
- };
57
- const system = messages.find((m) => m.role === "system");
58
- if (system === undefined)
59
- return "";
60
- const context = messages.find(isAppContextMessage);
61
- return context === undefined ? partsText(system) : `${partsText(system)}\n${partsText(context)}`;
62
- }
63
- function envPositiveInt(vctx, key, fallback) {
64
- const raw = vctx.sthis.env.get(key);
65
- if (typeof raw !== "string")
66
- return fallback;
67
- const n = Number.parseInt(raw, 10);
68
- return Number.isFinite(n) && n > 0 ? n : fallback;
69
- }
70
- export function createLlmDispatchBudget(vctx) {
71
- return createLlmToolLoopBudget({
72
- totalBudgetMs: envPositiveInt(vctx, "DISPATCH_TOTAL_BUDGET_MS", DISPATCH_TOTAL_BUDGET_MS),
73
- maxAttempts: DISPATCH_MAX_ATTEMPTS,
74
- });
75
- }
76
- function logLlmAttemptFailure(args) {
77
- const { vctx, diag, label, model, elapsedMs, details } = args;
78
- vctx.logger
79
- .Warn()
80
- .Any("event", {
81
- chatId: diag.chatId,
82
- promptId: diag.promptId,
83
- phase: diag.phase,
84
- mode: diag.mode,
85
- label,
86
- model,
87
- elapsedMs,
88
- ...details,
89
- })
90
- .Msg("llm-request-attempt-failed");
91
- }
92
- export function pickFallbackModel(args) {
93
- const { models, mode, failedModel, requireImageInput } = args;
94
- if (mode !== "codegen" && mode !== "runtime") {
95
- return Result.Err(`Mode ${mode} does not support LLM fallback`);
96
- }
97
- for (const model of models) {
98
- if (model.id === failedModel || Array.isArray(model.fallbackFor) === false || model.fallbackFor.includes(mode) === false) {
99
- continue;
100
- }
101
- if (requireImageInput === true && modelSupportsImageInput(models, model.id) === false)
102
- continue;
103
- return Result.Ok(model.id);
104
- }
105
- return Result.Err(requireImageInput === true
106
- ? `No image-capable fallback model configured for ${mode} after ${failedModel}`
107
- : `No fallback model configured for ${mode} after ${failedModel}`);
108
- }
109
- export async function findFallbackModel(args) {
110
- const { vctx, mode, failedModel, requireImageInput } = args;
111
- const rModels = await loadModels(vctx);
112
- if (rModels.isErr()) {
113
- vctx.logger.Warn().Err(rModels).Msg("llm-fallback-model-load-failed");
114
- return Result.Err(`Failed to load fallback model catalog: ${String(rModels.Err())}`);
115
- }
116
- return pickFallbackModel({ models: rModels.Ok().models, mode, failedModel, requireImageInput });
117
- }
118
- export async function attemptLlmRequest(args) {
119
- const { vctx, diag, llmReq, label, firstTokenTimeoutMs } = args;
120
- const abort = new AbortController();
121
- const startedAt = Date.now();
122
- let timedOut = false;
123
- const timer = setTimeout(() => {
124
- timedOut = true;
125
- abort.abort();
126
- }, firstTokenTimeoutMs);
127
- const rRes = await exception2Result(() => vctx.llmRequest(llmReq, { signal: abort.signal }));
128
- clearTimeout(timer);
129
- const elapsedMs = Date.now() - startedAt;
130
- if (rRes.isErr()) {
131
- abort.abort();
132
- if (timedOut) {
133
- logLlmAttemptFailure({
134
- vctx,
135
- diag,
136
- label,
137
- model: llmReq.model,
138
- elapsedMs,
139
- details: { kind: "timeout", retryable: true, timeoutMs: firstTokenTimeoutMs },
140
- });
141
- return {
142
- ok: false,
143
- abort,
144
- failure: {
145
- kind: "timeout",
146
- label,
147
- model: llmReq.model,
148
- elapsedMs,
149
- retryable: true,
150
- },
151
- };
152
- }
153
- const error = String(rRes.Err());
154
- logLlmAttemptFailure({
155
- vctx,
156
- diag,
157
- label,
158
- model: llmReq.model,
159
- elapsedMs,
160
- details: { kind: "exception", retryable: false, error },
161
- });
162
- return {
163
- ok: false,
164
- abort,
165
- failure: {
166
- kind: "exception",
167
- label,
168
- model: llmReq.model,
169
- error,
170
- retryable: false,
171
- },
172
- };
173
- }
174
- const res = rRes.Ok();
175
- if (res.ok && res.body !== null) {
176
- return { ok: true, res, abort };
177
- }
178
- if (res.ok) {
179
- abort.abort();
180
- logLlmAttemptFailure({
181
- vctx,
182
- diag,
183
- label,
184
- model: llmReq.model,
185
- elapsedMs,
186
- details: {
187
- kind: "missing-body",
188
- status: res.status,
189
- statusText: res.statusText,
190
- retryable: false,
191
- error: "response body missing",
192
- },
193
- });
194
- return {
195
- ok: false,
196
- abort,
197
- failure: {
198
- kind: "missing-body",
199
- label,
200
- model: llmReq.model,
201
- status: res.status,
202
- statusText: res.statusText,
203
- error: "response body missing",
204
- retryable: false,
205
- },
206
- };
207
- }
208
- const { snippet: bodySnippet, full: bodyFull } = await readFailureBody(res);
209
- const retryAfterMs = parseRetryAfterMs(res.headers);
210
- abort.abort();
211
- const retryable = TRANSIENT_LLM_STATUSES.has(res.status);
212
- const provider = extractProviderErrorFields(bodyFull);
213
- const invalidModel = isInvalidModelError(res.status, provider);
214
- const edge = extractEdgeError(bodySnippet, provider.rawSnippet, res.headers);
215
- const headers = pickDiagnosticHeaders(res.headers);
216
- logLlmAttemptFailure({
217
- vctx,
218
- diag,
219
- label,
220
- model: llmReq.model,
221
- elapsedMs,
222
- details: {
223
- kind: "http-status",
224
- status: res.status,
225
- statusText: res.statusText,
226
- retryable,
227
- ...(invalidModel ? { invalidModel } : {}),
228
- ...(typeof retryAfterMs === "number" ? { retryAfterMs } : {}),
229
- ...(headers ? { headers } : {}),
230
- ...(bodySnippet.length > 0 ? { bodySnippet } : {}),
231
- ...provider,
232
- ...edge,
233
- },
234
- });
235
- const failure = {
236
- kind: "http-status",
237
- label,
238
- model: llmReq.model,
239
- status: res.status,
240
- statusText: res.statusText,
241
- retryable,
242
- ...(invalidModel ? { invalidModel } : {}),
243
- ...(bodySnippet.length > 0 ? { bodySnippet } : {}),
244
- ...(typeof retryAfterMs === "number" ? { retryAfterMs } : {}),
245
- };
246
- return {
247
- ok: false,
248
- abort,
249
- failure,
250
- };
251
- }
252
- export async function dispatchLlmRequestWithFallback(args) {
253
- const attempts = [];
254
- const diag = {
255
- chatId: args.chatId,
256
- promptId: args.promptId,
257
- phase: args.phase,
258
- mode: args.mode,
259
- };
260
- const firstTokenTimeoutMs = envPositiveInt(args.vctx, "DISPATCH_FIRST_TOKEN_TIMEOUT_MS", DISPATCH_FIRST_TOKEN_TIMEOUT_MS);
261
- const budget = args.budget ?? createLlmDispatchBudget(args.vctx);
262
- const dispatchDeadline = budget.deadlineAt;
263
- const budgetRemainingMs = () => Math.max(0, dispatchDeadline - Date.now());
264
- const attemptTimeoutMs = () => Math.min(firstTokenTimeoutMs, budgetRemainingMs());
265
- const backoffMs = (failure, defaultDelayMs) => Math.min(delayForAttempt(failure, defaultDelayMs), budgetRemainingMs());
266
- const budgetExhausted = () => {
267
- const abort = new AbortController();
268
- abort.abort();
269
- const note = `LLM dispatch budget exhausted before a response (attempts ${budget.attemptsUsed}/${budget.maxAttempts})`;
270
- const error = attempts.length > 0 ? `${formatLlmAttemptFailures(attempts)}; ${note}` : note;
271
- args.vctx.logger
272
- .Warn()
273
- .Any("event", { chatId: args.chatId, promptId: args.promptId, phase: args.phase, mode: args.mode, attempts: attempts.length })
274
- .Msg("llm-dispatch-budget-exhausted");
275
- return { ok: false, llmReq: args.llmReq, abort, attempts, error };
276
- };
277
- let lastPrimary;
278
- for (const label of ["primary", "primary-retry"]) {
279
- if (lastPrimary) {
280
- await waitMs(backoffMs(lastPrimary.failure, PRIMARY_RETRY_DELAY_MS));
281
- }
282
- if (Date.now() >= dispatchDeadline || budget.attemptsUsed >= budget.maxAttempts)
283
- return budgetExhausted();
284
- budget.attemptsUsed++;
285
- const attempt = await attemptLlmRequest({
286
- vctx: args.vctx,
287
- diag,
288
- llmReq: args.llmReq,
289
- label,
290
- firstTokenTimeoutMs: attemptTimeoutMs(),
291
- });
292
- if (attempt.ok)
293
- return { ok: true, res: attempt.res, llmReq: args.llmReq, abort: attempt.abort, attempts };
294
- attempts.push(attempt.failure);
295
- if (attempt.failure.retryable === false) {
296
- if (attempt.failure.kind === "http-status" && attempt.failure.invalidModel === true) {
297
- lastPrimary = attempt;
298
- break;
299
- }
300
- return { ok: false, llmReq: args.llmReq, abort: attempt.abort, attempts, error: formatLlmAttemptFailures(attempts) };
301
- }
302
- lastPrimary = attempt;
303
- }
304
- const failedPrimary = lastPrimary;
305
- if (Date.now() >= dispatchDeadline || budget.attemptsUsed >= budget.maxAttempts)
306
- return budgetExhausted();
307
- const rFallbackModel = await findFallbackModel({
308
- vctx: args.vctx,
309
- mode: args.mode,
310
- failedModel: args.llmReq.model,
311
- requireImageInput: messagesCarryImages(args.llmReq.messages),
312
- });
313
- if (rFallbackModel.isErr()) {
314
- const error = `${formatLlmAttemptFailures(attempts)}; ${String(rFallbackModel.Err())}`;
315
- return { ok: false, llmReq: args.llmReq, abort: failedPrimary.abort, attempts, error };
316
- }
317
- const fallbackModel = rFallbackModel.Ok();
318
- const fallbackReq = { ...args.llmReq, model: fallbackModel };
319
- await waitMs(backoffMs(failedPrimary.failure, FALLBACK_RETRY_DELAY_MS));
320
- if (Date.now() >= dispatchDeadline || budget.attemptsUsed >= budget.maxAttempts)
321
- return budgetExhausted();
322
- budget.attemptsUsed++;
323
- const fallback = await attemptLlmRequest({
324
- vctx: args.vctx,
325
- diag,
326
- llmReq: fallbackReq,
327
- label: "fallback",
328
- firstTokenTimeoutMs: attemptTimeoutMs(),
329
- });
330
- if (fallback.ok) {
331
- args.vctx.logger
332
- .Info()
333
- .Any("event", {
334
- mode: args.mode,
335
- fromModel: args.llmReq.model,
336
- toModel: fallbackModel,
337
- })
338
- .Msg("llm-fallback-succeeded");
339
- return { ok: true, res: fallback.res, llmReq: fallbackReq, abort: fallback.abort, attempts };
340
- }
341
- attempts.push(fallback.failure);
342
- return { ok: false, llmReq: fallbackReq, abort: fallback.abort, attempts, error: formatLlmAttemptFailures(attempts) };
343
- }
344
- const imgDocWrittenReqs = new WeakSet();
345
- function lastUserText(messages) {
346
- const users = messages.filter(isUserChatMessage);
347
- const last = users[users.length - 1];
348
- if (last === undefined)
349
- return "";
350
- return last.content.map((part) => (part.type === "text" ? part.text : "")).join("");
351
- }
352
- export async function appendBlockEvent({ ctx, vctx, req, promptId, blockSeq, evt, emitMode = "store", resChat, initiator, }) {
353
- if (isBlockImage(evt) && req._auth.type === "VerifiedGuestBuildAuth")
354
- return Result.Err("guest-build-images-refused");
355
- let processedEvt = evt;
356
- if (isBlockImage(evt) && evt.url && resChat) {
357
- const rConv = await convertImageEvtToFileRef(vctx, {
358
- evt,
359
- userId: req._auth.verifiedAuth.claims.userId,
360
- ownerHandle: resChat.ownerHandle,
361
- appSlug: resChat.appSlug,
362
- });
363
- if (rConv.isOk()) {
364
- processedEvt = rConv.Ok();
365
- }
366
- else {
367
- vctx.logger.Error().Err(rConv).Msg("[block.image] convert failed");
368
- }
369
- const img = processedEvt;
370
- if (req.mode === "img" &&
371
- isBlockImage(processedEvt) &&
372
- img.uploadId &&
373
- img.mimeType &&
374
- typeof img.size === "number" &&
375
- !imgDocWrittenReqs.has(req)) {
376
- const imgReq = req;
377
- const imgTarget = imgReq.imgTarget;
378
- if (imgTarget) {
379
- imgDocWrittenReqs.add(req);
380
- const wrote = await applyImgGenDocWrite(vctx, {
381
- userId: imgReq._auth.verifiedAuth.claims.userId,
382
- ownerHandle: resChat.ownerHandle,
383
- appSlug: resChat.appSlug,
384
- target: imgTarget,
385
- fileMeta: { uploadId: img.uploadId, mimeType: img.mimeType, size: img.size },
386
- promptText: lastUserText(imgReq.prompt.messages),
387
- model: imgReq.prompt.model,
388
- });
389
- if (wrote) {
390
- processedEvt = { ...img, docWritten: true };
391
- }
392
- else {
393
- imgDocWrittenReqs.delete(req);
394
- }
395
- }
396
- }
397
- }
398
- const now = new Date();
399
- const msgBase = wrapMsgBase(ctx.validated, {
400
- payload: {
401
- type: "vibes.diy.section-event",
402
- chatId: req.chatId,
403
- promptId,
404
- blockSeq,
405
- blocks: [processedEvt],
406
- timestamp: now,
407
- ...(initiator === undefined ? {} : { initiator }),
408
- },
409
- tid: req.outerTid,
410
- src: "promptChatSection",
411
- });
412
- if (emitMode !== "store-only") {
413
- await fanOutSectionToChat({
414
- connections: vctx.connections,
415
- chatId: req.chatId,
416
- sendCtx: ctx,
417
- msgBase,
418
- logger: vctx.logger,
419
- });
420
- }
421
- if (emitMode === "store" || emitMode === "store-only") {
422
- const rUpdate = await exception2Result(() => vctx.sql.db.insert(vctx.sql.tables.chatSections).values({
423
- chatId: req.chatId,
424
- promptId,
425
- blockSeq,
426
- blocks: [processedEvt],
427
- created: now.toISOString(),
428
- }));
429
- if (rUpdate.isErr()) {
430
- return Result.Err(rUpdate);
431
- }
432
- }
433
- return Result.Ok();
434
- }
435
- export async function autoPublishOnPersist(vctx, args) {
436
- const rMint = await exception2Result(async () => {
437
- const hb = vctx.sql.tables.handleBinding;
438
- const ownerRow = await vctx.sql.db
439
- .select({ userId: hb.userId })
440
- .from(hb)
441
- .where(eq(hb.handle, args.ownerHandle))
442
- .limit(1)
443
- .then((r) => r[0]);
444
- if (ownerRow === undefined || ownerRow.userId !== args.callerUserId) {
445
- vctx.logger
446
- .Debug()
447
- .Any("event", { ownerHandle: args.ownerHandle, appSlug: args.appSlug, promptId: args.promptId })
448
- .Msg("auto-publish-mint-skipped-not-owner");
449
- return;
450
- }
451
- const rPublish = await publishApp(vctx, {
452
- type: "vibes.diy.req-publish-app",
453
- auth: args.auth,
454
- ownerHandle: args.ownerHandle,
455
- appSlug: args.appSlug,
456
- fsId: args.fsId,
457
- auto: true,
458
- }, ownerRow.userId);
459
- if (rPublish.isErr()) {
460
- throw rPublish.Err();
461
- }
462
- });
463
- if (rMint.isErr()) {
464
- vctx.logger
465
- .Warn()
466
- .Err(rMint)
467
- .Any("event", { ownerHandle: args.ownerHandle, appSlug: args.appSlug, fsId: args.fsId, promptId: args.promptId })
468
- .Msg("auto-publish-mint-failed");
469
- }
470
- }
471
- async function persistChatSections(vctx, sections) {
472
- let result = Result.Ok(undefined);
473
- await chunkyAsync({
474
- input: sections,
475
- splitCondition: (secChunk) => secChunk.length >= 20,
476
- commit: async (secChunk) => {
477
- const rChunk = await exception2Result(() => vctx.sql.db.insert(vctx.sql.tables.chatSections).values(secChunk));
478
- if (rChunk.isErr())
479
- result = Result.Err(rChunk);
480
- },
481
- });
482
- return result;
483
- }
484
- export async function handlePromptContext({ vctx, req, resChat, promptId, blockSeq, value, collectedMsgs: iCollectedMsgs, proseOnly, blockChunks = 100, fileSystem, model, byok, researchRequested, codegenTheme, codegenVariety, editBaseFsId, initiator, agentDispatched, carried, onFsRef, frontendDraft, persistSections = (sections) => persistChatSections(vctx, sections), ctx, step, researchModel, }) {
485
- let fsRef = Option.None();
486
- let deferredPersist;
487
- const code = [];
488
- const sections = [];
489
- const collectedMsgs = [...iCollectedMsgs];
490
- const blockAcc = createBlockAccumulator();
491
- for (const msg of collectedMsgs) {
492
- if (!isBlockStreamMsg(msg)) {
493
- continue;
494
- }
495
- let sqlVal = sections[sections.length - 1];
496
- if (sections.length === 0 || sqlVal.blocks.length >= blockChunks) {
497
- sections.push({
498
- chatId: req.chatId,
499
- promptId,
500
- blockSeq: blockSeq++,
501
- blocks: [],
502
- created: msg.timestamp.toISOString(),
503
- });
504
- sqlVal = sections[sections.length - 1];
505
- }
506
- sqlVal.blocks.push(msg);
507
- const closed = blockAcc.ingest(msg);
508
- if (closed)
509
- code.push({ begin: closed.begin, lines: [...closed.lines], end: closed.end });
510
- }
511
- const promptContextValues = () => ({
512
- userId: req._auth.verifiedAuth.claims.userId,
513
- chatId: req.chatId,
514
- promptId,
515
- fsId: fsRef.IsSome() ? fsRef.unwrap().fsId : undefined,
516
- nethash: vctx.netHash(),
517
- promptTokens: value.usage.calculated.prompt_tokens,
518
- completionTokens: value.usage.calculated.completion_tokens,
519
- totalTokens: value.usage.calculated.total_tokens,
520
- ref: {
521
- type: "prompt.usage.sql",
522
- ...(frontendDraft === true ? { frontendDraft: true } : {}),
523
- usage: value.usage,
524
- fsRef: fsRef.toValue(),
525
- ...(model !== undefined && model !== "" ? { model } : {}),
526
- ...(step === undefined ? {} : { step }),
527
- ...(researchModel === undefined || researchModel === "" ? {} : { researchModel }),
528
- ...(byok === true ? { byok: true } : {}),
529
- ...(initiator === "platform" ? { initiator: "platform" } : {}),
530
- ...(agentDispatched === true ? { agentDispatched: true } : {}),
531
- ...clientRefStamp(vctx),
532
- },
533
- created: new Date().toISOString(),
534
- });
535
- let draftReplayPersisted = false;
536
- let ownerHandleForPersist = resChat.ownerHandle;
537
- let appSlugForPersist = resChat.appSlug;
538
- if (code.length > 0 && (resChat.mode === "codegen" || isPromptFSStyle(resChat.mode))) {
539
- if (isDraftOwnerHandle(ownerHandleForPersist)) {
540
- const rSections = await persistSections(sections);
541
- if (rSections.isErr())
542
- return Result.Err(rSections);
543
- const rContext = await exception2Result(() => req._auth.type === "VerifiedGuestBuildAuth"
544
- ? persistGuestUsage(vctx, promptContextValues())
545
- : vctx.sql.db.insert(vctx.sql.tables.promptContexts).values(promptContextValues()));
546
- if (rContext.isErr())
547
- return Result.Err(rContext);
548
- draftReplayPersisted = true;
549
- const rPark = await claimDraftState(vctx, req.chatId, ["unowned", "turn-parked"], "turn-parked");
550
- if (rPark.isErr())
551
- return Result.Err(rPark);
552
- if (rPark.Ok()) {
553
- vctx.logger
554
- .Info()
555
- .Any("event", { userId: req._auth.verifiedAuth.claims.userId, chatId: req.chatId, promptId })
556
- .Msg("draft-persist-deferred-awaiting-handle");
557
- deferredPersist = "awaiting-handle";
558
- }
559
- else {
560
- const rLive = await readChatAddress(vctx, req.chatId);
561
- if (rLive.isErr())
562
- return Result.Err(rLive);
563
- const live = rLive.Ok();
564
- if (live === undefined || isDraftOwnerHandle(live.ownerHandle)) {
565
- return Result.Err(`draft chat ${req.chatId} lost the park claim but still has no owner handle`);
566
- }
567
- ownerHandleForPersist = live.ownerHandle;
568
- appSlugForPersist = live.appSlug;
569
- }
570
- }
571
- else {
572
- const rLive = await readChatAddress(vctx, req.chatId);
573
- if (rLive.isErr())
574
- return Result.Err(rLive);
575
- const live = rLive.Ok();
576
- if (live !== undefined && isDraftOwnerHandle(live.ownerHandle) === false) {
577
- if (live.ownerHandle !== ownerHandleForPersist || live.appSlug !== appSlugForPersist) {
578
- vctx.logger
579
- .Info()
580
- .Any("event", {
581
- chatId: req.chatId,
582
- promptId,
583
- from: { ownerHandle: ownerHandleForPersist, appSlug: appSlugForPersist },
584
- to: live,
585
- })
586
- .Msg("persist-address-refreshed-mid-turn");
587
- }
588
- ownerHandleForPersist = live.ownerHandle;
589
- appSlugForPersist = live.appSlug;
590
- }
591
- }
592
- }
593
- if (deferredPersist === undefined &&
594
- proseOnly !== true &&
595
- code.length > 0 &&
596
- (resChat.mode === "codegen" || isPromptFSStyle(resChat.mode))) {
597
- if (req._auth.type === "VerifiedGuestBuildAuth" || req.auth === undefined)
598
- return Result.Err("guest-build-cannot-publish");
599
- let resolvedFileSystem;
600
- let changedPathsFromBlocks;
601
- let carriedItems = carried ?? [];
602
- if (fileSystem) {
603
- resolvedFileSystem = fileSystem;
604
- changedPathsFromBlocks = resolveCodeBlockChangedPaths(code);
605
- }
606
- else {
607
- const timelineResult = await loadVersionTimeline(vctx, req.chatId);
608
- if (timelineResult.isErr())
609
- return Result.Err(timelineResult);
610
- const timeline = timelineResult.Ok();
611
- const chainHeadFsId = timeline.length > 0 ? timeline[timeline.length - 1].fsId : undefined;
612
- const chainHeadVfs = chainHeadFsId !== undefined ? timeline[timeline.length - 1].vfs : new Map();
613
- const base = await resolveEditBaseSeed(vctx, {
614
- editBaseFsId,
615
- chainHeadFsId,
616
- chainHeadVfs,
617
- chainHeadCarried: chainHeadFsId !== undefined ? timeline[timeline.length - 1].carried : [],
618
- ownerHandle: ownerHandleForPersist,
619
- appSlug: appSlugForPersist,
620
- });
621
- carriedItems = base.carried;
622
- const resolved = resolveCodeBlocks(code, base.vfs);
623
- resolvedFileSystem = resolved.files;
624
- changedPathsFromBlocks = resolved.changedPaths;
625
- }
626
- {
627
- const sources = resolvedFileSystem
628
- .filter((file) => file.type === "code-block")
629
- .filter((file) => file.filename.startsWith("/~~") === false);
630
- if (agentDispatched === true && code.some((block) => block.begin.path?.includes("~~") === true))
631
- return Result.Err("Generated code cannot overwrite derived runtime artifacts.");
632
- const changedPaths = new Set(isReqPromptFSUpdateChatSection(req) && req.agentFileCommit !== undefined
633
- ? req.agentFileCommit.changes.map((file) => file.path)
634
- : changedPathsFromBlocks);
635
- const changes = sources
636
- .filter((file) => changedPaths.has(file.filename))
637
- .map((file) => ({ path: file.filename, contents: file.content }));
638
- if (agentDispatched === true || changes.length > 0) {
639
- const checked = await validateAgentFiles(vctx, {
640
- vfs: new Map(sources.map((file) => [file.filename, file.content])),
641
- changes,
642
- ownerHandle: ownerHandleForPersist,
643
- });
644
- if (checked.isErr())
645
- return Result.Err(checked.Err());
646
- }
647
- }
648
- const rFs = await ensureAppSlugItem(vctx, {
649
- type: "vibes.diy.req-ensure-app-slug",
650
- mode: "dev",
651
- runId: promptId,
652
- appSlug: appSlugForPersist,
653
- ownerHandle: ownerHandleForPersist,
654
- fileSystem: resolvedFileSystem,
655
- auth: req.auth,
656
- _auth: req._auth,
657
- }, { carried: carriedItems });
658
- if (rFs.isErr()) {
659
- console.error("Failed to ensure app slug item for code blocks:", rFs.Err());
660
- return Result.Err(rFs);
661
- }
662
- const ensured = rFs.Ok();
663
- if (isResError(ensured)) {
664
- return Result.Err(`${ensured.error.message} [${ensured.error.code}]`);
665
- }
666
- const tFsRef = type(FileSystemRef).onDeepUndeclaredKey("delete")(ensured);
667
- if (tFsRef instanceof type.errors) {
668
- return Result.Err(`Failed to parse FileSystemRef: ${tFsRef.summary}`);
669
- }
670
- fsRef = Option.Some(tFsRef);
671
- if (onFsRef) {
672
- const rReveal = await exception2Result(() => onFsRef(tFsRef));
673
- if (rReveal.isErr()) {
674
- vctx.logger.Warn().Err(rReveal).Str("promptId", promptId).Msg("reveal-emit-failed");
675
- }
676
- }
677
- }
678
- value.fsRef = fsRef.toValue();
679
- const rSql = await exception2Result(async () => {
680
- const values = promptContextValues();
681
- if (draftReplayPersisted === false) {
682
- if (req._auth.type === "VerifiedGuestBuildAuth") {
683
- const persisted = await persistGuestUsage(vctx, values);
684
- if (persisted.isErr())
685
- return persisted;
686
- }
687
- else
688
- await vctx.sql.db.insert(vctx.sql.tables.promptContexts).values(values);
689
- }
690
- else if (deferredPersist === undefined) {
691
- await vctx.sql.db
692
- .update(vctx.sql.tables.promptContexts)
693
- .set({ fsId: values.fsId, ref: values.ref })
694
- .where(eq(vctx.sql.tables.promptContexts.promptId, promptId));
695
- const terminalSection = sections.find((section) => section.blocks.includes(value));
696
- if (terminalSection !== undefined && fsRef.IsSome()) {
697
- const t = vctx.sql.tables.chatSections;
698
- await vctx.sql.db
699
- .update(t)
700
- .set({ blocks: terminalSection.blocks })
701
- .where(and(eq(t.chatId, req.chatId), eq(t.promptId, promptId), eq(t.blockSeq, terminalSection.blockSeq)));
702
- }
703
- }
704
- });
705
- if (rSql.isErr()) {
706
- return Result.Err(rSql);
707
- }
708
- const costCents = costCentsFromUsage(value.usage);
709
- const billingP = (async () => {
710
- if (costCents <= 0 || req._auth.type === "VerifiedGuestBuildAuth")
711
- return;
712
- if (byok === true)
713
- return;
714
- const rTrack = await vctx.billing.trackUsage({
715
- userId: req._auth.verifiedAuth.claims.userId,
716
- costCents,
717
- promptId,
718
- ...(model !== undefined && model !== "" ? { model } : {}),
719
- mode: resChat.mode,
720
- });
721
- if (rTrack.isErr()) {
722
- vctx.logger.Warn().Err(rTrack).Str("promptId", promptId).Msg("billing-track-failed");
723
- }
724
- })();
725
- const markerP = (async () => {
726
- if (!(resChat.mode === "codegen" && fsRef.IsSome()))
727
- return;
728
- const rMark = await exception2Result(() => persistCodegenThemeMarker(vctx, {
729
- userId: req._auth.verifiedAuth.claims.userId,
730
- ownerHandle: ownerHandleForPersist,
731
- appSlug: appSlugForPersist,
732
- theme: codegenTheme,
733
- }));
734
- if (rMark.isErr()) {
735
- vctx.logger.Warn().Err(rMark).Str("chatId", req.chatId).Msg("persistCodegenThemeMarker failed");
736
- }
737
- const rVariety = await exception2Result(() => persistCodegenVarietyMarker(vctx, {
738
- userId: req._auth.verifiedAuth.claims.userId,
739
- ownerHandle: ownerHandleForPersist,
740
- appSlug: appSlugForPersist,
741
- variety: codegenVariety,
742
- }));
743
- if (rVariety.isErr()) {
744
- vctx.logger.Warn().Err(rVariety).Str("chatId", req.chatId).Msg("persistCodegenVarietyMarker failed");
745
- }
746
- })();
747
- await Promise.all([billingP, markerP, draftReplayPersisted ? Promise.resolve(Result.Ok(undefined)) : persistSections(sections)]);
748
- if (resChat.mode === "codegen") {
749
- const rSettle = await exception2Result(() => settleMintJobIfLive(vctx, {
750
- chatId: req.chatId,
751
- ownerHandle: ownerHandleForPersist,
752
- appSlug: appSlugForPersist,
753
- logModule: "handlePromptContext",
754
- }));
755
- if (rSettle.isErr()) {
756
- vctx.logger.Warn().Err(rSettle).Str("chatId", req.chatId).Str("promptId", promptId).Msg("mint-job-settle-rejected");
757
- }
758
- if (fsRef.IsSome()) {
759
- const rLight = await exception2Result(() => settleLightMintAtProvisionalAddress(vctx, {
760
- chatId: req.chatId,
761
- ownerHandle: ownerHandleForPersist,
762
- appSlug: appSlugForPersist,
763
- logModule: "handlePromptContext",
764
- }));
765
- if (rLight.isErr()) {
766
- vctx.logger.Warn().Err(rLight).Str("chatId", req.chatId).Str("promptId", promptId).Msg("light-mint-settle-rejected");
767
- }
768
- else if (rLight.Ok() && ctx !== undefined) {
769
- await publishChatAppClaimed({
770
- connections: vctx.connections,
771
- chatId: req.chatId,
772
- ownerHandle: ownerHandleForPersist,
773
- appSlug: appSlugForPersist,
774
- sendCtx: ctx,
775
- validated: ctx.validated,
776
- logger: vctx.logger,
777
- });
778
- }
779
- }
780
- }
781
- if (researchRequested === true) {
782
- const reconciliation = reconcileResearchPointer(vctx, {
783
- userId: req._auth.verifiedAuth.claims.userId,
784
- chatId: req.chatId,
785
- promptId,
786
- }).then((rResearch) => {
787
- if (rResearch.isErr()) {
788
- vctx.logger.Warn().Err(rResearch).Str("promptId", promptId).Msg("reconcile-url-research-pointer-failed");
789
- }
790
- }, (error) => {
791
- vctx.logger
792
- .Warn()
793
- .Any("event", { chatId: req.chatId, promptId, error: String(error) })
794
- .Msg("reconcile-url-research-pointer-rejected");
795
- });
796
- const waitUntil = vctx.waitUntil;
797
- if (waitUntil !== undefined) {
798
- const registered = exception2Result(() => waitUntil(reconciliation));
799
- if (registered.isErr()) {
800
- vctx.logger.Warn().Err(registered).Str("promptId", promptId).Msg("reconcile-url-research-pointer-wait-until-failed");
801
- }
802
- }
803
- else {
804
- await reconciliation;
805
- }
806
- }
807
- if (fsRef.IsSome() && initiator !== "platform" && req._auth.type === "VerifiedAuthResult" && req.auth !== undefined) {
808
- const mintTask = autoPublishOnPersist(vctx, {
809
- ownerHandle: ownerHandleForPersist,
810
- appSlug: appSlugForPersist,
811
- fsId: fsRef.Unwrap().fsId,
812
- callerUserId: req._auth.verifiedAuth.claims.userId,
813
- auth: req.auth,
814
- promptId,
815
- }).catch((error) => {
816
- vctx.logger
817
- .Warn()
818
- .Any("event", { chatId: req.chatId, promptId, error: String(error) })
819
- .Msg("auto-publish-mint-rejected");
820
- });
821
- const waitUntil = vctx.waitUntil;
822
- if (waitUntil !== undefined) {
823
- const registered = exception2Result(() => waitUntil(mintTask));
824
- if (registered.isErr()) {
825
- vctx.logger.Warn().Err(registered).Str("promptId", promptId).Msg("auto-publish-mint-wait-until-failed");
826
- }
827
- }
828
- else {
829
- await mintTask;
830
- }
831
- }
832
- iCollectedMsgs.splice(0, collectedMsgs.length);
833
- return Result.Ok({ blockSeq, fsRef, ...(deferredPersist === undefined ? {} : { deferredPersist }) });
834
- }
835
- async function readChatAddress(vctx, chatId) {
836
- const t = vctx.sql.tables.chatContexts;
837
- const r = await exception2Result(() => vctx.sql.db
838
- .select({ ownerHandle: t.ownerHandle, appSlug: t.appSlug })
839
- .from(t)
840
- .where(eq(t.chatId, chatId))
841
- .limit(1)
842
- .then((rows) => rows[0]));
843
- if (r.isErr())
844
- return Result.Err(r);
845
- const row = r.Ok();
846
- return Result.Ok(row === undefined ? undefined : { ownerHandle: row.ownerHandle, appSlug: row.appSlug });
847
- }
848
- export function mergeServerTools(outgoing, serverTools) {
849
- if (serverTools === undefined || serverTools.length === 0)
850
- return {};
851
- const clientTools = Array.isArray(outgoing.tools) ? outgoing.tools : [];
852
- const byName = new Map();
853
- for (const tool of clientTools) {
854
- if (byName.has(tool.function.name) === false)
855
- byName.set(tool.function.name, tool);
856
- }
857
- for (const tool of serverTools) {
858
- byName.set(tool.function.name, tool);
859
- }
860
- return {
861
- tools: [...byName.values()],
862
- ...(isForcedToolChoice(outgoing.tool_choice) ? {} : { tool_choice: "auto" }),
863
- };
864
- }
865
- async function runtimeImageInputGate(vctx, messages, modelId) {
866
- if (messagesCarryImages(messages) === false)
867
- return Result.Ok(undefined);
868
- const rModels = await loadModels(vctx);
869
- if (rModels.isErr()) {
870
- return Result.Err(`callAI cannot send an image: the model catalog could not be read (${rModels.Err().message}), so image support for ${modelId} is unknown`);
871
- }
872
- if (modelSupportsImageInput(rModels.Ok().models, modelId) === false) {
873
- return Result.Err(`callAI cannot send an image: the model ${modelId} does not accept image input`);
874
- }
875
- return Result.Ok(undefined);
876
- }
877
- export async function handlerLlmRequest({ scope, ctx, vctx, blockSeq, req, resChat, promptId, preAssembled, serverTools, initiator, agentDispatched, platformInitiated, modelUsage, }) {
878
- const resolved = await scope
879
- .evalResult(async () => {
880
- const r = await getModelDefaults(vctx, { appSlug: resChat.appSlug, ownerHandle: resChat.ownerHandle });
881
- if (r.isErr()) {
882
- return Result.Err(r);
883
- }
884
- let param;
885
- switch (req.mode) {
886
- case "codegen":
887
- param = modelUsage === "agent" ? r.Ok().agent : r.Ok().codegen;
888
- break;
889
- case "runtime":
890
- param = r.Ok().runtime;
891
- break;
892
- case "img":
893
- param = r.Ok().img;
894
- break;
895
- default:
896
- return Result.Err(`Unknown prompt mode: ${req.mode}`);
897
- }
898
- const storedApiKey = param.apiKey !== undefined && param.apiKey !== "" ? param.apiKey : undefined;
899
- const perCallApiKey = req.apiKey !== undefined && req.apiKey !== "" ? req.apiKey : undefined;
900
- const byokApiKey = perCallApiKey ?? storedApiKey;
901
- return Result.Ok({
902
- modelId: req.prompt.model ?? param.model.id,
903
- ...(byokApiKey !== undefined ? { byokApiKey } : {}),
904
- });
905
- })
906
- .do();
907
- const modelId = resolved.modelId;
908
- const byokApiKey = resolved.byokApiKey;
909
- if (req.mode === "runtime" && preAssembled === undefined) {
910
- await scope.evalResult(() => runtimeImageInputGate(vctx, req.prompt.messages.filter(isChatMessage), modelId)).do();
911
- }
912
- const withSystemPrompt = await scope
913
- .evalResult(async () => {
914
- let withSystemPrompt = Result.Ok({
915
- model: modelId,
916
- messages: [],
917
- });
918
- if (preAssembled !== undefined) {
919
- withSystemPrompt = Result.Ok(preAssembled);
920
- }
921
- else if (req.mode === "runtime" || req.mode === "img") {
922
- let messages = stripVibesFileBlocks(req.prompt.messages.filter(isChatMessage));
923
- if (req.mode === "img") {
924
- const imgReq = req;
925
- if (imgReq.inputImageBase64) {
926
- const lastUserIdx = (() => {
927
- for (let i = messages.length - 1; i >= 0; i--) {
928
- if (messages[i].role === "user")
929
- return i;
930
- }
931
- return -1;
932
- })();
933
- if (lastUserIdx >= 0) {
934
- const target = messages[lastUserIdx];
935
- messages = [
936
- ...messages.slice(0, lastUserIdx),
937
- {
938
- ...target,
939
- content: [...target.content, { type: "image_url", image_url: { url: imgReq.inputImageBase64 } }],
940
- },
941
- ...messages.slice(lastUserIdx + 1),
942
- ];
943
- }
944
- }
945
- }
946
- withSystemPrompt = Result.Ok({
947
- model: req.prompt.model ?? modelId,
948
- messages,
949
- });
950
- }
951
- return withSystemPrompt;
952
- })
953
- .do();
954
- const isInitialTurn = preAssembled?.isInitialTurn ?? withSystemPrompt.messages.filter((m) => m.role === "user").length <= 1;
955
- const { apiKey: _clientApiKey, tools: clientTools, tool_choice: clientToolChoice, ...promptSansClientKey } = req.prompt;
956
- if (req.mode !== "codegen") {
957
- if (clientTools !== undefined)
958
- promptSansClientKey.tools = clientTools;
959
- if (clientToolChoice !== undefined)
960
- promptSansClientKey.tool_choice = clientToolChoice;
961
- }
962
- const activatedUrls = req.mode === "codegen"
963
- ? activateUrlCandidates({
964
- currentUserText: currentUserMessageText(req.prompt.messages.filter(isChatMessage)),
965
- enrichedPrompt: preAssembled?.enrichedPrompt,
966
- isInitialTurn,
967
- })
968
- : [];
969
- const llmReq = {
970
- ...{
971
- ...promptSansClientKey,
972
- messages: withSystemPrompt.messages,
973
- },
974
- model: withSystemPrompt.model,
975
- headers: vctx.params.llm.headers,
976
- ...(byokApiKey !== undefined ? { apiKey: byokApiKey } : {}),
977
- stream: true,
978
- ...(isInitialTurn && req.mode === "codegen" ? { verbosity: "low" } : {}),
979
- ...(req.mode === "img" ? { modalities: ["text", "image"] } : {}),
980
- ...mergeServerTools(promptSansClientKey, serverTools),
981
- };
982
- await scope
983
- .evalResult(async () => {
984
- const r = await appendBlockEvent({
985
- ctx,
986
- vctx,
987
- req,
988
- promptId,
989
- blockSeq: blockSeq,
990
- evt: {
991
- type: "prompt.req",
992
- ...(req.mode === "codegen" && req.source === "suggestion" ? { source: "suggestion" } : {}),
993
- streamId: promptId,
994
- chatId: req.chatId,
995
- seq: blockSeq,
996
- request: { ...promptSansClientKey, model: modelId },
997
- timestamp: new Date(),
998
- ...(agentDispatched === true ? { agentDispatched: true } : {}),
999
- ...(platformInitiated === true ? { platformInitiated: true } : {}),
1000
- },
1001
- ...(initiator === undefined ? {} : { initiator }),
1002
- });
1003
- blockSeq++;
1004
- return r;
1005
- })
1006
- .do();
1007
- if (canonicalModelUsage(req.mode) === "codegen") {
1008
- const rOpen = await openTurn(vctx.sql, {
1009
- chatId: req.chatId,
1010
- promptId,
1011
- userId: req._auth.verifiedAuth.claims.userId,
1012
- ownerHandle: resChat.ownerHandle,
1013
- appSlug: resChat.appSlug,
1014
- kind: isInitialTurn ? "first-build" : "codegen",
1015
- ...(vctx.doInstance === undefined ? {} : { objectId: vctx.doInstance.objectId, instanceId: vctx.doInstance.instanceId }),
1016
- });
1017
- if (rOpen.isErr()) {
1018
- vctx.logger.Warn().Err(rOpen).Any("event", { chatId: req.chatId, promptId }).Msg("open-turn-record-failed");
1019
- }
1020
- }
1021
- if (req.mode === "codegen" && preAssembled !== undefined) {
1022
- const systemText = systemMessageText(preAssembled.messages);
1023
- if (systemText !== "") {
1024
- await scope
1025
- .evalResult(async () => {
1026
- const r = await appendBlockEvent({
1027
- ctx,
1028
- vctx,
1029
- req,
1030
- promptId,
1031
- blockSeq: blockSeq,
1032
- evt: {
1033
- type: "prompt.system-prompt",
1034
- streamId: promptId,
1035
- chatId: req.chatId,
1036
- seq: blockSeq,
1037
- text: systemText,
1038
- timestamp: new Date(),
1039
- },
1040
- emitMode: "emit-only",
1041
- });
1042
- blockSeq++;
1043
- return r;
1044
- })
1045
- .do();
1046
- }
1047
- }
1048
- if (vctx.billing.enforceEnabled && byokApiKey === undefined) {
1049
- await scope
1050
- .evalResult(async () => {
1051
- const rCheck = await vctx.billing.checkCredits({ userId: req._auth.verifiedAuth.claims.userId });
1052
- if (rCheck.isErr()) {
1053
- vctx.logger.Warn().Err(rCheck).Str("chatId", req.chatId).Msg("billing-check-failed-open");
1054
- return Result.Ok(undefined);
1055
- }
1056
- if (rCheck.Ok().allowed === false) {
1057
- postMakerFact(vctx, {
1058
- eventId: `wall:${req.chatId}:${new Date().toISOString().slice(0, 10)}`,
1059
- userId: req._auth.verifiedAuth.claims.userId,
1060
- at: new Date().toISOString(),
1061
- kind: "paywall-hit",
1062
- }, "credit-wall");
1063
- return Result.Err("credits-exhausted: you have used your included AI credits — upgrade to continue");
1064
- }
1065
- return Result.Ok(undefined);
1066
- })
1067
- .do();
1068
- }
1069
- const dispatchStartedAt = Date.now();
1070
- vctx.logger.Info().Any("event", { chatId: req.chatId, promptId, mode: req.mode, model: modelId }).Msg("llm-dispatch-start");
1071
- const dispatch = await dispatchLlmRequestWithFallback({
1072
- vctx,
1073
- mode: req.mode,
1074
- llmReq,
1075
- chatId: req.chatId,
1076
- promptId,
1077
- phase: "initial",
1078
- });
1079
- vctx.logger
1080
- .Info()
1081
- .Any("event", {
1082
- chatId: req.chatId,
1083
- promptId,
1084
- mode: req.mode,
1085
- ok: dispatch.ok,
1086
- model: dispatch.llmReq.model,
1087
- attempts: dispatch.attempts.length,
1088
- elapsedMs: Date.now() - dispatchStartedAt,
1089
- })
1090
- .Msg("llm-dispatch-settled");
1091
- if (req.mode === "img") {
1092
- const lastFailure = dispatch.ok ? undefined : dispatch.attempts[dispatch.attempts.length - 1];
1093
- const status = lastFailure !== undefined && (lastFailure.kind === "http-status" || lastFailure.kind === "missing-body")
1094
- ? lastFailure.status
1095
- : undefined;
1096
- captureImgGenOutcome(vctx, {
1097
- outcome: dispatch.ok ? "succeeded" : "failed",
1098
- userId: req._auth.verifiedAuth.claims.userId,
1099
- ownerHandle: resChat.ownerHandle,
1100
- appSlug: resChat.appSlug,
1101
- model: dispatch.llmReq.model ?? modelId,
1102
- ...(dispatch.ok
1103
- ? {}
1104
- : { errorClass: classifyImgGenError(dispatch.error, status), ...(status === undefined ? {} : { status }) }),
1105
- });
1106
- }
1107
- const res = await scope
1108
- .evalResult(async () => {
1109
- return dispatch.ok ? Result.Ok(dispatch.res) : Result.Err(dispatch.error);
1110
- })
1111
- .do();
1112
- const finalModel = dispatch.llmReq.model;
1113
- if (dispatch.ok && finalModel !== undefined && finalModel !== modelId) {
1114
- await scope
1115
- .evalResult(async () => {
1116
- const r = await appendBlockEvent({
1117
- ctx,
1118
- vctx,
1119
- req,
1120
- promptId,
1121
- blockSeq,
1122
- evt: {
1123
- type: "prompt.model-resolved",
1124
- streamId: promptId,
1125
- chatId: req.chatId,
1126
- seq: blockSeq,
1127
- model: finalModel,
1128
- timestamp: new Date(),
1129
- },
1130
- });
1131
- blockSeq++;
1132
- return r;
1133
- })
1134
- .do();
1135
- }
1136
- const currentUserText = currentUserMessageText(req.prompt.messages.filter(isChatMessage));
1137
- const urlResearchJob = buildUrlResearchJob({
1138
- mode: req.mode,
1139
- isInitialTurn,
1140
- userId: req._auth.verifiedAuth.claims.userId,
1141
- chatId: req.chatId,
1142
- promptId,
1143
- ownerHandle: resChat.ownerHandle,
1144
- appSlug: resChat.appSlug,
1145
- currentUserText,
1146
- activatedUrls,
1147
- model: dispatch.llmReq.model ?? modelId,
1148
- });
1149
- return {
1150
- res,
1151
- blockSeq,
1152
- llmReq: dispatch.llmReq,
1153
- abort: dispatch.abort,
1154
- ...(urlResearchJob === undefined ? {} : { urlResearchJob }),
1155
- };
1156
- }
1157
- import { validateAgentFiles } from "../intern/agent-file-validation.js";
1158
- import { isAppContextMessage } from "../intern/agent-request-frame.js";
1
+ export { DISPATCH_TOTAL_BUDGET_MS, pickFallbackModel, dispatchLlmRequestWithFallback, } from "./llm-dispatch-fallback.js";
2
+ export { announceModelResolved, appendBlockEvent } from "./append-block-event.js";
3
+ export { autoPublishOnPersist } from "./auto-publish-on-persist.js";
4
+ export { handlePromptContext } from "./handle-prompt-context.js";
5
+ export { currentUserMessageText, mergeServerTools, handlerLlmRequest } from "./handler-llm-request.js";
1159
6
  //# sourceMappingURL=llm-dispatch.js.map