@superblocksteam/vite-plugin-file-sync 2.0.130-next.1 → 2.0.130-next.2
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/tool-message-utils.d.ts.map +1 -1
- package/dist/ai-service/agent/tool-message-utils.js +30 -0
- package/dist/ai-service/agent/tool-message-utils.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts +8 -0
- 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 +94 -4
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js.map +1 -1
- package/dist/ai-service/agent/utils.d.ts.map +1 -1
- package/dist/ai-service/agent/utils.js +6 -1
- package/dist/ai-service/agent/utils.js.map +1 -1
- package/dist/ai-service/app-skills/helpers.d.ts.map +1 -1
- package/dist/ai-service/app-skills/helpers.js +17 -18
- package/dist/ai-service/app-skills/helpers.js.map +1 -1
- package/dist/ai-service/chat/chat-push-metrics.d.ts +12 -0
- package/dist/ai-service/chat/chat-push-metrics.d.ts.map +1 -0
- package/dist/ai-service/chat/chat-push-metrics.js +68 -0
- package/dist/ai-service/chat/chat-push-metrics.js.map +1 -0
- package/dist/ai-service/chat/chat-session-store.d.ts +61 -5
- package/dist/ai-service/chat/chat-session-store.d.ts.map +1 -1
- package/dist/ai-service/chat/chat-session-store.js +214 -8
- package/dist/ai-service/chat/chat-session-store.js.map +1 -1
- package/dist/ai-service/chat/transcript-budget.d.ts +108 -0
- package/dist/ai-service/chat/transcript-budget.d.ts.map +1 -0
- package/dist/ai-service/chat/transcript-budget.js +175 -0
- package/dist/ai-service/chat/transcript-budget.js.map +1 -0
- package/dist/ai-service/chat/transcript-metrics.d.ts +16 -0
- package/dist/ai-service/chat/transcript-metrics.d.ts.map +1 -0
- package/dist/ai-service/chat/transcript-metrics.js +128 -0
- package/dist/ai-service/chat/transcript-metrics.js.map +1 -0
- package/dist/ai-service/index.d.ts.map +1 -1
- package/dist/ai-service/index.js +4 -0
- package/dist/ai-service/index.js.map +1 -1
- package/dist/ai-service/llm/clark-stream-telemetry-sink.d.ts +4 -0
- package/dist/ai-service/llm/clark-stream-telemetry-sink.d.ts.map +1 -0
- package/dist/ai-service/llm/clark-stream-telemetry-sink.js +105 -0
- package/dist/ai-service/llm/clark-stream-telemetry-sink.js.map +1 -0
- package/dist/ai-service/llm/client.d.ts.map +1 -1
- package/dist/ai-service/llm/client.js +63 -39
- package/dist/ai-service/llm/client.js.map +1 -1
- package/dist/ai-service/llm/context-v2/compaction/client-side.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/compaction/client-side.js +10 -1
- package/dist/ai-service/llm/context-v2/compaction/client-side.js.map +1 -1
- package/dist/ai-service/llm/context-v2/context.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/context.js +43 -2
- package/dist/ai-service/llm/context-v2/context.js.map +1 -1
- package/dist/ai-service/llm/context-v2/storage/event-types.d.ts +10 -1
- package/dist/ai-service/llm/context-v2/storage/event-types.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/storage/event-types.js +7 -1
- package/dist/ai-service/llm/context-v2/storage/event-types.js.map +1 -1
- package/dist/ai-service/llm/stream/config.d.ts +30 -0
- package/dist/ai-service/llm/stream/config.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/config.js +34 -17
- package/dist/ai-service/llm/stream/config.js.map +1 -1
- package/dist/ai-service/llm/stream/errors.d.ts +10 -1
- package/dist/ai-service/llm/stream/errors.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/errors.js +3 -1
- package/dist/ai-service/llm/stream/errors.js.map +1 -1
- package/dist/ai-service/llm/stream/idle-monitor.d.ts +13 -4
- package/dist/ai-service/llm/stream/idle-monitor.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/idle-monitor.js +28 -6
- package/dist/ai-service/llm/stream/idle-monitor.js.map +1 -1
- package/dist/ai-service/llm/stream/index.d.ts +1 -1
- package/dist/ai-service/llm/stream/index.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/index.js.map +1 -1
- package/dist/ai-service/llm/stream/managed-stream.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/managed-stream.js +1 -0
- package/dist/ai-service/llm/stream/managed-stream.js.map +1 -1
- package/dist/ai-service/llm/stream/orchestrator.d.ts +19 -0
- package/dist/ai-service/llm/stream/orchestrator.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/orchestrator.js +277 -29
- package/dist/ai-service/llm/stream/orchestrator.js.map +1 -1
- package/dist/ai-service/llm/stream/retry-engine.d.ts +15 -0
- package/dist/ai-service/llm/stream/retry-engine.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/retry-engine.js +55 -2
- package/dist/ai-service/llm/stream/retry-engine.js.map +1 -1
- package/dist/ai-service/llm/stream/session.d.ts +3 -0
- package/dist/ai-service/llm/stream/session.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/session.js +13 -0
- package/dist/ai-service/llm/stream/session.js.map +1 -1
- package/dist/ai-service/llm/stream/wait-state-tracker.d.ts +54 -0
- package/dist/ai-service/llm/stream/wait-state-tracker.d.ts.map +1 -0
- package/dist/ai-service/llm/stream/wait-state-tracker.js +105 -0
- package/dist/ai-service/llm/stream/wait-state-tracker.js.map +1 -0
- package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.js +13 -7
- 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 +55 -43
- package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/flush-pending-chat-pushes.d.ts +11 -0
- package/dist/ai-service/state-machine/handlers/flush-pending-chat-pushes.d.ts.map +1 -0
- package/dist/ai-service/state-machine/handlers/flush-pending-chat-pushes.js +11 -0
- package/dist/ai-service/state-machine/handlers/flush-pending-chat-pushes.js.map +1 -0
- 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 -0
- package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/background-chat-record-metrics.d.ts +8 -0
- package/dist/ai-service/state-machine/helpers/background-chat-record-metrics.d.ts.map +1 -0
- package/dist/ai-service/state-machine/helpers/background-chat-record-metrics.js +52 -0
- package/dist/ai-service/state-machine/helpers/background-chat-record-metrics.js.map +1 -0
- package/dist/ai-service/state-machine/helpers/background-chat-record.d.ts +33 -0
- package/dist/ai-service/state-machine/helpers/background-chat-record.d.ts.map +1 -0
- package/dist/ai-service/state-machine/helpers/background-chat-record.js +61 -0
- package/dist/ai-service/state-machine/helpers/background-chat-record.js.map +1 -0
- package/dist/ai-service/state-machine/mocks.d.ts.map +1 -1
- package/dist/ai-service/state-machine/mocks.js +1 -0
- package/dist/ai-service/state-machine/mocks.js.map +1 -1
- package/dist/ai-service/tasks/peer-push.d.ts +7 -4
- package/dist/ai-service/tasks/peer-push.d.ts.map +1 -1
- package/dist/ai-service/tasks/peer-push.js +7 -4
- package/dist/ai-service/tasks/peer-push.js.map +1 -1
- package/dist/migration-templates/app-fullstack/package.json +1 -1
- package/dist/socket-manager.d.ts +2 -1
- package/dist/socket-manager.d.ts.map +1 -1
- package/dist/socket-manager.js +17 -6
- package/dist/socket-manager.js.map +1 -1
- package/dist/util/logger.d.ts +1 -0
- package/dist/util/logger.d.ts.map +1 -1
- package/dist/util/logger.js.map +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why the push failed: `timeout` means the per-push bound elapsed (the RPC
|
|
3
|
+
* may still complete later — see `ChatSessionStore.PUSH_TIMEOUT_MS`);
|
|
4
|
+
* `rpc_error` means the call itself rejected.
|
|
5
|
+
*/
|
|
6
|
+
export declare const CHAT_PUSH_FAILURE_REASONS: readonly ["rpc_error", "timeout"];
|
|
7
|
+
export type ChatPushFailureReason = (typeof CHAT_PUSH_FAILURE_REASONS)[number];
|
|
8
|
+
export declare function recordChatPushFailure(input: {
|
|
9
|
+
reason: ChatPushFailureReason;
|
|
10
|
+
}): void;
|
|
11
|
+
export declare function _resetChatPushMetricsForTesting(): void;
|
|
12
|
+
//# sourceMappingURL=chat-push-metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-push-metrics.d.ts","sourceRoot":"","sources":["../../../src/ai-service/chat/chat-push-metrics.ts"],"names":[],"mappings":"AAwBA;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,mCAAoC,CAAC;AAC3E,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AA4C/E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,MAAM,EAAE,qBAAqB,CAAC;CAC/B,GAAG,IAAI,CAEP;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat-push failure metrics (APPS-4729).
|
|
3
|
+
*
|
|
4
|
+
* Chat pushes became fire-and-forget when the stream loop stopped awaiting
|
|
5
|
+
* `commitMessage`, so a failing or timing-out push no longer surfaces as a
|
|
6
|
+
* stalled generation — it surfaces only as an error log. This counter makes
|
|
7
|
+
* the failure *rate* alertable (e.g. "N% of pushes timing out over 5m"),
|
|
8
|
+
* which log-based monitors track too lazily for an on-call signal.
|
|
9
|
+
*
|
|
10
|
+
* Wiring follows `ClarkContextMetrics` (APPS-4708): the meter resolves
|
|
11
|
+
* lazily from the OTel global and the instrument re-binds when the global
|
|
12
|
+
* MeterProvider changes (tests swap providers per case). Zero-config process
|
|
13
|
+
* singleton — no DI plumbing.
|
|
14
|
+
*
|
|
15
|
+
* Metric:
|
|
16
|
+
*
|
|
17
|
+
* - `superblocks.clark.chat.push_failures` counter {reason}
|
|
18
|
+
*
|
|
19
|
+
* Cardinality budget: reason ≤ 2 → 2 worst-case series. The label is a
|
|
20
|
+
* compile-time closed union supplied by Superblocks-owned call sites (never
|
|
21
|
+
* derived from user input or error text).
|
|
22
|
+
*/
|
|
23
|
+
import { metrics } from "@opentelemetry/api";
|
|
24
|
+
/**
|
|
25
|
+
* Why the push failed: `timeout` means the per-push bound elapsed (the RPC
|
|
26
|
+
* may still complete later — see `ChatSessionStore.PUSH_TIMEOUT_MS`);
|
|
27
|
+
* `rpc_error` means the call itself rejected.
|
|
28
|
+
*/
|
|
29
|
+
export const CHAT_PUSH_FAILURE_REASONS = ["rpc_error", "timeout"];
|
|
30
|
+
const METER_NAME = "superblocks.clark.chat";
|
|
31
|
+
const METRIC_PUSH_FAILURES = "superblocks.clark.chat.push_failures";
|
|
32
|
+
class ChatPushMetrics {
|
|
33
|
+
cachedMeter;
|
|
34
|
+
cachedCounter;
|
|
35
|
+
/**
|
|
36
|
+
* Lazy instrument factory. Re-creates the counter when the global
|
|
37
|
+
* MeterProvider changes (a stale instrument bound to a replaced provider
|
|
38
|
+
* silently drops data).
|
|
39
|
+
*/
|
|
40
|
+
getCounter() {
|
|
41
|
+
const meter = metrics.getMeter(METER_NAME);
|
|
42
|
+
if (meter === this.cachedMeter && this.cachedCounter) {
|
|
43
|
+
return this.cachedCounter;
|
|
44
|
+
}
|
|
45
|
+
this.cachedMeter = meter;
|
|
46
|
+
this.cachedCounter = meter.createCounter(METRIC_PUSH_FAILURES, {
|
|
47
|
+
description: "Chat-history pushes that failed or exceeded the per-push timeout after the stream loop stopped awaiting them (APPS-4729).",
|
|
48
|
+
unit: "{failure}",
|
|
49
|
+
});
|
|
50
|
+
return this.cachedCounter;
|
|
51
|
+
}
|
|
52
|
+
recordFailure(input) {
|
|
53
|
+
this.getCounter().add(1, { reason: input.reason });
|
|
54
|
+
}
|
|
55
|
+
/** Drop cached instrument bindings (tests swap the global provider). */
|
|
56
|
+
_resetForTesting() {
|
|
57
|
+
this.cachedMeter = undefined;
|
|
58
|
+
this.cachedCounter = undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const chatPushMetrics = new ChatPushMetrics();
|
|
62
|
+
export function recordChatPushFailure(input) {
|
|
63
|
+
chatPushMetrics.recordFailure(input);
|
|
64
|
+
}
|
|
65
|
+
export function _resetChatPushMetricsForTesting() {
|
|
66
|
+
chatPushMetrics._resetForTesting();
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=chat-push-metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-push-metrics.js","sourceRoot":"","sources":["../../../src/ai-service/chat/chat-push-metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,OAAO,EAAc,MAAM,oBAAoB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,WAAW,EAAE,SAAS,CAAU,CAAC;AAG3E,MAAM,UAAU,GAAG,wBAAwB,CAAC;AAE5C,MAAM,oBAAoB,GAAG,sCAAsC,CAAC;AAIpE,MAAM,eAAe;IACX,WAAW,CAAoB;IAC/B,aAAa,CAAsB;IAE3C;;;;OAIG;IACK,UAAU;QAChB,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC3C,IAAI,KAAK,KAAK,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACrD,OAAO,IAAI,CAAC,aAAa,CAAC;QAC5B,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;QACzB,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC,oBAAoB,EAAE;YAC7D,WAAW,EACT,2HAA2H;YAC7H,IAAI,EAAE,WAAW;SAClB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,aAAa,CAAC,KAAwC;QACpD,IAAI,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,wEAAwE;IACxE,gBAAgB;QACd,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;QAC7B,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;IACjC,CAAC;CACF;AAED,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;AAE9C,MAAM,UAAU,qBAAqB,CAAC,KAErC;IACC,eAAe,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,+BAA+B;IAC7C,eAAe,CAAC,gBAAgB,EAAE,CAAC;AACrC,CAAC"}
|
|
@@ -3,6 +3,7 @@ import type { AiPromptCostUsageEntry, AiWorkflowEvent } from "@superblocksteam/s
|
|
|
3
3
|
import type { AutoConnectingRpcClient } from "../../server-rpc/index.js";
|
|
4
4
|
import { type Logger } from "../../util/logger.js";
|
|
5
5
|
import type { LLMObsTracer } from "../llmobs/tracer.js";
|
|
6
|
+
import { type HeapMonitor } from "./transcript-budget.js";
|
|
6
7
|
export type PromptCostUsageEntry = AiPromptCostUsageEntry;
|
|
7
8
|
export type AiChatItem = AiChatMessage | AiChatSummary;
|
|
8
9
|
export type AiChatSummaryWithOutcome = AiChatSummary & {
|
|
@@ -15,6 +16,12 @@ export declare function isAiChatSummary(item: AiChatItem): item is AiChatSummary
|
|
|
15
16
|
* This prevents large requests from failing when there are many messages.
|
|
16
17
|
*/
|
|
17
18
|
export declare const DEFAULT_MESSAGE_LIMIT = 3000;
|
|
19
|
+
export interface TranscriptBudgetConfig {
|
|
20
|
+
maxResidentBytes?: number;
|
|
21
|
+
maxResidentItems?: number;
|
|
22
|
+
minResidentItems?: number;
|
|
23
|
+
heapMonitor?: HeapMonitor;
|
|
24
|
+
}
|
|
18
25
|
interface ChatSessionStoreConfig {
|
|
19
26
|
applicationId: string;
|
|
20
27
|
rpcClient: AutoConnectingRpcClient;
|
|
@@ -26,6 +33,11 @@ interface ChatSessionStoreConfig {
|
|
|
26
33
|
messageLimit?: number;
|
|
27
34
|
/** Injectable for tests; defaults to the LLMObs singleton. */
|
|
28
35
|
tracer?: LLMObsTracer;
|
|
36
|
+
/**
|
|
37
|
+
* Bounds on the in-heap transcript. Defaults: see
|
|
38
|
+
* transcript-budget.ts; the count cap defaults to messageLimit.
|
|
39
|
+
*/
|
|
40
|
+
transcriptBudget?: TranscriptBudgetConfig;
|
|
29
41
|
}
|
|
30
42
|
export declare class ChatSessionStore {
|
|
31
43
|
private applicationId;
|
|
@@ -37,6 +49,17 @@ export declare class ChatSessionStore {
|
|
|
37
49
|
private loadedFromServer;
|
|
38
50
|
private inProgressMessages;
|
|
39
51
|
private messageLimit;
|
|
52
|
+
private transcriptBudget;
|
|
53
|
+
/**
|
|
54
|
+
* Kill-switch for the in-heap transcript budget. When set, the transcript
|
|
55
|
+
* grows unbounded as it did before the fix — a remediation lever if
|
|
56
|
+
* eviction ever misbehaves in production. Read once at startup, so toggling
|
|
57
|
+
* the env var needs a pod restart (no code deploy or rebuild) to take
|
|
58
|
+
* effect.
|
|
59
|
+
*/
|
|
60
|
+
private readonly transcriptBudgetDisabled;
|
|
61
|
+
private lastEvictionWarnAt;
|
|
62
|
+
private lastEvictionWarnTrigger;
|
|
40
63
|
constructor(config: ChatSessionStoreConfig);
|
|
41
64
|
/***
|
|
42
65
|
* Validates and sanitizes the attachments array
|
|
@@ -89,13 +112,20 @@ export declare class ChatSessionStore {
|
|
|
89
112
|
commitMessage(id: string, type: "text" | "reasoning", group?: string): Promise<void>;
|
|
90
113
|
recordAssistant(payload: AiChatMessage): Promise<void>;
|
|
91
114
|
recordAssistantChangeInfo(payload: AiChangeInfoPush): Promise<void>;
|
|
115
|
+
private pushChain;
|
|
116
|
+
private static readonly PUSH_TIMEOUT_MS;
|
|
117
|
+
private static readonly PUSH_TIMEOUT_MESSAGE;
|
|
118
|
+
private appendMessage;
|
|
92
119
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
120
|
+
* Resolves when every push queued so far has completed (or failed/timed
|
|
121
|
+
* out). Never rejects. Callers use this to make chat history durable at a
|
|
122
|
+
* boundary (end of generation) without coupling stream consumption to the
|
|
123
|
+
* RPC socket (APPS-4729).
|
|
97
124
|
*/
|
|
98
|
-
|
|
125
|
+
flushPendingPushes(): Promise<void>;
|
|
126
|
+
private boundedAppendMessage;
|
|
127
|
+
private static isPushTimeoutError;
|
|
128
|
+
private doAppendMessage;
|
|
99
129
|
submitWorkflowSummary(summary: AiWorkflowEvent): Promise<void>;
|
|
100
130
|
calculatePromptCost(promptId: string, usage: AiPromptCostUsageEntry[]): Promise<number>;
|
|
101
131
|
private determineSummaryOutcome;
|
|
@@ -114,6 +144,32 @@ export declare class ChatSessionStore {
|
|
|
114
144
|
* Used when checkpoint is restored to ensure ineligible summaries are filtered out
|
|
115
145
|
*/
|
|
116
146
|
invalidateCache(): void;
|
|
147
|
+
/**
|
|
148
|
+
* Reset everything tied to the resident transcript for a fresh session
|
|
149
|
+
* (newChat) or a cache invalidation (checkpoint restore): the loaded flag,
|
|
150
|
+
* the message array, the byte accounting, and the eviction-warn throttle.
|
|
151
|
+
* The throttle must reset too, otherwise a fresh transcript that evicts on
|
|
152
|
+
* the same trigger within the throttle window would skip its first
|
|
153
|
+
* chatTranscriptEvicted warn even though it is a different session.
|
|
154
|
+
*/
|
|
155
|
+
private resetTranscriptState;
|
|
156
|
+
/** Resident-transcript accounting, exposed for telemetry. */
|
|
157
|
+
getTranscriptStats(): {
|
|
158
|
+
residentBytes: number;
|
|
159
|
+
residentCount: number;
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Drop the oldest evictable transcript entries when over the byte budget,
|
|
163
|
+
* over the count cap, or under live heap pressure. The durable
|
|
164
|
+
* transcript stays server-side; this only bounds the in-heap copy.
|
|
165
|
+
*/
|
|
166
|
+
private enforceTranscriptBudget;
|
|
167
|
+
/**
|
|
168
|
+
* Throttle the eviction warn to one line per trigger per window. A trigger
|
|
169
|
+
* change always warns (meaningful transition); repeated same-trigger
|
|
170
|
+
* evictions on every append collapse to one line per EVICTION_WARN_THROTTLE_MS.
|
|
171
|
+
*/
|
|
172
|
+
private shouldWarnForEviction;
|
|
117
173
|
}
|
|
118
174
|
export {};
|
|
119
175
|
//# sourceMappingURL=chat-session-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-session-store.d.ts","sourceRoot":"","sources":["../../../src/ai-service/chat/chat-session-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,aAAa,EAiBlB,KAAK,UAAU,EAEf,KAAK,2BAA2B,EAKjC,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EACV,sBAAsB,EACtB,eAAe,EAChB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"chat-session-store.d.ts","sourceRoot":"","sources":["../../../src/ai-service/chat/chat-session-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,7 +35,10 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
35
35
|
import { EXCLUDED_MESSAGE_TYPES, TEXT_ATTACHMENT_MIME_TYPES, } from "@superblocksteam/library-shared/types";
|
|
36
36
|
import { getErrorMeta } from "../../util/logger.js";
|
|
37
37
|
import llmobs from "../llmobs/index.js";
|
|
38
|
+
import { recordChatPushFailure } from "./chat-push-metrics.js";
|
|
38
39
|
import { getLastUserMessage } from "./extract-history.js";
|
|
40
|
+
import { TranscriptBudget, } from "./transcript-budget.js";
|
|
41
|
+
import { recordTranscriptEviction, registerTranscriptResidentBytesSource, } from "./transcript-metrics.js";
|
|
39
42
|
import { inferTextAttachmentType, sanitizeAiProviderResponse, sanitizeToolOutput, } from "./utils.js";
|
|
40
43
|
export function isAiChatMessage(item) {
|
|
41
44
|
return "role" in item && "content" in item;
|
|
@@ -53,6 +56,40 @@ function isEligibleSummary(item) {
|
|
|
53
56
|
* This prevents large requests from failing when there are many messages.
|
|
54
57
|
*/
|
|
55
58
|
export const DEFAULT_MESSAGE_LIMIT = 3000;
|
|
59
|
+
/**
|
|
60
|
+
* Minimum gap between `chatTranscriptEvicted` warn logs for the *same*
|
|
61
|
+
* trigger. The OTel counters fire on every eviction (cheap, cumulative); the
|
|
62
|
+
* warn is a low-frequency sentinel. A pathological session evicts on every
|
|
63
|
+
* append, so an unthrottled warn would emit thousands of lines per session.
|
|
64
|
+
* A trigger change is always logged (it is a meaningful state transition);
|
|
65
|
+
* repeated same-trigger evictions are collapsed to one line per window.
|
|
66
|
+
*/
|
|
67
|
+
const EVICTION_WARN_THROTTLE_MS = 30_000;
|
|
68
|
+
/**
|
|
69
|
+
* Chat item types that are never evicted from the resident transcript.
|
|
70
|
+
* They are byte-tiny but semantically load-bearing as presence sentinels:
|
|
71
|
+
* commit markers feed getLatestCommitId (checkpoint restore), draft
|
|
72
|
+
* accept/reject feed determineSummaryOutcome, and aws_infra_demo gates the
|
|
73
|
+
* one-time demo card off "has this type ever appeared in history". Eviction
|
|
74
|
+
* is oldest-first and the demo sentinel is emitted on the first prompt, so
|
|
75
|
+
* without protection it is the first item dropped in exactly the long
|
|
76
|
+
* sessions this budget targets, which re-fires the demo card mid-session.
|
|
77
|
+
* Summaries are protected separately via isAiChatSummary.
|
|
78
|
+
*/
|
|
79
|
+
const EVICTION_PROTECTED_TYPES = new Set([
|
|
80
|
+
"aws_infra_demo",
|
|
81
|
+
"commitRestored",
|
|
82
|
+
"commitRestoring",
|
|
83
|
+
"draftAccepted",
|
|
84
|
+
"draftCommitted",
|
|
85
|
+
"draftRejected",
|
|
86
|
+
]);
|
|
87
|
+
function isEvictableChatItem(item) {
|
|
88
|
+
if (isAiChatSummary(item)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return item.type === undefined || !EVICTION_PROTECTED_TYPES.has(item.type);
|
|
92
|
+
}
|
|
56
93
|
let ChatSessionStore = (() => {
|
|
57
94
|
let _instanceExtraInitializers = [];
|
|
58
95
|
let _getMessages_decorators;
|
|
@@ -81,12 +118,36 @@ let ChatSessionStore = (() => {
|
|
|
81
118
|
loadedFromServer = "no";
|
|
82
119
|
inProgressMessages = {};
|
|
83
120
|
messageLimit;
|
|
121
|
+
transcriptBudget;
|
|
122
|
+
/**
|
|
123
|
+
* Kill-switch for the in-heap transcript budget. When set, the transcript
|
|
124
|
+
* grows unbounded as it did before the fix — a remediation lever if
|
|
125
|
+
* eviction ever misbehaves in production. Read once at startup, so toggling
|
|
126
|
+
* the env var needs a pod restart (no code deploy or rebuild) to take
|
|
127
|
+
* effect.
|
|
128
|
+
*/
|
|
129
|
+
transcriptBudgetDisabled = process.env.CLARK_TRANSCRIPT_BUDGET_DISABLED === "true";
|
|
130
|
+
lastEvictionWarnAt = 0;
|
|
131
|
+
lastEvictionWarnTrigger = null;
|
|
84
132
|
constructor(config) {
|
|
85
133
|
this.rpcClient = config.rpcClient;
|
|
86
134
|
this.applicationId = config.applicationId;
|
|
87
135
|
this.messages = [];
|
|
88
136
|
this.logger = config.logger;
|
|
89
137
|
this.messageLimit = config.messageLimit ?? DEFAULT_MESSAGE_LIMIT;
|
|
138
|
+
this.transcriptBudget = new TranscriptBudget({
|
|
139
|
+
isEvictable: isEvictableChatItem,
|
|
140
|
+
maxResidentBytes: config.transcriptBudget?.maxResidentBytes,
|
|
141
|
+
// The count cap defaults to messageLimit, which the load-time filter
|
|
142
|
+
// already enforces; it is a coarse backstop for items appended past the
|
|
143
|
+
// limit mid-session, behind the byte and heap-pressure triggers.
|
|
144
|
+
maxResidentItems: config.transcriptBudget?.maxResidentItems ?? this.messageLimit,
|
|
145
|
+
minResidentItems: config.transcriptBudget?.minResidentItems,
|
|
146
|
+
heapMonitor: config.transcriptBudget?.heapMonitor,
|
|
147
|
+
});
|
|
148
|
+
// The observable gauge pulls resident bytes from the live budget every
|
|
149
|
+
// collect interval (one store per dev-server process; replace semantics).
|
|
150
|
+
registerTranscriptResidentBytesSource(() => this.transcriptBudget.residentBytes);
|
|
90
151
|
this.tracer = config.tracer ?? llmobs;
|
|
91
152
|
// NOTE: This is used to serialize messages to be after any load.
|
|
92
153
|
// We initialize it as resolved to handle the prompt-from-empty-state case.
|
|
@@ -661,6 +722,9 @@ let ChatSessionStore = (() => {
|
|
|
661
722
|
if (entities) {
|
|
662
723
|
baseMessage.entities = entities;
|
|
663
724
|
}
|
|
725
|
+
if (msg.includesFreshDatabase === true) {
|
|
726
|
+
baseMessage.includesFreshDatabase = true;
|
|
727
|
+
}
|
|
664
728
|
}
|
|
665
729
|
// Preserve commitId for commitRestored, commitRestoring, and draftCommitted messages
|
|
666
730
|
if ((msg.type === "commitRestored" ||
|
|
@@ -783,6 +847,8 @@ let ChatSessionStore = (() => {
|
|
|
783
847
|
.sort((a, b) => {
|
|
784
848
|
return (a.timestamp || 0) - (b.timestamp || 0);
|
|
785
849
|
}) ?? [];
|
|
850
|
+
this.transcriptBudget.reset(this.messages);
|
|
851
|
+
this.enforceTranscriptBudget();
|
|
786
852
|
this.logger.info(`[ai-service] chatHistoryLoadSummary accepted=${this.messages.length} dropped=${droppedTotal} total=${fromRpcTotal}`);
|
|
787
853
|
if (droppedTotal > 0) {
|
|
788
854
|
this.logger.warn(`[ai-service] chatHistoryDroppedMessages count=${droppedTotal}`);
|
|
@@ -835,8 +901,7 @@ let ChatSessionStore = (() => {
|
|
|
835
901
|
const applicationId = this.applicationId;
|
|
836
902
|
return await client.call.v1.ai.chat.new({ applicationId });
|
|
837
903
|
});
|
|
838
|
-
this.
|
|
839
|
-
this.messages = [];
|
|
904
|
+
this.resetTranscriptState();
|
|
840
905
|
this.logger.info("[ai-service] chatSessionNewSuccess");
|
|
841
906
|
}
|
|
842
907
|
catch (error) {
|
|
@@ -1002,13 +1067,71 @@ let ChatSessionStore = (() => {
|
|
|
1002
1067
|
};
|
|
1003
1068
|
await this.appendMessage(message);
|
|
1004
1069
|
}
|
|
1070
|
+
// Remote persistence is strictly serialized through this chain so message
|
|
1071
|
+
// order is preserved even when callers do not await (APPS-4729). The chain
|
|
1072
|
+
// link is caught so one failed push can never wedge all later pushes.
|
|
1073
|
+
pushChain = Promise.resolve();
|
|
1074
|
+
// Bounds a single remote push. The underlying socket's noResponseTimeout is
|
|
1075
|
+
// 5 minutes (sized for judge evaluation calls); a chat push on a half-open
|
|
1076
|
+
// socket must fail much faster than that or it stalls everything queued
|
|
1077
|
+
// behind it (APPS-4729). The underlying RPC may still complete later because
|
|
1078
|
+
// rpcClient.call does not expose cancellation yet; this bounds the queue, not
|
|
1079
|
+
// the socket call itself. Worst case on a dead socket: each timed-out push
|
|
1080
|
+
// leaves one orphaned RPC await holding a reference to this store for up to
|
|
1081
|
+
// the socket's 5-minute noResponseTimeout. The chain serializes pushes, so
|
|
1082
|
+
// orphans spawn at most one per PUSH_TIMEOUT_MS — concurrency is capped at
|
|
1083
|
+
// ~noResponseTimeout / PUSH_TIMEOUT_MS (~15) regardless of how many blocks
|
|
1084
|
+
// a generation committed. They settle on socket close and hold no other
|
|
1085
|
+
// resources. TODO(APPS-4769): switch to AbortSignal once rpcClient.call
|
|
1086
|
+
// supports it.
|
|
1087
|
+
static PUSH_TIMEOUT_MS = 20_000;
|
|
1088
|
+
static PUSH_TIMEOUT_MESSAGE = "chat push timed out";
|
|
1089
|
+
appendMessage(message) {
|
|
1090
|
+
const result = this.pushChain.then(() => this.boundedAppendMessage(message));
|
|
1091
|
+
this.pushChain = result.catch(() => undefined);
|
|
1092
|
+
return result;
|
|
1093
|
+
}
|
|
1005
1094
|
/**
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1095
|
+
* Resolves when every push queued so far has completed (or failed/timed
|
|
1096
|
+
* out). Never rejects. Callers use this to make chat history durable at a
|
|
1097
|
+
* boundary (end of generation) without coupling stream consumption to the
|
|
1098
|
+
* RPC socket (APPS-4729).
|
|
1010
1099
|
*/
|
|
1011
|
-
async
|
|
1100
|
+
async flushPendingPushes() {
|
|
1101
|
+
await this.pushChain.catch(() => undefined);
|
|
1102
|
+
}
|
|
1103
|
+
async boundedAppendMessage(message) {
|
|
1104
|
+
let timer;
|
|
1105
|
+
try {
|
|
1106
|
+
return await Promise.race([
|
|
1107
|
+
this.doAppendMessage(message),
|
|
1108
|
+
new Promise((_, reject) => {
|
|
1109
|
+
timer = setTimeout(() => reject(new Error(`${ChatSessionStore.PUSH_TIMEOUT_MESSAGE} after ${ChatSessionStore.PUSH_TIMEOUT_MS}ms`)), ChatSessionStore.PUSH_TIMEOUT_MS);
|
|
1110
|
+
}),
|
|
1111
|
+
]);
|
|
1112
|
+
}
|
|
1113
|
+
catch (error) {
|
|
1114
|
+
const reason = ChatSessionStore.isPushTimeoutError(error)
|
|
1115
|
+
? "timeout"
|
|
1116
|
+
: "rpc_error";
|
|
1117
|
+
recordChatPushFailure({ reason });
|
|
1118
|
+
this.logger.error("[ai-service] Chat message push failed or timed out", {
|
|
1119
|
+
...getErrorMeta(error),
|
|
1120
|
+
reason,
|
|
1121
|
+
});
|
|
1122
|
+
return undefined;
|
|
1123
|
+
}
|
|
1124
|
+
finally {
|
|
1125
|
+
if (timer !== undefined) {
|
|
1126
|
+
clearTimeout(timer);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
static isPushTimeoutError(error) {
|
|
1131
|
+
return (error instanceof Error &&
|
|
1132
|
+
error.message.startsWith(ChatSessionStore.PUSH_TIMEOUT_MESSAGE));
|
|
1133
|
+
}
|
|
1134
|
+
async doAppendMessage(message) {
|
|
1012
1135
|
const messageType = "type" in message && typeof message.type === "string"
|
|
1013
1136
|
? message.type
|
|
1014
1137
|
: "unknown";
|
|
@@ -1028,6 +1151,8 @@ let ChatSessionStore = (() => {
|
|
|
1028
1151
|
const localMessageCountBefore = this.messages.length;
|
|
1029
1152
|
this.logger.info(`[ai-service] chatMessagePushStart count=${localMessageCountBefore}`);
|
|
1030
1153
|
this.messages.push(message);
|
|
1154
|
+
this.transcriptBudget.track(message);
|
|
1155
|
+
this.enforceTranscriptBudget();
|
|
1031
1156
|
const payload = {
|
|
1032
1157
|
...message,
|
|
1033
1158
|
};
|
|
@@ -1052,7 +1177,11 @@ let ChatSessionStore = (() => {
|
|
|
1052
1177
|
return response.data?.chatmessageId;
|
|
1053
1178
|
}
|
|
1054
1179
|
catch (error) {
|
|
1055
|
-
|
|
1180
|
+
recordChatPushFailure({ reason: "rpc_error" });
|
|
1181
|
+
this.logger.error("[ai-service] Failed to write chat message to server", {
|
|
1182
|
+
...getErrorMeta(error),
|
|
1183
|
+
reason: "rpc_error",
|
|
1184
|
+
});
|
|
1056
1185
|
annotate({ pushed: false, error });
|
|
1057
1186
|
return undefined;
|
|
1058
1187
|
}
|
|
@@ -1124,8 +1253,85 @@ let ChatSessionStore = (() => {
|
|
|
1124
1253
|
* Used when checkpoint is restored to ensure ineligible summaries are filtered out
|
|
1125
1254
|
*/
|
|
1126
1255
|
invalidateCache() {
|
|
1256
|
+
this.resetTranscriptState();
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Reset everything tied to the resident transcript for a fresh session
|
|
1260
|
+
* (newChat) or a cache invalidation (checkpoint restore): the loaded flag,
|
|
1261
|
+
* the message array, the byte accounting, and the eviction-warn throttle.
|
|
1262
|
+
* The throttle must reset too, otherwise a fresh transcript that evicts on
|
|
1263
|
+
* the same trigger within the throttle window would skip its first
|
|
1264
|
+
* chatTranscriptEvicted warn even though it is a different session.
|
|
1265
|
+
*/
|
|
1266
|
+
resetTranscriptState() {
|
|
1127
1267
|
this.loadedFromServer = "no";
|
|
1128
1268
|
this.messages = [];
|
|
1269
|
+
this.transcriptBudget.reset([]);
|
|
1270
|
+
this.lastEvictionWarnAt = 0;
|
|
1271
|
+
this.lastEvictionWarnTrigger = null;
|
|
1272
|
+
}
|
|
1273
|
+
/** Resident-transcript accounting, exposed for telemetry. */
|
|
1274
|
+
getTranscriptStats() {
|
|
1275
|
+
return {
|
|
1276
|
+
residentBytes: this.transcriptBudget.residentBytes,
|
|
1277
|
+
residentCount: this.messages.length,
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Drop the oldest evictable transcript entries when over the byte budget,
|
|
1282
|
+
* over the count cap, or under live heap pressure. The durable
|
|
1283
|
+
* transcript stays server-side; this only bounds the in-heap copy.
|
|
1284
|
+
*/
|
|
1285
|
+
enforceTranscriptBudget() {
|
|
1286
|
+
if (this.transcriptBudgetDisabled) {
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
const result = this.transcriptBudget.enforce(this.messages);
|
|
1290
|
+
// enforce() returns a non-null trigger whenever it evicts; bail otherwise.
|
|
1291
|
+
if (result.evictedCount === 0 || !result.trigger) {
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
const trigger = result.trigger;
|
|
1295
|
+
this.messages = result.kept;
|
|
1296
|
+
// Counter fires on every eviction; the warn below is throttled.
|
|
1297
|
+
recordTranscriptEviction({
|
|
1298
|
+
trigger,
|
|
1299
|
+
messages: result.evictedCount,
|
|
1300
|
+
bytes: result.evictedBytes,
|
|
1301
|
+
});
|
|
1302
|
+
if (!this.shouldWarnForEviction(trigger)) {
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
const attributes = {
|
|
1306
|
+
evicted_count: result.evictedCount,
|
|
1307
|
+
evicted_bytes: result.evictedBytes,
|
|
1308
|
+
resident_count: this.messages.length,
|
|
1309
|
+
resident_bytes: this.transcriptBudget.residentBytes,
|
|
1310
|
+
trigger,
|
|
1311
|
+
used_heap_bytes: result.usedHeapBytes,
|
|
1312
|
+
heap_limit_bytes: result.heapLimitBytes,
|
|
1313
|
+
};
|
|
1314
|
+
if (this.logger.warnStructured) {
|
|
1315
|
+
this.logger.warnStructured("[ai-service] chatTranscriptEvicted", attributes);
|
|
1316
|
+
}
|
|
1317
|
+
else {
|
|
1318
|
+
this.logger.warn(`[ai-service] chatTranscriptEvicted trigger=${attributes.trigger} evicted_count=${attributes.evicted_count} evicted_bytes=${attributes.evicted_bytes} resident_count=${attributes.resident_count} resident_bytes=${attributes.resident_bytes}`);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Throttle the eviction warn to one line per trigger per window. A trigger
|
|
1323
|
+
* change always warns (meaningful transition); repeated same-trigger
|
|
1324
|
+
* evictions on every append collapse to one line per EVICTION_WARN_THROTTLE_MS.
|
|
1325
|
+
*/
|
|
1326
|
+
shouldWarnForEviction(trigger) {
|
|
1327
|
+
const now = Date.now();
|
|
1328
|
+
if (trigger === this.lastEvictionWarnTrigger &&
|
|
1329
|
+
now - this.lastEvictionWarnAt < EVICTION_WARN_THROTTLE_MS) {
|
|
1330
|
+
return false;
|
|
1331
|
+
}
|
|
1332
|
+
this.lastEvictionWarnAt = now;
|
|
1333
|
+
this.lastEvictionWarnTrigger = trigger;
|
|
1334
|
+
return true;
|
|
1129
1335
|
}
|
|
1130
1336
|
};
|
|
1131
1337
|
})();
|