@superblocksteam/vite-plugin-file-sync 2.0.139 → 2.0.140-next.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/dist/ai-service/agent/tools/apis/api-validation-orchestrator.js +1 -1
- package/dist/ai-service/agent/tools/apis/api-validation-orchestrator.js.map +1 -1
- package/dist/ai-service/agent/tools/apis/test-api.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/apis/test-api.js +3 -2
- package/dist/ai-service/agent/tools/apis/test-api.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js +1 -13
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/git.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/tools/git.js +19 -1
- package/dist/ai-service/agent/tools2/tools/git.js.map +1 -1
- package/dist/ai-service/agent/utils.d.ts.map +1 -1
- package/dist/ai-service/agent/utils.js +46 -3
- package/dist/ai-service/agent/utils.js.map +1 -1
- package/dist/ai-service/app-interface/npm-registry.d.ts +11 -7
- package/dist/ai-service/app-interface/npm-registry.d.ts.map +1 -1
- package/dist/ai-service/app-interface/npm-registry.js +11 -7
- package/dist/ai-service/app-interface/npm-registry.js.map +1 -1
- package/dist/ai-service/app-skills/helpers.d.ts.map +1 -1
- package/dist/ai-service/app-skills/helpers.js +2 -0
- package/dist/ai-service/app-skills/helpers.js.map +1 -1
- package/dist/ai-service/chat/chat-session-store-metrics.d.ts.map +1 -1
- package/dist/ai-service/chat/chat-session-store-metrics.js +26 -17
- package/dist/ai-service/chat/chat-session-store-metrics.js.map +1 -1
- package/dist/ai-service/chat/chat-session-store.d.ts +1 -4
- package/dist/ai-service/chat/chat-session-store.d.ts.map +1 -1
- package/dist/ai-service/chat/chat-session-store.js +32 -8
- package/dist/ai-service/chat/chat-session-store.js.map +1 -1
- package/dist/ai-service/chat/transcript-metrics.d.ts +5 -0
- package/dist/ai-service/chat/transcript-metrics.d.ts.map +1 -1
- package/dist/ai-service/chat/transcript-metrics.js +42 -15
- package/dist/ai-service/chat/transcript-metrics.js.map +1 -1
- package/dist/ai-service/index.d.ts +2 -0
- package/dist/ai-service/index.d.ts.map +1 -1
- package/dist/ai-service/index.js +206 -31
- package/dist/ai-service/index.js.map +1 -1
- package/dist/ai-service/llm/client.d.ts.map +1 -1
- package/dist/ai-service/llm/client.js +9 -9
- package/dist/ai-service/llm/client.js.map +1 -1
- package/dist/ai-service/llm/context-v2/context-metrics.d.ts +6 -0
- package/dist/ai-service/llm/context-v2/context-metrics.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/context-metrics.js +18 -1
- package/dist/ai-service/llm/context-v2/context-metrics.js.map +1 -1
- package/dist/ai-service/llm/provider.d.ts +7 -1
- package/dist/ai-service/llm/provider.d.ts.map +1 -1
- package/dist/ai-service/llm/provider.js +292 -15
- package/dist/ai-service/llm/provider.js.map +1 -1
- package/dist/ai-service/llm/stream/empty-tool-call-tracker.d.ts +47 -0
- package/dist/ai-service/llm/stream/empty-tool-call-tracker.d.ts.map +1 -0
- package/dist/ai-service/llm/stream/empty-tool-call-tracker.js +122 -0
- package/dist/ai-service/llm/stream/empty-tool-call-tracker.js.map +1 -0
- package/dist/ai-service/llm/stream/errors.d.ts +21 -1
- package/dist/ai-service/llm/stream/errors.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/errors.js +20 -0
- package/dist/ai-service/llm/stream/errors.js.map +1 -1
- package/dist/ai-service/llm/stream/managed-stream.d.ts +5 -1
- package/dist/ai-service/llm/stream/managed-stream.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/managed-stream.js +51 -1
- package/dist/ai-service/llm/stream/managed-stream.js.map +1 -1
- package/dist/ai-service/llm/stream/orchestrator.d.ts +17 -2
- package/dist/ai-service/llm/stream/orchestrator.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/orchestrator.js +185 -135
- package/dist/ai-service/llm/stream/orchestrator.js.map +1 -1
- package/dist/ai-service/llmobs/otel-exporter.d.ts.map +1 -1
- package/dist/ai-service/llmobs/otel-exporter.js +17 -6
- package/dist/ai-service/llmobs/otel-exporter.js.map +1 -1
- package/dist/ai-service/llmobs/tracer.d.ts +24 -2
- package/dist/ai-service/llmobs/tracer.d.ts.map +1 -1
- package/dist/ai-service/llmobs/tracer.js +90 -25
- package/dist/ai-service/llmobs/tracer.js.map +1 -1
- package/dist/ai-service/llmobs/types.d.ts +5 -0
- package/dist/ai-service/llmobs/types.d.ts.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.d.ts +24 -7
- package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.js +5 -0
- package/dist/ai-service/state-machine/clark-fsm.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/agent-planning.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/agent-planning.js +30 -43
- package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/awaiting-user.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/awaiting-user.js +21 -0
- package/dist/ai-service/state-machine/handlers/awaiting-user.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/idle.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/idle.js +16 -1
- package/dist/ai-service/state-machine/handlers/idle.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/llm-generating.js +10 -21
- package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/persist-prompt-attachments.d.ts +25 -0
- package/dist/ai-service/state-machine/handlers/persist-prompt-attachments.d.ts.map +1 -0
- package/dist/ai-service/state-machine/handlers/persist-prompt-attachments.js +68 -0
- package/dist/ai-service/state-machine/handlers/persist-prompt-attachments.js.map +1 -0
- package/dist/ai-service/state-machine/helpers/prompt-failure.d.ts +51 -0
- package/dist/ai-service/state-machine/helpers/prompt-failure.d.ts.map +1 -0
- package/dist/ai-service/state-machine/helpers/prompt-failure.js +149 -0
- package/dist/ai-service/state-machine/helpers/prompt-failure.js.map +1 -0
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts +77 -0
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts.map +1 -0
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.js +229 -0
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.js.map +1 -0
- package/dist/ai-service/state-machine/helpers/transition.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/transition.js +5 -1
- package/dist/ai-service/state-machine/helpers/transition.js.map +1 -1
- package/dist/ai-service/state-machine/traced-fsm.d.ts +6 -1
- package/dist/ai-service/state-machine/traced-fsm.d.ts.map +1 -1
- package/dist/ai-service/state-machine/traced-fsm.js +13 -3
- package/dist/ai-service/state-machine/traced-fsm.js.map +1 -1
- package/dist/ai-service/util/llm-config-utils.d.ts +2 -2
- package/dist/ai-service/util/llm-config-utils.d.ts.map +1 -1
- package/dist/ai-service/util/llm-config-utils.js +2 -2
- package/dist/ai-service/util/llm-config-utils.js.map +1 -1
- package/dist/git-service/app-layout-guard.d.ts +36 -0
- package/dist/git-service/app-layout-guard.d.ts.map +1 -0
- package/dist/git-service/app-layout-guard.js +66 -0
- package/dist/git-service/app-layout-guard.js.map +1 -0
- package/dist/git-service/errors.d.ts +5 -0
- package/dist/git-service/errors.d.ts.map +1 -1
- package/dist/git-service/errors.js +10 -0
- package/dist/git-service/errors.js.map +1 -1
- package/dist/git-service/index.d.ts +2 -2
- package/dist/git-service/index.d.ts.map +1 -1
- package/dist/git-service/index.js +2 -2
- package/dist/git-service/index.js.map +1 -1
- package/dist/migration-templates/app-fullstack/package.json +1 -1
- package/dist/socket-manager.d.ts.map +1 -1
- package/dist/socket-manager.js +6 -0
- package/dist/socket-manager.js.map +1 -1
- package/dist/sync-service/index.d.ts +16 -9
- package/dist/sync-service/index.d.ts.map +1 -1
- package/dist/sync-service/index.js +154 -4
- package/dist/sync-service/index.js.map +1 -1
- package/dist/util/logger.d.ts +7 -0
- package/dist/util/logger.d.ts.map +1 -1
- package/dist/util/logger.js +29 -0
- package/dist/util/logger.js.map +1 -1
- package/package.json +14 -13
package/dist/ai-service/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { context as otelContext, propagation, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
|
|
5
|
+
import { stepCountIs, streamText as aiStreamText, } from "ai";
|
|
5
6
|
import { z } from "zod";
|
|
6
7
|
import { AiGenerationState, AiMode, resolveAttachmentFileName, } from "@superblocksteam/library-shared/types";
|
|
7
8
|
import { addTracingToMethods, classifyAttachmentDelivery, FactAccessType, FactCreatedSource, FactEntityScope, TracedEventEmitter, getAiQuotaPaywallReasonFromMessage, } from "@superblocksteam/shared";
|
|
@@ -47,7 +48,7 @@ import { selectContextManagementStrategy, } from "./llm/context-v2/context-manag
|
|
|
47
48
|
import { ContextManagerV2 } from "./llm/context-v2/manager.js";
|
|
48
49
|
import { JsonlEventStore } from "./llm/context-v2/storage/jsonl-event-store.js";
|
|
49
50
|
import { LocalContextStorageV2 } from "./llm/context-v2/storage/local.js";
|
|
50
|
-
import { createLLMProvider } from "./llm/provider.js";
|
|
51
|
+
import { createLLMProvider, setSpanError } from "./llm/provider.js";
|
|
51
52
|
import { traceLLM } from "./llmobs/helpers.js";
|
|
52
53
|
import { getJwtTraceTags } from "./llmobs/helpers.js";
|
|
53
54
|
import llmobs from "./llmobs/index.js";
|
|
@@ -72,6 +73,7 @@ import { classificationHelper } from "./state-machine/helpers/classification.js"
|
|
|
72
73
|
import { getContextId } from "./state-machine/helpers/context-id.js";
|
|
73
74
|
import { sendUserGenerationStateChannel } from "./state-machine/helpers/peer.js";
|
|
74
75
|
import { sendUserMessageChannel, clearPendingToolPermissionRequest, } from "./state-machine/helpers/peer.js";
|
|
76
|
+
import { promptFailureFromStreamAbort, promptFailurePaywall, promptFailurePlatform, promptFailureUserCanceled, recordPromptAttempt, recordPromptEntryRejection, recordPromptOutcome, recordAwaitingUserHandoffDismissed, resolvePromptOutcomeDedupeKey, resolvePromptRequestDedupeKey, setPromptRequestMetricIdentity, } from "./state-machine/helpers/prompt-request-metrics.js";
|
|
75
77
|
import { catchBackgroundPeerDisconnect, StablePeer, } from "./state-machine/helpers/stable-peer.js";
|
|
76
78
|
import { transitionFrom } from "./state-machine/helpers/transition.js";
|
|
77
79
|
import { isTaskAccessibleTo, registerBackgroundTaskTypes, resyncActiveTaskStatusesToPeer, TaskStore, wireTaskStatusPushToPeer, } from "./tasks/index.js";
|
|
@@ -326,6 +328,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
326
328
|
requestDeduplicator = new RequestDeduplicator();
|
|
327
329
|
entityPermissionStore = new SessionEntityPermissionStore();
|
|
328
330
|
_onGenerationCompleteCallback;
|
|
331
|
+
activeGenerationTrace;
|
|
329
332
|
/** JWT-derived userId of the currently-authenticated peer, set by
|
|
330
333
|
* `notifyAuthenticatedRpc` and cleared on disconnect / peer swap.
|
|
331
334
|
* Only trusted while `authenticatedPeerId` still matches the bound
|
|
@@ -510,6 +513,10 @@ export class AiService extends TracedEventEmitter {
|
|
|
510
513
|
super(config.tracer);
|
|
511
514
|
this.config = config;
|
|
512
515
|
setDefaultLogger(config.logger);
|
|
516
|
+
setPromptRequestMetricIdentity({
|
|
517
|
+
applicationId: config.applicationId,
|
|
518
|
+
organizationId: config.organizationId,
|
|
519
|
+
});
|
|
513
520
|
// Store the wrapped logger so getLogger() can return it
|
|
514
521
|
// The wrapped logger forwards logs to the browser console when Clark logs are enabled
|
|
515
522
|
this._logger = getLogger();
|
|
@@ -923,6 +930,67 @@ export class AiService extends TracedEventEmitter {
|
|
|
923
930
|
// when Clark logs are enabled (via ?clark_logs=true or SUPERBLOCKS_CLI_ENV=local)
|
|
924
931
|
return this._logger;
|
|
925
932
|
}
|
|
933
|
+
startGenerationTrace(request, promptId) {
|
|
934
|
+
// A span still open here means the previous run never reached a terminal
|
|
935
|
+
// transition. Close it now so its listeners cannot fire on this run's
|
|
936
|
+
// events.
|
|
937
|
+
this.activeGenerationTrace?.finish();
|
|
938
|
+
const parentContext = otelContext.active();
|
|
939
|
+
const span = this.config.tracer.startSpan("AiService.generation", {
|
|
940
|
+
attributes: {
|
|
941
|
+
"ai.mode": request.mode,
|
|
942
|
+
"ai.model": request.llmConfig?.coreModel,
|
|
943
|
+
"ai.provider": request.llmConfig?.provider,
|
|
944
|
+
"app.id": this.config.applicationId,
|
|
945
|
+
"org.id": this.config.organizationId,
|
|
946
|
+
},
|
|
947
|
+
kind: SpanKind.INTERNAL,
|
|
948
|
+
}, parentContext);
|
|
949
|
+
const spanContext = trace.setSpan(parentContext, span);
|
|
950
|
+
const traceHeaders = {};
|
|
951
|
+
propagation.inject(spanContext, traceHeaders);
|
|
952
|
+
let finished = false;
|
|
953
|
+
const finish = (error) => {
|
|
954
|
+
if (finished)
|
|
955
|
+
return;
|
|
956
|
+
finished = true;
|
|
957
|
+
this.off("generationCompleted", onCompleted);
|
|
958
|
+
this.off("generationFailed", onFailed);
|
|
959
|
+
if (this.activeGenerationTrace === generationTrace) {
|
|
960
|
+
this.activeGenerationTrace = undefined;
|
|
961
|
+
}
|
|
962
|
+
if (error === undefined) {
|
|
963
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
964
|
+
}
|
|
965
|
+
else {
|
|
966
|
+
setSpanError(span, error);
|
|
967
|
+
}
|
|
968
|
+
span.end();
|
|
969
|
+
};
|
|
970
|
+
// Only end this span for its own run: generationCompleted carries the
|
|
971
|
+
// generation id, so a stale listener cannot close a later run's span.
|
|
972
|
+
// generationFailed carries no id, but a stale listener cannot survive
|
|
973
|
+
// into the next run either way — the span is finished (removing both
|
|
974
|
+
// listeners) when the FSM parks and, as a backstop, when the next run
|
|
975
|
+
// starts.
|
|
976
|
+
const onCompleted = (_producedDraft, generationId) => {
|
|
977
|
+
if (generationId === promptId) {
|
|
978
|
+
finish();
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
const onFailed = (error) => finish(error);
|
|
982
|
+
this.on("generationCompleted", onCompleted);
|
|
983
|
+
this.on("generationFailed", onFailed);
|
|
984
|
+
const generationTrace = {
|
|
985
|
+
context: spanContext,
|
|
986
|
+
finish,
|
|
987
|
+
traceHeaders: traceHeaders.traceparent || traceHeaders.tracestate
|
|
988
|
+
? traceHeaders
|
|
989
|
+
: undefined,
|
|
990
|
+
};
|
|
991
|
+
this.activeGenerationTrace = generationTrace;
|
|
992
|
+
return generationTrace;
|
|
993
|
+
}
|
|
926
994
|
createClarkTransitionHandler() {
|
|
927
995
|
const clarkStateHandlerFactories = {
|
|
928
996
|
Idle: doIdle,
|
|
@@ -960,7 +1028,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
960
1028
|
integrationStore: this.integrationStore,
|
|
961
1029
|
knowledgeManager: this.knowledgeManager,
|
|
962
1030
|
appContextStore: this.appContext,
|
|
963
|
-
llmProvider: createLLMProvider(this.getLLMProviderSettings(clark.context.llmConfig), () => this.requireInferenceJwt(), clark.context.llmConfig, () => this.getLlmAiAttributes(clark)),
|
|
1031
|
+
llmProvider: createLLMProvider(this.getLLMProviderSettings(clark.context.llmConfig), () => this.requireInferenceJwt(), clark.context.llmConfig, () => this.getLlmAiAttributes(clark), clark.context.traceHeaders),
|
|
964
1032
|
chatSessionStore: this.chatSessionStore,
|
|
965
1033
|
clarkProfiler: this.clarkProfiler,
|
|
966
1034
|
contextManagerV2: this.contextManagerV2,
|
|
@@ -1021,12 +1089,16 @@ export class AiService extends TracedEventEmitter {
|
|
|
1021
1089
|
if (clark.context.abortController?.signal.aborted &&
|
|
1022
1090
|
event.type !== USER_CANCELED &&
|
|
1023
1091
|
event.type !== AGENT_CANCELED) {
|
|
1024
|
-
return clark.send({
|
|
1092
|
+
return clark.send({
|
|
1093
|
+
type: USER_CANCELED,
|
|
1094
|
+
failure: promptFailureFromStreamAbort(clark.context),
|
|
1095
|
+
});
|
|
1025
1096
|
}
|
|
1026
1097
|
if (event.type !== AGENT_CANCELED) {
|
|
1027
1098
|
return clark.send({
|
|
1028
1099
|
type: AGENT_CANCELED,
|
|
1029
1100
|
advice: `An unexpected error occurred. Please try again.`,
|
|
1101
|
+
failure: promptFailurePlatform("handler_error"),
|
|
1030
1102
|
});
|
|
1031
1103
|
}
|
|
1032
1104
|
throw error;
|
|
@@ -1134,7 +1206,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
1134
1206
|
return selectContextManagementStrategy({
|
|
1135
1207
|
serverContextManagementEnabled: llmConfig?.serverContextManagement?.enabled,
|
|
1136
1208
|
thresholds: llmConfig?.serverContextManagement,
|
|
1137
|
-
summarizationModel: () => createLLMProvider(this.getLLMProviderSettings(llmConfig), () => this.getInferenceJwt(), llmConfig, () => this.getLlmAiAttributes(this.clark)).modelForTask("summarizeMessages").model,
|
|
1209
|
+
summarizationModel: () => createLLMProvider(this.getLLMProviderSettings(llmConfig), () => this.getInferenceJwt(), llmConfig, () => this.getLlmAiAttributes(this.clark), this.clark.context.traceHeaders).modelForTask("summarizeMessages").model,
|
|
1138
1210
|
});
|
|
1139
1211
|
}
|
|
1140
1212
|
getLlmAiAttributes(clark) {
|
|
@@ -1384,14 +1456,14 @@ export class AiService extends TracedEventEmitter {
|
|
|
1384
1456
|
async tryHandlePaywallForRequest(request) {
|
|
1385
1457
|
try {
|
|
1386
1458
|
await this.ensureAiQuotaAvailable(request.jwt);
|
|
1387
|
-
return
|
|
1459
|
+
return undefined;
|
|
1388
1460
|
}
|
|
1389
1461
|
catch (error) {
|
|
1390
1462
|
if (error instanceof Error) {
|
|
1391
1463
|
const reason = getAiQuotaPaywallReasonFromMessage(error.message);
|
|
1392
1464
|
if (reason) {
|
|
1393
1465
|
await this.handlePreCallPaywall(request, reason);
|
|
1394
|
-
return
|
|
1466
|
+
return reason;
|
|
1395
1467
|
}
|
|
1396
1468
|
}
|
|
1397
1469
|
throw error;
|
|
@@ -1666,6 +1738,10 @@ export class AiService extends TracedEventEmitter {
|
|
|
1666
1738
|
async handleAiGenerate(request) {
|
|
1667
1739
|
this.getLogger().info(`[ai-service] handleAiGenerate: peerId=${this.stablePeer.peerId ?? "unknown"}`);
|
|
1668
1740
|
if (this.clark.state === ClarkStateNames.Dead) {
|
|
1741
|
+
recordPromptEntryRejection({
|
|
1742
|
+
dedupeKey: resolvePromptRequestDedupeKey(request),
|
|
1743
|
+
failure: promptFailurePlatform("service_dead"),
|
|
1744
|
+
});
|
|
1669
1745
|
throw new Error("Service is unavailable");
|
|
1670
1746
|
}
|
|
1671
1747
|
// a reconnect can cause the client to re-send this prompt
|
|
@@ -1676,6 +1752,10 @@ export class AiService extends TracedEventEmitter {
|
|
|
1676
1752
|
this.assertNoActivePolicyGateRun();
|
|
1677
1753
|
if (this.isBusy()) {
|
|
1678
1754
|
this.getLogger().warn("[ai-service] Service is busy. State:", this.clark.state);
|
|
1755
|
+
recordPromptEntryRejection({
|
|
1756
|
+
dedupeKey: resolvePromptRequestDedupeKey(request),
|
|
1757
|
+
failure: promptFailurePlatform("service_busy"),
|
|
1758
|
+
});
|
|
1679
1759
|
throw new Error("Service is busy");
|
|
1680
1760
|
}
|
|
1681
1761
|
// Pre-call quota check. When denied, `ensureAiQuotaAvailable` throws so
|
|
@@ -1688,7 +1768,12 @@ export class AiService extends TracedEventEmitter {
|
|
|
1688
1768
|
// advice + open modal via the same `aiSetGenerationState(CANCELLED,
|
|
1689
1769
|
// paywallReason)` saga path the mid-stream paywall uses, then resolve
|
|
1690
1770
|
// the socket call so the UI's catch doesn't run. (APPS-4080)
|
|
1691
|
-
|
|
1771
|
+
const paywallReason = await this.tryHandlePaywallForRequest(request);
|
|
1772
|
+
if (paywallReason) {
|
|
1773
|
+
recordPromptEntryRejection({
|
|
1774
|
+
dedupeKey: resolvePromptRequestDedupeKey(request),
|
|
1775
|
+
failure: promptFailurePaywall(paywallReason),
|
|
1776
|
+
});
|
|
1692
1777
|
return;
|
|
1693
1778
|
}
|
|
1694
1779
|
if (request.requestId &&
|
|
@@ -1696,11 +1781,18 @@ export class AiService extends TracedEventEmitter {
|
|
|
1696
1781
|
this.getLogger().warn("[ai-service] Ignoring duplicate aiGenerate re-send (lost dedup race)", { requestId: request.requestId });
|
|
1697
1782
|
return;
|
|
1698
1783
|
}
|
|
1784
|
+
recordPromptAttempt(resolvePromptRequestDedupeKey(request));
|
|
1699
1785
|
// The id is now recorded. If anything below throws before generation is
|
|
1700
1786
|
// actually kicked off (the `transitionTo` USER_SENT_PROMPT), evict it so a
|
|
1701
1787
|
// legitimate reconnect re-send of this same prompt is not swallowed as a
|
|
1702
1788
|
// duplicate while nothing is running. (APPS-4663)
|
|
1789
|
+
let generationTrace;
|
|
1703
1790
|
try {
|
|
1791
|
+
// Generate prompt_id for this user interaction. It doubles as the
|
|
1792
|
+
// generation id on terminal events, so the trace needs it up front.
|
|
1793
|
+
const promptId = randomUUID();
|
|
1794
|
+
generationTrace = this.startGenerationTrace(request, promptId);
|
|
1795
|
+
const activeGenerationTrace = generationTrace;
|
|
1704
1796
|
this.clarkProfiler.startSession();
|
|
1705
1797
|
getLogger().info("AI Service received request with llmConfig:", request.llmConfig || {});
|
|
1706
1798
|
getLogger().info("[ai-service] handleAiGenerate received request", {
|
|
@@ -1710,16 +1802,20 @@ export class AiService extends TracedEventEmitter {
|
|
|
1710
1802
|
existingPlanContextApproved: this.clark.context.planContext?.approved,
|
|
1711
1803
|
responseMetadataType: request.responseMetadata?.type,
|
|
1712
1804
|
});
|
|
1713
|
-
|
|
1714
|
-
const promptId = randomUUID();
|
|
1805
|
+
const promptOutcomeDedupeKey = resolvePromptRequestDedupeKey(request);
|
|
1715
1806
|
getLogger().info("[ai-service] Generated prompt_id for user interaction", {
|
|
1716
1807
|
promptId,
|
|
1808
|
+
requestId: request.requestId,
|
|
1809
|
+
dedupeKeySource: request.requestId ? "requestId" : "ephemeral",
|
|
1717
1810
|
applicationId: this.config.applicationId,
|
|
1718
1811
|
});
|
|
1719
1812
|
this.clark.updateContext((context) => {
|
|
1720
1813
|
return {
|
|
1721
1814
|
...context,
|
|
1722
1815
|
promptId,
|
|
1816
|
+
promptOutcomeDedupeKey,
|
|
1817
|
+
isInterruptAbort: undefined,
|
|
1818
|
+
userInitiatedPromptAbort: undefined,
|
|
1723
1819
|
// Third-party import turns are billed as app_migration; everything
|
|
1724
1820
|
// else falls back to the default in-app Clark generation metadata.
|
|
1725
1821
|
usageMetadata: request.importSource
|
|
@@ -1740,20 +1836,42 @@ export class AiService extends TracedEventEmitter {
|
|
|
1740
1836
|
workflowStartedAt: new Date().toISOString(),
|
|
1741
1837
|
},
|
|
1742
1838
|
llmConfig: request.llmConfig,
|
|
1839
|
+
traceHeaders: activeGenerationTrace.traceHeaders,
|
|
1743
1840
|
partialResponse: undefined,
|
|
1744
1841
|
userPreferences: request.userPreferences,
|
|
1745
1842
|
};
|
|
1746
1843
|
});
|
|
1747
1844
|
const transitionTo = transitionFrom(this.clark);
|
|
1748
|
-
void
|
|
1845
|
+
void otelContext
|
|
1846
|
+
.with(activeGenerationTrace.context, () => Promise.resolve(transitionTo({
|
|
1749
1847
|
type: USER_SENT_PROMPT,
|
|
1750
1848
|
request,
|
|
1849
|
+
})))
|
|
1850
|
+
.catch((error) => {
|
|
1851
|
+
activeGenerationTrace.finish(error);
|
|
1852
|
+
if (request.requestId) {
|
|
1853
|
+
this.requestDeduplicator.forget(request.requestId);
|
|
1854
|
+
}
|
|
1855
|
+
recordPromptOutcome({
|
|
1856
|
+
dedupeKey: promptOutcomeDedupeKey,
|
|
1857
|
+
outcome: "failure",
|
|
1858
|
+
failure: promptFailurePlatform("handler_error"),
|
|
1859
|
+
});
|
|
1860
|
+
this.getLogger().error("[ai-service] Failed to start generation", {
|
|
1861
|
+
error,
|
|
1862
|
+
});
|
|
1751
1863
|
});
|
|
1752
1864
|
}
|
|
1753
1865
|
catch (error) {
|
|
1866
|
+
generationTrace?.finish(error);
|
|
1754
1867
|
if (request.requestId) {
|
|
1755
1868
|
this.requestDeduplicator.forget(request.requestId);
|
|
1756
1869
|
}
|
|
1870
|
+
recordPromptOutcome({
|
|
1871
|
+
dedupeKey: resolvePromptRequestDedupeKey(request),
|
|
1872
|
+
outcome: "failure",
|
|
1873
|
+
failure: promptFailurePlatform("handler_error"),
|
|
1874
|
+
});
|
|
1757
1875
|
throw error;
|
|
1758
1876
|
}
|
|
1759
1877
|
}
|
|
@@ -2049,7 +2167,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
2049
2167
|
application_id: this.config.applicationId,
|
|
2050
2168
|
session_id: this.sessionId,
|
|
2051
2169
|
}));
|
|
2052
|
-
const { model } = llmProvider.modelForTask("fast");
|
|
2170
|
+
const { model, providerConfig } = llmProvider.modelForTask("fast");
|
|
2053
2171
|
const LabelAttachmentSchema = z.object({
|
|
2054
2172
|
label: z
|
|
2055
2173
|
.string()
|
|
@@ -2065,21 +2183,31 @@ export class AiService extends TracedEventEmitter {
|
|
|
2065
2183
|
try {
|
|
2066
2184
|
// Build the appropriate content part based on attachment type
|
|
2067
2185
|
const contentPart = this.attachmentToContentPart(attachment);
|
|
2068
|
-
|
|
2186
|
+
// Route labeling through the shared LLMClient (APPS-5061) as a stateless
|
|
2187
|
+
// one-shot, mirroring the APPS-1213 (#20263) migrations. No safety config:
|
|
2188
|
+
// labeling had no safety pass before this migration, and redacting the
|
|
2189
|
+
// upload before the model would defeat labeling (the model must see it).
|
|
2190
|
+
// Upload content can still carry secrets, so we keep it out of telemetry
|
|
2191
|
+
// via recordObsContent: false rather than via a safety pass.
|
|
2192
|
+
const result = await this.llmClient.generateObject({
|
|
2069
2193
|
model,
|
|
2070
2194
|
schema: LabelAttachmentSchema,
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2195
|
+
context: { mode: "stateless" },
|
|
2196
|
+
// Raw user data may carry secrets; keep it out of LLMObs span content
|
|
2197
|
+
// (metrics/model/timing are still recorded).
|
|
2198
|
+
recordObsContent: false,
|
|
2199
|
+
user: {
|
|
2200
|
+
role: "user",
|
|
2201
|
+
content: [
|
|
2202
|
+
{
|
|
2203
|
+
type: "text",
|
|
2204
|
+
text: "Analyze this attachment and provide a brief label and key insight about its content.",
|
|
2205
|
+
},
|
|
2206
|
+
contentPart,
|
|
2207
|
+
],
|
|
2208
|
+
},
|
|
2209
|
+
providerConfig,
|
|
2210
|
+
retry: { maxAttempts: 2 },
|
|
2083
2211
|
abortSignal: timeoutController.signal,
|
|
2084
2212
|
});
|
|
2085
2213
|
const durationMs = Date.now() - startTime;
|
|
@@ -2207,17 +2335,44 @@ export class AiService extends TracedEventEmitter {
|
|
|
2207
2335
|
await this.clark.recordSystemMessage(params, "Environment event", "User closed the integration setup form. The integration setup might not have been completed successfully.");
|
|
2208
2336
|
}
|
|
2209
2337
|
async handleUserCanceled() {
|
|
2210
|
-
|
|
2338
|
+
const awaitingUser = this.isAwaitingUser();
|
|
2339
|
+
if (!this.isBusy() && !awaitingUser) {
|
|
2211
2340
|
return;
|
|
2212
2341
|
}
|
|
2342
|
+
this.clark.updateContext((context) => ({
|
|
2343
|
+
...context,
|
|
2344
|
+
userInitiatedPromptAbort: true,
|
|
2345
|
+
}));
|
|
2213
2346
|
this.clark.context.abortController?.abort();
|
|
2347
|
+
if (this.isBusy()) {
|
|
2348
|
+
recordPromptOutcome({
|
|
2349
|
+
dedupeKey: resolvePromptOutcomeDedupeKey(this.clark.context),
|
|
2350
|
+
outcome: "failure",
|
|
2351
|
+
failure: promptFailureUserCanceled(),
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
if (awaitingUser && this.clark.context.awaitingUserHandoffRecorded) {
|
|
2355
|
+
recordAwaitingUserHandoffDismissed({
|
|
2356
|
+
awaitingUserHandoffPromptId: this.clark.context.awaitingUserHandoffPromptId,
|
|
2357
|
+
awaitingUserHandoffRecorded: this.clark.context.awaitingUserHandoffRecorded,
|
|
2358
|
+
promptId: this.clark.context.promptId,
|
|
2359
|
+
promptOutcomeDedupeKey: this.clark.context.promptOutcomeDedupeKey,
|
|
2360
|
+
reason: "cancel",
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2214
2363
|
// Clear the prompt_id since this prompt was abandoned
|
|
2215
2364
|
this.clark.updateContext((context) => ({
|
|
2216
2365
|
...context,
|
|
2217
2366
|
promptId: undefined,
|
|
2367
|
+
promptOutcomeDedupeKey: undefined,
|
|
2218
2368
|
usageMetadata: undefined,
|
|
2369
|
+
awaitingUserHandoffPromptId: undefined,
|
|
2370
|
+
awaitingUserHandoffRecorded: undefined,
|
|
2219
2371
|
}));
|
|
2220
|
-
void this.clark.send({
|
|
2372
|
+
void this.clark.send({
|
|
2373
|
+
type: USER_CANCELED,
|
|
2374
|
+
failure: promptFailureUserCanceled(),
|
|
2375
|
+
});
|
|
2221
2376
|
}
|
|
2222
2377
|
/**
|
|
2223
2378
|
* UI-initiated cancellation of a background task. Delegates to
|
|
@@ -2268,11 +2423,16 @@ export class AiService extends TracedEventEmitter {
|
|
|
2268
2423
|
async handleAiGenerateWithQueue(request, mode) {
|
|
2269
2424
|
this.getLogger().info(`[ai-service] handleAiGenerateWithQueue: peerId=${this.stablePeer.peerId ?? "unknown"}, mode=${mode}`);
|
|
2270
2425
|
if (this.clark.state === ClarkStateNames.Dead) {
|
|
2426
|
+
recordPromptEntryRejection({
|
|
2427
|
+
dedupeKey: resolvePromptRequestDedupeKey(request),
|
|
2428
|
+
failure: promptFailurePlatform("service_dead"),
|
|
2429
|
+
});
|
|
2271
2430
|
throw new Error("Service is unavailable");
|
|
2272
2431
|
}
|
|
2273
2432
|
// peek (do not record) so a re-sent prompt is dropped before the
|
|
2274
|
-
// interrupt path tears down the in-flight generation.
|
|
2275
|
-
//
|
|
2433
|
+
// interrupt path tears down the in-flight generation. Entry metrics
|
|
2434
|
+
// (attempt + pre-call paywall) are recorded in handleAiGenerate or
|
|
2435
|
+
// interruptAndContinue once this proceeds.
|
|
2276
2436
|
if (request.requestId && this.requestDeduplicator.has(request.requestId)) {
|
|
2277
2437
|
this.getLogger().info("[ai-service] Ignoring duplicate aiGenerateWithQueue re-send", { requestId: request.requestId, mode });
|
|
2278
2438
|
return { status: "started" };
|
|
@@ -2306,7 +2466,10 @@ export class AiService extends TracedEventEmitter {
|
|
|
2306
2466
|
availableIntegrations: this.clark.context.availableIntegrations,
|
|
2307
2467
|
};
|
|
2308
2468
|
this.getLogger().info(`[ai-service] Interrupting current generation. Partial response length: ${partialResponse.length}`);
|
|
2309
|
-
this.clark.updateContext({
|
|
2469
|
+
this.clark.updateContext({
|
|
2470
|
+
isInterruptAbort: true,
|
|
2471
|
+
userInitiatedPromptAbort: true,
|
|
2472
|
+
});
|
|
2310
2473
|
this.clark.context.abortController?.abort();
|
|
2311
2474
|
await this.waitForAbortComplete();
|
|
2312
2475
|
// Note: `interruptedPreviousGeneration` and `interruptOriginalPrompt`
|
|
@@ -2322,6 +2485,8 @@ export class AiService extends TracedEventEmitter {
|
|
|
2322
2485
|
availableIntegrations: preservedContext.availableIntegrations,
|
|
2323
2486
|
abortController: new AbortController(),
|
|
2324
2487
|
partialResponse: undefined,
|
|
2488
|
+
isInterruptAbort: undefined,
|
|
2489
|
+
userInitiatedPromptAbort: undefined,
|
|
2325
2490
|
});
|
|
2326
2491
|
if (partialResponse.trim().length > 10) {
|
|
2327
2492
|
await this.chatSessionStore.recordAssistant({
|
|
@@ -2335,7 +2500,12 @@ export class AiService extends TracedEventEmitter {
|
|
|
2335
2500
|
// `handleAiGenerate`. The aborted run is NOT resumed; the user can
|
|
2336
2501
|
// start fresh once credits are restored via the same "send any
|
|
2337
2502
|
// message" affordance the standard paywall uses.
|
|
2338
|
-
|
|
2503
|
+
const paywallReason = await this.tryHandlePaywallForRequest(request);
|
|
2504
|
+
if (paywallReason) {
|
|
2505
|
+
recordPromptEntryRejection({
|
|
2506
|
+
dedupeKey: resolvePromptRequestDedupeKey(request),
|
|
2507
|
+
failure: promptFailurePaywall(paywallReason),
|
|
2508
|
+
});
|
|
2339
2509
|
return;
|
|
2340
2510
|
}
|
|
2341
2511
|
// Only set the interrupt-state fields once we're committed to calling
|
|
@@ -3636,6 +3806,11 @@ export class AiService extends TracedEventEmitter {
|
|
|
3636
3806
|
* Should be called when Clark transitions to Idle or AwaitingUser state.
|
|
3637
3807
|
*/
|
|
3638
3808
|
triggerGenerationComplete() {
|
|
3809
|
+
// The FSM parked, so any still-open generation span is over. Runs that
|
|
3810
|
+
// completed or failed already ended it via the generation events; this
|
|
3811
|
+
// ends it on the paths that emit neither (user/agent cancel,
|
|
3812
|
+
// runtime-updated-without-edits, planning errors that park in Idle).
|
|
3813
|
+
this.activeGenerationTrace?.finish();
|
|
3639
3814
|
if (this._onGenerationCompleteCallback) {
|
|
3640
3815
|
const callback = this._onGenerationCompleteCallback;
|
|
3641
3816
|
this._onGenerationCompleteCallback = undefined;
|