@bastani/atomic 0.9.4-alpha.8 → 0.9.4
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/CHANGELOG.md +55 -0
- package/dist/builtin/cursor/CHANGELOG.md +6 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +34 -0
- package/dist/builtin/subagents/agents/code-simplifier.md +2 -2
- package/dist/builtin/subagents/agents/codebase-analyzer.md +2 -2
- package/dist/builtin/subagents/agents/codebase-locator.md +2 -2
- package/dist/builtin/subagents/agents/codebase-online-researcher.md +2 -2
- package/dist/builtin/subagents/agents/codebase-pattern-finder.md +2 -2
- package/dist/builtin/subagents/agents/codebase-research-analyzer.md +2 -2
- package/dist/builtin/subagents/agents/codebase-research-locator.md +2 -2
- package/dist/builtin/subagents/agents/debugger.md +1 -1
- package/dist/builtin/subagents/agents/worker.md +2 -2
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/slash/slash-commands.ts +2 -2
- package/dist/builtin/subagents/src/tui/render-result-compact.ts +3 -2
- package/dist/builtin/subagents/src/tui/render-result.ts +3 -3
- package/dist/builtin/subagents/src/tui/render-widget-graph.ts +4 -3
- package/dist/builtin/subagents/src/tui/render-widget.ts +10 -4
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +30 -0
- package/dist/builtin/workflows/builtin/deep-research-codebase-utils.ts +24 -29
- package/dist/builtin/workflows/builtin/goal-runner.ts +13 -37
- package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +6 -9
- package/dist/builtin/workflows/builtin/ralph-models.ts +58 -115
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +4 -0
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +4 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-input.ts +12 -1
- package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +2 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +6 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +4 -0
- package/dist/builtin/workflows/src/tui/workflow-attach-pane-types.ts +4 -0
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +8 -9
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +3 -2
- package/dist/bun/cli.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +15 -5
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +10 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -1
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-events.d.ts.map +1 -1
- package/dist/core/agent-session-events.js +16 -8
- package/dist/core/agent-session-events.js.map +1 -1
- package/dist/core/agent-session-export.d.ts.map +1 -1
- package/dist/core/agent-session-export.js +4 -2
- package/dist/core/agent-session-export.js.map +1 -1
- package/dist/core/agent-session-extension-bindings.d.ts +3 -1
- package/dist/core/agent-session-extension-bindings.d.ts.map +1 -1
- package/dist/core/agent-session-extension-bindings.js +7 -4
- package/dist/core/agent-session-extension-bindings.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +8 -2
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-retry.d.ts +2 -0
- package/dist/core/agent-session-retry.d.ts.map +1 -1
- package/dist/core/agent-session-retry.js +66 -7
- package/dist/core/agent-session-retry.js.map +1 -1
- package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
- package/dist/core/agent-session-tool-registry.js +4 -0
- package/dist/core/agent-session-tool-registry.js.map +1 -1
- package/dist/core/agent-session.d.ts +4 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +4 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/async/format.d.ts +3 -0
- package/dist/core/async/format.d.ts.map +1 -0
- package/dist/core/async/format.js +50 -0
- package/dist/core/async/format.js.map +1 -0
- package/dist/core/async/job-manager.d.ts +39 -0
- package/dist/core/async/job-manager.d.ts.map +1 -0
- package/dist/core/async/job-manager.js +254 -0
- package/dist/core/async/job-manager.js.map +1 -0
- package/dist/core/async/session-manager.d.ts +18 -0
- package/dist/core/async/session-manager.d.ts.map +1 -0
- package/dist/core/async/session-manager.js +61 -0
- package/dist/core/async/session-manager.js.map +1 -0
- package/dist/core/async/types.d.ts +23 -0
- package/dist/core/async/types.d.ts.map +1 -0
- package/dist/core/async/types.js +2 -0
- package/dist/core/async/types.js.map +1 -0
- package/dist/core/copilot-model-static-fallbacks.d.ts +43 -0
- package/dist/core/copilot-model-static-fallbacks.d.ts.map +1 -0
- package/dist/core/copilot-model-static-fallbacks.js +50 -0
- package/dist/core/copilot-model-static-fallbacks.js.map +1 -0
- package/dist/core/extensions/loader-virtual-modules.d.ts +11 -0
- package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.js +79 -11
- package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
- package/dist/core/model-registry-builtins.d.ts.map +1 -1
- package/dist/core/model-registry-builtins.js +9 -1
- package/dist/core/model-registry-builtins.js.map +1 -1
- package/dist/core/resource-loader-reload.d.ts.map +1 -1
- package/dist/core/resource-loader-reload.js +4 -2
- package/dist/core/resource-loader-reload.js.map +1 -1
- package/dist/core/resource-loader-types.d.ts +6 -0
- package/dist/core/resource-loader-types.d.ts.map +1 -1
- package/dist/core/resource-loader-types.js.map +1 -1
- package/dist/core/tools/bash-async-execution.d.ts +25 -0
- package/dist/core/tools/bash-async-execution.d.ts.map +1 -0
- package/dist/core/tools/bash-async-execution.js +71 -0
- package/dist/core/tools/bash-async-execution.js.map +1 -0
- package/dist/core/tools/bash-async-jobs.d.ts +10 -0
- package/dist/core/tools/bash-async-jobs.d.ts.map +1 -1
- package/dist/core/tools/bash-async-jobs.js +18 -2
- package/dist/core/tools/bash-async-jobs.js.map +1 -1
- package/dist/core/tools/bash-async-output.d.ts +3 -1
- package/dist/core/tools/bash-async-output.d.ts.map +1 -1
- package/dist/core/tools/bash-async-output.js +4 -1
- package/dist/core/tools/bash-async-output.js.map +1 -1
- package/dist/core/tools/bash-pty-native.d.ts.map +1 -1
- package/dist/core/tools/bash-pty-native.js +2 -2
- package/dist/core/tools/bash-pty-native.js.map +1 -1
- package/dist/core/tools/bash.d.ts +10 -4
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +20 -38
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/search-native.d.ts.map +1 -1
- package/dist/core/tools/search-native.js +2 -2
- package/dist/core/tools/search-native.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +56 -32
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-deferred-startup.d.ts +2 -0
- package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-deferred-startup.js +59 -0
- package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -0
- package/dist/modes/interactive/interactive-mode-base.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.js +4 -0
- package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-types.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode-types.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-types.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/interactive-session-runtime.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-session-runtime.js +4 -2
- package/dist/modes/interactive/interactive-session-runtime.js.map +1 -1
- package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +11 -1
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/utils/module-require.d.ts +11 -0
- package/dist/utils/module-require.d.ts.map +1 -0
- package/dist/utils/module-require.js +16 -0
- package/dist/utils/module-require.js.map +1 -0
- package/docs/tools.md +4 -2
- package/npm-shrinkwrap.json +23 -23
- package/package.json +5 -3
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { COMPLETED_JOB_TTL_MS, MAX_MANAGED_BASH_JOBS } from "../tools/bash-async-jobs.js";
|
|
2
|
+
import { formatAsyncResultForFollowUp } from "./format.js";
|
|
3
|
+
const DEFAULT_MAX_RUNNING_JOBS = 15;
|
|
4
|
+
const DELIVERY_RETRY_BASE_MS = 500;
|
|
5
|
+
const DELIVERY_RETRY_MAX_MS = 30_000;
|
|
6
|
+
const DELIVERY_RETRY_JITTER_MS = 200;
|
|
7
|
+
export class AsyncJobManager {
|
|
8
|
+
static #instance;
|
|
9
|
+
static instance() {
|
|
10
|
+
return AsyncJobManager.#instance;
|
|
11
|
+
}
|
|
12
|
+
static setInstance(value) {
|
|
13
|
+
AsyncJobManager.#instance = value;
|
|
14
|
+
}
|
|
15
|
+
static resetForTests() {
|
|
16
|
+
AsyncJobManager.#instance = undefined;
|
|
17
|
+
}
|
|
18
|
+
#jobs = new Map();
|
|
19
|
+
#deliveries = [];
|
|
20
|
+
#suppressedDeliveries = new Set();
|
|
21
|
+
#inFlightDeliveries = new Map();
|
|
22
|
+
#deliveryHandlers = new Map();
|
|
23
|
+
#jobSessions = new Map();
|
|
24
|
+
#sessions = new Map();
|
|
25
|
+
#onJobComplete;
|
|
26
|
+
#maxRunningJobs;
|
|
27
|
+
#maxRetainedJobs;
|
|
28
|
+
#completedJobTtlMs;
|
|
29
|
+
#timer;
|
|
30
|
+
#disposed = false;
|
|
31
|
+
#runningDeliveryLoop = false;
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.#onJobComplete = options.onJobComplete;
|
|
34
|
+
this.#maxRunningJobs = Math.max(1, Math.floor(options.maxRunningJobs ?? DEFAULT_MAX_RUNNING_JOBS));
|
|
35
|
+
this.#maxRetainedJobs = Math.max(1, Math.floor(options.maxRetainedJobs ?? MAX_MANAGED_BASH_JOBS));
|
|
36
|
+
this.#completedJobTtlMs = Math.max(0, Math.floor(options.completedJobTtlMs ?? COMPLETED_JOB_TTL_MS));
|
|
37
|
+
}
|
|
38
|
+
get disposed() {
|
|
39
|
+
return this.#disposed;
|
|
40
|
+
}
|
|
41
|
+
get atCapacity() {
|
|
42
|
+
let running = 0;
|
|
43
|
+
for (const job of this.#jobs.values())
|
|
44
|
+
if (job.status === "running")
|
|
45
|
+
running += 1;
|
|
46
|
+
return running >= this.#maxRunningJobs;
|
|
47
|
+
}
|
|
48
|
+
registerSession() {
|
|
49
|
+
if (this.#disposed)
|
|
50
|
+
throw new Error("Async job manager is disposed");
|
|
51
|
+
const sessionId = Symbol("async-job-session");
|
|
52
|
+
this.#sessions.set(sessionId, { disposed: false, activeJobIds: new Set() });
|
|
53
|
+
return sessionId;
|
|
54
|
+
}
|
|
55
|
+
releaseSession(sessionId) {
|
|
56
|
+
const session = this.#sessions.get(sessionId);
|
|
57
|
+
if (!session)
|
|
58
|
+
return;
|
|
59
|
+
session.disposed = true;
|
|
60
|
+
this.acknowledgeDeliveries([...session.activeJobIds]);
|
|
61
|
+
this.#sessions.delete(sessionId);
|
|
62
|
+
if (this.#sessions.size === 0)
|
|
63
|
+
this.dispose();
|
|
64
|
+
}
|
|
65
|
+
isSessionDisposed(sessionId) {
|
|
66
|
+
return this.#disposed || this.#sessions.get(sessionId)?.disposed !== false;
|
|
67
|
+
}
|
|
68
|
+
registerBashJob(job, onComplete, sessionId) {
|
|
69
|
+
if (this.#disposed)
|
|
70
|
+
throw new Error("Async job manager is disposed");
|
|
71
|
+
this.#pruneRetention();
|
|
72
|
+
if (this.atCapacity)
|
|
73
|
+
throw new Error(`Background job limit reached (${this.#maxRunningJobs}). Wait for running jobs to finish or cancel one.`);
|
|
74
|
+
if (sessionId !== undefined && this.isSessionDisposed(sessionId))
|
|
75
|
+
throw new Error("Async job session is disposed");
|
|
76
|
+
this.#suppressedDeliveries.delete(job.jobId);
|
|
77
|
+
if (onComplete)
|
|
78
|
+
this.#deliveryHandlers.set(job.jobId, onComplete);
|
|
79
|
+
else
|
|
80
|
+
this.#deliveryHandlers.delete(job.jobId);
|
|
81
|
+
if (sessionId !== undefined) {
|
|
82
|
+
this.#jobSessions.set(job.jobId, sessionId);
|
|
83
|
+
this.#sessions.get(sessionId)?.activeJobIds.add(job.jobId);
|
|
84
|
+
}
|
|
85
|
+
else
|
|
86
|
+
this.#jobSessions.delete(job.jobId);
|
|
87
|
+
this.#jobs.set(job.jobId, job);
|
|
88
|
+
this.#pruneRetention();
|
|
89
|
+
}
|
|
90
|
+
completeBashJob(job) {
|
|
91
|
+
if (this.#disposed || this.#suppressedDeliveries.has(job.jobId))
|
|
92
|
+
return;
|
|
93
|
+
if (job.status === "running")
|
|
94
|
+
return;
|
|
95
|
+
this.#jobs.set(job.jobId, job);
|
|
96
|
+
this.#pruneRetention();
|
|
97
|
+
if (this.#jobs.has(job.jobId))
|
|
98
|
+
this.#enqueueDelivery(job);
|
|
99
|
+
}
|
|
100
|
+
acknowledgeDeliveries(jobIds) {
|
|
101
|
+
for (const jobId of jobIds) {
|
|
102
|
+
this.#suppressedDeliveries.add(jobId);
|
|
103
|
+
for (let index = this.#deliveries.length - 1; index >= 0; index -= 1) {
|
|
104
|
+
if (this.#deliveries[index]?.jobId === jobId)
|
|
105
|
+
this.#deliveries.splice(index, 1);
|
|
106
|
+
}
|
|
107
|
+
this.#deliveryHandlers.delete(jobId);
|
|
108
|
+
this.#completeTrackedJob(jobId);
|
|
109
|
+
}
|
|
110
|
+
this.#pruneRetention();
|
|
111
|
+
}
|
|
112
|
+
isDeliverySuppressed(jobId) {
|
|
113
|
+
return this.#suppressedDeliveries.has(jobId);
|
|
114
|
+
}
|
|
115
|
+
deliveryState() {
|
|
116
|
+
this.#pruneRetention();
|
|
117
|
+
return {
|
|
118
|
+
queued: this.#deliveries.length,
|
|
119
|
+
delivering: this.#runningDeliveryLoop || this.#inFlightDeliveries.size > 0,
|
|
120
|
+
pendingJobIds: [...this.#deliveries.map((delivery) => delivery.jobId), ...this.#inFlightDeliveries.keys()],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
retentionState() {
|
|
124
|
+
this.#pruneRetention();
|
|
125
|
+
return { jobs: this.#jobs.size, suppressions: this.#suppressedDeliveries.size, handlers: this.#deliveryHandlers.size, queued: this.#deliveries.length + this.#inFlightDeliveries.size, sessions: this.#sessions.size };
|
|
126
|
+
}
|
|
127
|
+
dispose() {
|
|
128
|
+
this.#disposed = true;
|
|
129
|
+
if (this.#timer)
|
|
130
|
+
clearTimeout(this.#timer);
|
|
131
|
+
this.#timer = undefined;
|
|
132
|
+
this.#deliveries.length = 0;
|
|
133
|
+
this.#inFlightDeliveries.clear();
|
|
134
|
+
this.#deliveryHandlers.clear();
|
|
135
|
+
this.#jobSessions.clear();
|
|
136
|
+
this.#sessions.clear();
|
|
137
|
+
this.#jobs.clear();
|
|
138
|
+
this.#suppressedDeliveries.clear();
|
|
139
|
+
}
|
|
140
|
+
#enqueueDelivery(job) {
|
|
141
|
+
if (this.#inFlightDeliveries.has(job.jobId))
|
|
142
|
+
return;
|
|
143
|
+
for (let index = this.#deliveries.length - 1; index >= 0; index -= 1)
|
|
144
|
+
if (this.#deliveries[index]?.jobId === job.jobId)
|
|
145
|
+
this.#deliveries.splice(index, 1);
|
|
146
|
+
this.#deliveries.push({ jobId: job.jobId, message: formatAsyncResultForFollowUp(job), attempt: 0, nextAttemptAt: Date.now() });
|
|
147
|
+
this.#pruneRetention();
|
|
148
|
+
this.#scheduleDeliveryLoop(25);
|
|
149
|
+
}
|
|
150
|
+
#completeTrackedJob(jobId) {
|
|
151
|
+
const sessionId = this.#jobSessions.get(jobId);
|
|
152
|
+
if (sessionId !== undefined)
|
|
153
|
+
this.#sessions.get(sessionId)?.activeJobIds.delete(jobId);
|
|
154
|
+
this.#jobSessions.delete(jobId);
|
|
155
|
+
}
|
|
156
|
+
#pruneRetention(now = Date.now()) {
|
|
157
|
+
for (const [jobId, job] of this.#jobs) {
|
|
158
|
+
if (job.status !== "running" && job.endedAt !== undefined && now - job.endedAt > this.#completedJobTtlMs) {
|
|
159
|
+
this.#jobs.delete(jobId);
|
|
160
|
+
this.#completeTrackedJob(jobId);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const jobId of this.#suppressedDeliveries) {
|
|
164
|
+
if (!this.#jobs.has(jobId))
|
|
165
|
+
this.#suppressedDeliveries.delete(jobId);
|
|
166
|
+
}
|
|
167
|
+
const oldestTerminalJobs = [...this.#jobs.values()]
|
|
168
|
+
.filter((job) => job.status !== "running")
|
|
169
|
+
.sort((a, b) => (a.endedAt ?? a.startedAt) - (b.endedAt ?? b.startedAt));
|
|
170
|
+
while (this.#jobs.size > this.#maxRetainedJobs && oldestTerminalJobs.length > 0) {
|
|
171
|
+
const job = oldestTerminalJobs.shift();
|
|
172
|
+
if (job) {
|
|
173
|
+
this.#jobs.delete(job.jobId);
|
|
174
|
+
this.#completeTrackedJob(job.jobId);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (let index = this.#deliveries.length - 1; index >= 0; index -= 1) {
|
|
178
|
+
const delivery = this.#deliveries[index];
|
|
179
|
+
if (!delivery || !this.#jobs.has(delivery.jobId) || this.#suppressedDeliveries.has(delivery.jobId))
|
|
180
|
+
this.#deliveries.splice(index, 1);
|
|
181
|
+
}
|
|
182
|
+
while (this.#deliveries.length + this.#inFlightDeliveries.size > this.#maxRetainedJobs && this.#deliveries.length > 0)
|
|
183
|
+
this.#deliveries.shift();
|
|
184
|
+
for (const jobId of this.#deliveryHandlers.keys())
|
|
185
|
+
if (!this.#jobs.has(jobId) || this.#suppressedDeliveries.has(jobId))
|
|
186
|
+
this.#deliveryHandlers.delete(jobId);
|
|
187
|
+
for (const session of this.#sessions.values())
|
|
188
|
+
for (const jobId of session.activeJobIds)
|
|
189
|
+
if (!this.#jobs.has(jobId))
|
|
190
|
+
session.activeJobIds.delete(jobId);
|
|
191
|
+
}
|
|
192
|
+
#scheduleDeliveryLoop(delayMs) {
|
|
193
|
+
if (this.#disposed)
|
|
194
|
+
return;
|
|
195
|
+
if (this.#timer)
|
|
196
|
+
clearTimeout(this.#timer);
|
|
197
|
+
this.#timer = setTimeout(() => {
|
|
198
|
+
this.#timer = undefined;
|
|
199
|
+
void this.#runDeliveryLoop();
|
|
200
|
+
}, delayMs);
|
|
201
|
+
this.#timer.unref?.();
|
|
202
|
+
}
|
|
203
|
+
async #runDeliveryLoop() {
|
|
204
|
+
if (this.#runningDeliveryLoop || this.#disposed)
|
|
205
|
+
return;
|
|
206
|
+
this.#runningDeliveryLoop = true;
|
|
207
|
+
try {
|
|
208
|
+
while (!this.#disposed) {
|
|
209
|
+
const now = Date.now();
|
|
210
|
+
const delivery = this.#deliveries.find((candidate) => candidate.nextAttemptAt <= now);
|
|
211
|
+
if (!delivery)
|
|
212
|
+
break;
|
|
213
|
+
this.#deliveries.splice(this.#deliveries.indexOf(delivery), 1);
|
|
214
|
+
this.#startDelivery(delivery);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
this.#runningDeliveryLoop = false;
|
|
219
|
+
}
|
|
220
|
+
this.#scheduleNextDelivery();
|
|
221
|
+
}
|
|
222
|
+
#startDelivery(delivery) {
|
|
223
|
+
if (this.#suppressedDeliveries.has(delivery.jobId) || !this.#jobs.has(delivery.jobId) || this.#inFlightDeliveries.has(delivery.jobId))
|
|
224
|
+
return;
|
|
225
|
+
const handler = this.#deliveryHandlers.get(delivery.jobId) ?? this.#onJobComplete;
|
|
226
|
+
this.#inFlightDeliveries.set(delivery.jobId, delivery);
|
|
227
|
+
delivery.promise = (async () => handler(delivery.message))()
|
|
228
|
+
.then(() => {
|
|
229
|
+
if (!this.#suppressedDeliveries.has(delivery.jobId) && this.#jobs.has(delivery.jobId)) {
|
|
230
|
+
this.#deliveryHandlers.delete(delivery.jobId);
|
|
231
|
+
this.#completeTrackedJob(delivery.jobId);
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
.catch(() => {
|
|
235
|
+
if (this.#disposed || this.#suppressedDeliveries.has(delivery.jobId) || !this.#jobs.has(delivery.jobId))
|
|
236
|
+
return;
|
|
237
|
+
delivery.attempt += 1;
|
|
238
|
+
const jitter = Math.floor(Math.random() * DELIVERY_RETRY_JITTER_MS);
|
|
239
|
+
delivery.nextAttemptAt = Date.now() + Math.min(DELIVERY_RETRY_MAX_MS, DELIVERY_RETRY_BASE_MS * 2 ** delivery.attempt) + jitter;
|
|
240
|
+
this.#deliveries.push(delivery);
|
|
241
|
+
})
|
|
242
|
+
.finally(() => {
|
|
243
|
+
this.#inFlightDeliveries.delete(delivery.jobId);
|
|
244
|
+
this.#pruneRetention();
|
|
245
|
+
this.#scheduleNextDelivery();
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
#scheduleNextDelivery() {
|
|
249
|
+
const next = this.#deliveries.reduce((soonest, delivery) => soonest === undefined ? delivery.nextAttemptAt : Math.min(soonest, delivery.nextAttemptAt), undefined);
|
|
250
|
+
if (next !== undefined)
|
|
251
|
+
this.#scheduleDeliveryLoop(Math.max(0, next - Date.now()));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=job-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-manager.js","sourceRoot":"","sources":["../../../src/core/async/job-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import { COMPLETED_JOB_TTL_MS, MAX_MANAGED_BASH_JOBS, type ManagedBashJob } from \"../tools/bash-async-jobs.js\";\nimport { formatAsyncResultForFollowUp } from \"./format.js\";\nimport type { AsyncJobDeliveryCallback, AsyncJobDeliveryHandler, AsyncJobDeliveryMessage, ManagedAsyncBashJob } from \"./types.js\";\n\nconst DEFAULT_MAX_RUNNING_JOBS = 15;\nconst DELIVERY_RETRY_BASE_MS = 500;\nconst DELIVERY_RETRY_MAX_MS = 30_000;\nconst DELIVERY_RETRY_JITTER_MS = 200;\n\ninterface AsyncJobManagerOptions {\n\tonJobComplete: AsyncJobDeliveryCallback;\n\tmaxRunningJobs?: number;\n\tmaxRetainedJobs?: number;\n\tcompletedJobTtlMs?: number;\n}\n\ninterface Delivery {\n\tjobId: string;\n\tmessage: AsyncJobDeliveryMessage;\n\tattempt: number;\n\tnextAttemptAt: number;\n\tpromise?: Promise<void>;\n}\n\ninterface RegisteredSession {\n\tdisposed: boolean;\n\tactiveJobIds: Set<string>;\n}\n\nexport class AsyncJobManager {\n\tstatic #instance: AsyncJobManager | undefined;\n\n\tstatic instance(): AsyncJobManager | undefined {\n\t\treturn AsyncJobManager.#instance;\n\t}\n\n\tstatic setInstance(value: AsyncJobManager | undefined): void {\n\t\tAsyncJobManager.#instance = value;\n\t}\n\n\tstatic resetForTests(): void {\n\t\tAsyncJobManager.#instance = undefined;\n\t}\n\n\treadonly #jobs = new Map<string, ManagedBashJob>();\n\treadonly #deliveries: Delivery[] = [];\n\treadonly #suppressedDeliveries = new Set<string>();\n\treadonly #inFlightDeliveries = new Map<string, Delivery>();\n\treadonly #deliveryHandlers = new Map<string, AsyncJobDeliveryHandler>();\n\treadonly #jobSessions = new Map<string, symbol>();\n\treadonly #sessions = new Map<symbol, RegisteredSession>();\n\treadonly #onJobComplete: AsyncJobDeliveryCallback;\n\treadonly #maxRunningJobs: number;\n\treadonly #maxRetainedJobs: number;\n\treadonly #completedJobTtlMs: number;\n\t#timer: NodeJS.Timeout | undefined;\n\t#disposed = false;\n\t#runningDeliveryLoop = false;\n\n\tconstructor(options: AsyncJobManagerOptions) {\n\t\tthis.#onJobComplete = options.onJobComplete;\n\t\tthis.#maxRunningJobs = Math.max(1, Math.floor(options.maxRunningJobs ?? DEFAULT_MAX_RUNNING_JOBS));\n\t\tthis.#maxRetainedJobs = Math.max(1, Math.floor(options.maxRetainedJobs ?? MAX_MANAGED_BASH_JOBS));\n\t\tthis.#completedJobTtlMs = Math.max(0, Math.floor(options.completedJobTtlMs ?? COMPLETED_JOB_TTL_MS));\n\t}\n\n\tget disposed(): boolean {\n\t\treturn this.#disposed;\n\t}\n\n\tget atCapacity(): boolean {\n\t\tlet running = 0;\n\t\tfor (const job of this.#jobs.values()) if (job.status === \"running\") running += 1;\n\t\treturn running >= this.#maxRunningJobs;\n\t}\n\n\n\tregisterSession(): symbol {\n\t\tif (this.#disposed) throw new Error(\"Async job manager is disposed\");\n\t\tconst sessionId = Symbol(\"async-job-session\");\n\t\tthis.#sessions.set(sessionId, { disposed: false, activeJobIds: new Set() });\n\t\treturn sessionId;\n\t}\n\n\treleaseSession(sessionId: symbol): void {\n\t\tconst session = this.#sessions.get(sessionId);\n\t\tif (!session) return;\n\t\tsession.disposed = true;\n\t\tthis.acknowledgeDeliveries([...session.activeJobIds]);\n\t\tthis.#sessions.delete(sessionId);\n\t\tif (this.#sessions.size === 0) this.dispose();\n\t}\n\n\tisSessionDisposed(sessionId: symbol): boolean {\n\t\treturn this.#disposed || this.#sessions.get(sessionId)?.disposed !== false;\n\t}\n\tregisterBashJob(job: ManagedBashJob, onComplete?: AsyncJobDeliveryHandler, sessionId?: symbol): void {\n\t\tif (this.#disposed) throw new Error(\"Async job manager is disposed\");\n\t\tthis.#pruneRetention();\n\t\tif (this.atCapacity) throw new Error(`Background job limit reached (${this.#maxRunningJobs}). Wait for running jobs to finish or cancel one.`);\n\t\tif (sessionId !== undefined && this.isSessionDisposed(sessionId)) throw new Error(\"Async job session is disposed\");\n\t\tthis.#suppressedDeliveries.delete(job.jobId);\n\t\tif (onComplete) this.#deliveryHandlers.set(job.jobId, onComplete);\n\t\telse this.#deliveryHandlers.delete(job.jobId);\n\t\tif (sessionId !== undefined) {\n\t\t\tthis.#jobSessions.set(job.jobId, sessionId);\n\t\t\tthis.#sessions.get(sessionId)?.activeJobIds.add(job.jobId);\n\t\t} else this.#jobSessions.delete(job.jobId);\n\t\tthis.#jobs.set(job.jobId, job);\n\t\tthis.#pruneRetention();\n\t}\n\n\tcompleteBashJob(job: ManagedBashJob): void {\n\t\tif (this.#disposed || this.#suppressedDeliveries.has(job.jobId)) return;\n\t\tif (job.status === \"running\") return;\n\t\tthis.#jobs.set(job.jobId, job);\n\t\tthis.#pruneRetention();\n\t\tif (this.#jobs.has(job.jobId)) this.#enqueueDelivery(job as ManagedAsyncBashJob);\n\t}\n\n\tacknowledgeDeliveries(jobIds: readonly string[]): void {\n\t\tfor (const jobId of jobIds) {\n\t\t\tthis.#suppressedDeliveries.add(jobId);\n\t\t\tfor (let index = this.#deliveries.length - 1; index >= 0; index -= 1) {\n\t\t\t\tif (this.#deliveries[index]?.jobId === jobId) this.#deliveries.splice(index, 1);\n\t\t\t}\n\t\t\tthis.#deliveryHandlers.delete(jobId);\n\t\t\tthis.#completeTrackedJob(jobId);\n\t\t}\n\t\tthis.#pruneRetention();\n\t}\n\n\tisDeliverySuppressed(jobId: string): boolean {\n\t\treturn this.#suppressedDeliveries.has(jobId);\n\t}\n\n\tdeliveryState(): { queued: number; delivering: boolean; pendingJobIds: string[] } {\n\t\tthis.#pruneRetention();\n\t\treturn {\n\t\t\tqueued: this.#deliveries.length,\n\t\t\tdelivering: this.#runningDeliveryLoop || this.#inFlightDeliveries.size > 0,\n\t\t\tpendingJobIds: [...this.#deliveries.map((delivery) => delivery.jobId), ...this.#inFlightDeliveries.keys()],\n\t\t};\n\t}\n\n\tretentionState(): { jobs: number; suppressions: number; handlers: number; queued: number; sessions: number } {\n\t\tthis.#pruneRetention();\n\t\treturn { jobs: this.#jobs.size, suppressions: this.#suppressedDeliveries.size, handlers: this.#deliveryHandlers.size, queued: this.#deliveries.length + this.#inFlightDeliveries.size, sessions: this.#sessions.size };\n\t}\n\n\tdispose(): void {\n\t\tthis.#disposed = true;\n\t\tif (this.#timer) clearTimeout(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tthis.#deliveries.length = 0;\n\t\tthis.#inFlightDeliveries.clear();\n\t\tthis.#deliveryHandlers.clear();\n\t\tthis.#jobSessions.clear();\n\t\tthis.#sessions.clear();\n\t\tthis.#jobs.clear();\n\t\tthis.#suppressedDeliveries.clear();\n\t}\n\n\t#enqueueDelivery(job: ManagedAsyncBashJob): void {\n\t\tif (this.#inFlightDeliveries.has(job.jobId)) return;\n\t\tfor (let index = this.#deliveries.length - 1; index >= 0; index -= 1) if (this.#deliveries[index]?.jobId === job.jobId) this.#deliveries.splice(index, 1);\n\t\tthis.#deliveries.push({ jobId: job.jobId, message: formatAsyncResultForFollowUp(job), attempt: 0, nextAttemptAt: Date.now() });\n\t\tthis.#pruneRetention();\n\t\tthis.#scheduleDeliveryLoop(25);\n\t}\n\n\n\t#completeTrackedJob(jobId: string): void {\n\t\tconst sessionId = this.#jobSessions.get(jobId);\n\t\tif (sessionId !== undefined) this.#sessions.get(sessionId)?.activeJobIds.delete(jobId);\n\t\tthis.#jobSessions.delete(jobId);\n\t}\n\t#pruneRetention(now = Date.now()): void {\n\t\tfor (const [jobId, job] of this.#jobs) {\n\t\t\tif (job.status !== \"running\" && job.endedAt !== undefined && now - job.endedAt > this.#completedJobTtlMs) {\n\t\t\t\tthis.#jobs.delete(jobId);\n\t\t\t\tthis.#completeTrackedJob(jobId);\n\t\t\t}\n\t\t}\n\t\tfor (const jobId of this.#suppressedDeliveries) {\n\t\t\tif (!this.#jobs.has(jobId)) this.#suppressedDeliveries.delete(jobId);\n\t\t}\n\t\tconst oldestTerminalJobs = [...this.#jobs.values()]\n\t\t\t.filter((job) => job.status !== \"running\")\n\t\t\t.sort((a, b) => (a.endedAt ?? a.startedAt) - (b.endedAt ?? b.startedAt));\n\t\twhile (this.#jobs.size > this.#maxRetainedJobs && oldestTerminalJobs.length > 0) {\n\t\t\tconst job = oldestTerminalJobs.shift();\n\t\t\tif (job) {\n\t\t\t\tthis.#jobs.delete(job.jobId);\n\t\t\t\tthis.#completeTrackedJob(job.jobId);\n\t\t\t}\n\t\t}\n\t\tfor (let index = this.#deliveries.length - 1; index >= 0; index -= 1) {\n\t\t\tconst delivery = this.#deliveries[index];\n\t\t\tif (!delivery || !this.#jobs.has(delivery.jobId) || this.#suppressedDeliveries.has(delivery.jobId)) this.#deliveries.splice(index, 1);\n\t\t}\n\t\twhile (this.#deliveries.length + this.#inFlightDeliveries.size > this.#maxRetainedJobs && this.#deliveries.length > 0) this.#deliveries.shift();\n\t\tfor (const jobId of this.#deliveryHandlers.keys()) if (!this.#jobs.has(jobId) || this.#suppressedDeliveries.has(jobId)) this.#deliveryHandlers.delete(jobId);\n\t\tfor (const session of this.#sessions.values()) for (const jobId of session.activeJobIds) if (!this.#jobs.has(jobId)) session.activeJobIds.delete(jobId);\n\t}\n\n\t#scheduleDeliveryLoop(delayMs: number): void {\n\t\tif (this.#disposed) return;\n\t\tif (this.#timer) clearTimeout(this.#timer);\n\t\tthis.#timer = setTimeout(() => {\n\t\t\tthis.#timer = undefined;\n\t\t\tvoid this.#runDeliveryLoop();\n\t\t}, delayMs);\n\t\tthis.#timer.unref?.();\n\t}\n\n\tasync #runDeliveryLoop(): Promise<void> {\n\t\tif (this.#runningDeliveryLoop || this.#disposed) return;\n\t\tthis.#runningDeliveryLoop = true;\n\t\ttry {\n\t\t\twhile (!this.#disposed) {\n\t\t\t\tconst now = Date.now();\n\t\t\t\tconst delivery = this.#deliveries.find((candidate) => candidate.nextAttemptAt <= now);\n\t\t\t\tif (!delivery) break;\n\t\t\t\tthis.#deliveries.splice(this.#deliveries.indexOf(delivery), 1);\n\t\t\t\tthis.#startDelivery(delivery);\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.#runningDeliveryLoop = false;\n\t\t}\n\t\tthis.#scheduleNextDelivery();\n\t}\n\n\t#startDelivery(delivery: Delivery): void {\n\t\tif (this.#suppressedDeliveries.has(delivery.jobId) || !this.#jobs.has(delivery.jobId) || this.#inFlightDeliveries.has(delivery.jobId)) return;\n\t\tconst handler = this.#deliveryHandlers.get(delivery.jobId) ?? this.#onJobComplete;\n\t\tthis.#inFlightDeliveries.set(delivery.jobId, delivery);\n\t\tdelivery.promise = (async () => handler(delivery.message))()\n\t\t\t.then(() => {\n\t\t\t\tif (!this.#suppressedDeliveries.has(delivery.jobId) && this.#jobs.has(delivery.jobId)) {\n\t\t\t\t\tthis.#deliveryHandlers.delete(delivery.jobId);\n\t\t\t\t\tthis.#completeTrackedJob(delivery.jobId);\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => {\n\t\t\t\tif (this.#disposed || this.#suppressedDeliveries.has(delivery.jobId) || !this.#jobs.has(delivery.jobId)) return;\n\t\t\t\tdelivery.attempt += 1;\n\t\t\t\tconst jitter = Math.floor(Math.random() * DELIVERY_RETRY_JITTER_MS);\n\t\t\t\tdelivery.nextAttemptAt = Date.now() + Math.min(DELIVERY_RETRY_MAX_MS, DELIVERY_RETRY_BASE_MS * 2 ** delivery.attempt) + jitter;\n\t\t\t\tthis.#deliveries.push(delivery);\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tthis.#inFlightDeliveries.delete(delivery.jobId);\n\t\t\t\tthis.#pruneRetention();\n\t\t\t\tthis.#scheduleNextDelivery();\n\t\t\t});\n\t}\n\n\t#scheduleNextDelivery(): void {\n\t\tconst next = this.#deliveries.reduce<number | undefined>((soonest, delivery) => soonest === undefined ? delivery.nextAttemptAt : Math.min(soonest, delivery.nextAttemptAt), undefined);\n\t\tif (next !== undefined) this.#scheduleDeliveryLoop(Math.max(0, next - Date.now()));\n\t}\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CustomMessage } from "../messages.js";
|
|
2
|
+
import type { SendMessageOptions } from "../extensions/index.js";
|
|
3
|
+
import { AsyncJobManager } from "./job-manager.js";
|
|
4
|
+
import type { AsyncJobDeliveryHandler } from "./types.js";
|
|
5
|
+
export interface SessionAsyncJobManagerHandle {
|
|
6
|
+
manager: AsyncJobManager;
|
|
7
|
+
owns: boolean;
|
|
8
|
+
sessionId: symbol;
|
|
9
|
+
}
|
|
10
|
+
interface AsyncDeliverySession {
|
|
11
|
+
readonly isStreaming?: boolean;
|
|
12
|
+
sendCustomMessage<T>(message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">, options?: SendMessageOptions): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export declare function createSessionAsyncDeliveryHandler(session: AsyncDeliverySession, manager?: AsyncJobManager, sessionId?: symbol): AsyncJobDeliveryHandler;
|
|
15
|
+
export declare function createSessionAsyncJobManager(session: AsyncDeliverySession): SessionAsyncJobManagerHandle;
|
|
16
|
+
export declare function disposeSessionAsyncJobManager(manager: AsyncJobManager | undefined, sessionId: symbol | undefined): void;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=session-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../../src/core/async/session-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,uBAAuB,EAA2B,MAAM,YAAY,CAAC;AAEnF,MAAM,WAAW,4BAA4B;IAC5C,OAAO,EAAE,eAAe,CAAC;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CAClB;AACD,UAAU,oBAAoB;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,CAAC,EAClB,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,EACjF,OAAO,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAAC;CACjB;AAiCD,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,oBAAoB,EAAE,OAAO,CAAC,EAAE,eAAe,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,uBAAuB,CAUvJ;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,oBAAoB,GAAG,4BAA4B,CASxG;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAIvH","sourcesContent":["import type { CustomMessage } from \"../messages.js\";\nimport type { SendMessageOptions } from \"../extensions/index.js\";\nimport { AsyncJobManager } from \"./job-manager.js\";\nimport type { AsyncJobDeliveryHandler, AsyncJobDeliveryMessage } from \"./types.js\";\n\nexport interface SessionAsyncJobManagerHandle {\n\tmanager: AsyncJobManager;\n\towns: boolean;\n\tsessionId: symbol;\n}\ninterface AsyncDeliverySession {\n\treadonly isStreaming?: boolean;\n\tsendCustomMessage<T>(\n\t\tmessage: Pick<CustomMessage<T>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\toptions?: SendMessageOptions,\n\t): Promise<void>;\n}\n\nconst STREAMING_DELIVERY_POLL_MS = 10;\n\nfunction scheduleBoundaryCheck(callback: () => void): NodeJS.Timeout {\n\tconst timer = setTimeout(callback, STREAMING_DELIVERY_POLL_MS);\n\ttimer.unref?.();\n\treturn timer;\n}\n\nfunction waitForStreamingBoundary(session: AsyncDeliverySession, isStale: () => boolean): Promise<\"ready\" | \"stale\"> {\n\treturn new Promise((resolve) => {\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tconst settle = (value: \"ready\" | \"stale\") => {\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tresolve(value);\n\t\t};\n\t\tconst check = () => {\n\t\t\ttimer = undefined;\n\t\t\tif (isStale()) {\n\t\t\t\tsettle(\"stale\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (session.isStreaming !== true) {\n\t\t\t\tsettle(\"ready\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttimer = scheduleBoundaryCheck(check);\n\t\t};\n\t\tcheck();\n\t});\n}\n\nexport function createSessionAsyncDeliveryHandler(session: AsyncDeliverySession, manager?: AsyncJobManager, sessionId?: symbol): AsyncJobDeliveryHandler {\n\treturn async (message: AsyncJobDeliveryMessage) => {\n\t\tconst isStale = () =>\n\t\t\tmanager?.disposed === true ||\n\t\t\tmanager?.isDeliverySuppressed(message.details.jobId) === true ||\n\t\t\t(sessionId !== undefined && manager?.isSessionDisposed(sessionId) === true);\n\t\tif (await waitForStreamingBoundary(session, isStale) === \"stale\") return;\n\t\tif (isStale()) return;\n\t\tawait session.sendCustomMessage(message, { deliverAs: \"followUp\", triggerTurn: true });\n\t};\n}\n\nexport function createSessionAsyncJobManager(session: AsyncDeliverySession): SessionAsyncJobManagerHandle {\n\tconst existing = AsyncJobManager.instance();\n\tif (existing) return { manager: existing, owns: false, sessionId: existing.registerSession() };\n\tlet manager: AsyncJobManager;\n\tmanager = new AsyncJobManager({\n\t\tonJobComplete: (message) => createSessionAsyncDeliveryHandler(session, manager)(message),\n\t});\n\tAsyncJobManager.setInstance(manager);\n\treturn { manager, owns: true, sessionId: manager.registerSession() };\n}\n\nexport function disposeSessionAsyncJobManager(manager: AsyncJobManager | undefined, sessionId: symbol | undefined): void {\n\tif (!manager || !sessionId) return;\n\tmanager.releaseSession(sessionId);\n\tif (manager.disposed && AsyncJobManager.instance() === manager) AsyncJobManager.setInstance(undefined);\n}\n\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AsyncJobManager } from "./job-manager.js";
|
|
2
|
+
const STREAMING_DELIVERY_POLL_MS = 10;
|
|
3
|
+
function scheduleBoundaryCheck(callback) {
|
|
4
|
+
const timer = setTimeout(callback, STREAMING_DELIVERY_POLL_MS);
|
|
5
|
+
timer.unref?.();
|
|
6
|
+
return timer;
|
|
7
|
+
}
|
|
8
|
+
function waitForStreamingBoundary(session, isStale) {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
let timer;
|
|
11
|
+
const settle = (value) => {
|
|
12
|
+
if (timer)
|
|
13
|
+
clearTimeout(timer);
|
|
14
|
+
resolve(value);
|
|
15
|
+
};
|
|
16
|
+
const check = () => {
|
|
17
|
+
timer = undefined;
|
|
18
|
+
if (isStale()) {
|
|
19
|
+
settle("stale");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (session.isStreaming !== true) {
|
|
23
|
+
settle("ready");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
timer = scheduleBoundaryCheck(check);
|
|
27
|
+
};
|
|
28
|
+
check();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export function createSessionAsyncDeliveryHandler(session, manager, sessionId) {
|
|
32
|
+
return async (message) => {
|
|
33
|
+
const isStale = () => manager?.disposed === true ||
|
|
34
|
+
manager?.isDeliverySuppressed(message.details.jobId) === true ||
|
|
35
|
+
(sessionId !== undefined && manager?.isSessionDisposed(sessionId) === true);
|
|
36
|
+
if (await waitForStreamingBoundary(session, isStale) === "stale")
|
|
37
|
+
return;
|
|
38
|
+
if (isStale())
|
|
39
|
+
return;
|
|
40
|
+
await session.sendCustomMessage(message, { deliverAs: "followUp", triggerTurn: true });
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function createSessionAsyncJobManager(session) {
|
|
44
|
+
const existing = AsyncJobManager.instance();
|
|
45
|
+
if (existing)
|
|
46
|
+
return { manager: existing, owns: false, sessionId: existing.registerSession() };
|
|
47
|
+
let manager;
|
|
48
|
+
manager = new AsyncJobManager({
|
|
49
|
+
onJobComplete: (message) => createSessionAsyncDeliveryHandler(session, manager)(message),
|
|
50
|
+
});
|
|
51
|
+
AsyncJobManager.setInstance(manager);
|
|
52
|
+
return { manager, owns: true, sessionId: manager.registerSession() };
|
|
53
|
+
}
|
|
54
|
+
export function disposeSessionAsyncJobManager(manager, sessionId) {
|
|
55
|
+
if (!manager || !sessionId)
|
|
56
|
+
return;
|
|
57
|
+
manager.releaseSession(sessionId);
|
|
58
|
+
if (manager.disposed && AsyncJobManager.instance() === manager)
|
|
59
|
+
AsyncJobManager.setInstance(undefined);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=session-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../../../src/core/async/session-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CustomMessage } from \"../messages.js\";\nimport type { SendMessageOptions } from \"../extensions/index.js\";\nimport { AsyncJobManager } from \"./job-manager.js\";\nimport type { AsyncJobDeliveryHandler, AsyncJobDeliveryMessage } from \"./types.js\";\n\nexport interface SessionAsyncJobManagerHandle {\n\tmanager: AsyncJobManager;\n\towns: boolean;\n\tsessionId: symbol;\n}\ninterface AsyncDeliverySession {\n\treadonly isStreaming?: boolean;\n\tsendCustomMessage<T>(\n\t\tmessage: Pick<CustomMessage<T>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\toptions?: SendMessageOptions,\n\t): Promise<void>;\n}\n\nconst STREAMING_DELIVERY_POLL_MS = 10;\n\nfunction scheduleBoundaryCheck(callback: () => void): NodeJS.Timeout {\n\tconst timer = setTimeout(callback, STREAMING_DELIVERY_POLL_MS);\n\ttimer.unref?.();\n\treturn timer;\n}\n\nfunction waitForStreamingBoundary(session: AsyncDeliverySession, isStale: () => boolean): Promise<\"ready\" | \"stale\"> {\n\treturn new Promise((resolve) => {\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tconst settle = (value: \"ready\" | \"stale\") => {\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tresolve(value);\n\t\t};\n\t\tconst check = () => {\n\t\t\ttimer = undefined;\n\t\t\tif (isStale()) {\n\t\t\t\tsettle(\"stale\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (session.isStreaming !== true) {\n\t\t\t\tsettle(\"ready\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttimer = scheduleBoundaryCheck(check);\n\t\t};\n\t\tcheck();\n\t});\n}\n\nexport function createSessionAsyncDeliveryHandler(session: AsyncDeliverySession, manager?: AsyncJobManager, sessionId?: symbol): AsyncJobDeliveryHandler {\n\treturn async (message: AsyncJobDeliveryMessage) => {\n\t\tconst isStale = () =>\n\t\t\tmanager?.disposed === true ||\n\t\t\tmanager?.isDeliverySuppressed(message.details.jobId) === true ||\n\t\t\t(sessionId !== undefined && manager?.isSessionDisposed(sessionId) === true);\n\t\tif (await waitForStreamingBoundary(session, isStale) === \"stale\") return;\n\t\tif (isStale()) return;\n\t\tawait session.sendCustomMessage(message, { deliverAs: \"followUp\", triggerTurn: true });\n\t};\n}\n\nexport function createSessionAsyncJobManager(session: AsyncDeliverySession): SessionAsyncJobManagerHandle {\n\tconst existing = AsyncJobManager.instance();\n\tif (existing) return { manager: existing, owns: false, sessionId: existing.registerSession() };\n\tlet manager: AsyncJobManager;\n\tmanager = new AsyncJobManager({\n\t\tonJobComplete: (message) => createSessionAsyncDeliveryHandler(session, manager)(message),\n\t});\n\tAsyncJobManager.setInstance(manager);\n\treturn { manager, owns: true, sessionId: manager.registerSession() };\n}\n\nexport function disposeSessionAsyncJobManager(manager: AsyncJobManager | undefined, sessionId: symbol | undefined): void {\n\tif (!manager || !sessionId) return;\n\tmanager.releaseSession(sessionId);\n\tif (manager.disposed && AsyncJobManager.instance() === manager) AsyncJobManager.setInstance(undefined);\n}\n\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ManagedBashJob } from "../tools/bash-async-jobs.js";
|
|
2
|
+
export type AsyncJobStatus = "running" | "completed" | "failed";
|
|
3
|
+
export interface AsyncJobDeliveryDetails {
|
|
4
|
+
jobId: string;
|
|
5
|
+
type: "bash";
|
|
6
|
+
status: AsyncJobStatus;
|
|
7
|
+
command: string;
|
|
8
|
+
exitCode?: number | null;
|
|
9
|
+
fullOutputPath?: string;
|
|
10
|
+
wallTimeMs?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface AsyncJobDeliveryMessage {
|
|
13
|
+
customType: "async-job-result";
|
|
14
|
+
content: string;
|
|
15
|
+
display: true;
|
|
16
|
+
details: AsyncJobDeliveryDetails;
|
|
17
|
+
}
|
|
18
|
+
export type AsyncJobDeliveryCallback = (message: AsyncJobDeliveryMessage) => void | Promise<void>;
|
|
19
|
+
export type AsyncJobDeliveryHandler = (message: AsyncJobDeliveryMessage) => void | Promise<void>;
|
|
20
|
+
export interface ManagedAsyncBashJob extends ManagedBashJob {
|
|
21
|
+
status: AsyncJobStatus;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/async/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;AAEhE,MAAM,WAAW,uBAAuB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACvC,UAAU,EAAE,kBAAkB,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,uBAAuB,CAAC;CACjC;AAED,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAElG,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEjG,MAAM,WAAW,mBAAoB,SAAQ,cAAc;IAC1D,MAAM,EAAE,cAAc,CAAC;CACvB","sourcesContent":["import type { ManagedBashJob } from \"../tools/bash-async-jobs.js\";\n\nexport type AsyncJobStatus = \"running\" | \"completed\" | \"failed\";\n\nexport interface AsyncJobDeliveryDetails {\n\tjobId: string;\n\ttype: \"bash\";\n\tstatus: AsyncJobStatus;\n\tcommand: string;\n\texitCode?: number | null;\n\tfullOutputPath?: string;\n\twallTimeMs?: number;\n}\n\nexport interface AsyncJobDeliveryMessage {\n\tcustomType: \"async-job-result\";\n\tcontent: string;\n\tdisplay: true;\n\tdetails: AsyncJobDeliveryDetails;\n}\n\nexport type AsyncJobDeliveryCallback = (message: AsyncJobDeliveryMessage) => void | Promise<void>;\n\nexport type AsyncJobDeliveryHandler = (message: AsyncJobDeliveryMessage) => void | Promise<void>;\n\nexport interface ManagedAsyncBashJob extends ManagedBashJob {\n\tstatus: AsyncJobStatus;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/async/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ManagedBashJob } from \"../tools/bash-async-jobs.js\";\n\nexport type AsyncJobStatus = \"running\" | \"completed\" | \"failed\";\n\nexport interface AsyncJobDeliveryDetails {\n\tjobId: string;\n\ttype: \"bash\";\n\tstatus: AsyncJobStatus;\n\tcommand: string;\n\texitCode?: number | null;\n\tfullOutputPath?: string;\n\twallTimeMs?: number;\n}\n\nexport interface AsyncJobDeliveryMessage {\n\tcustomType: \"async-job-result\";\n\tcontent: string;\n\tdisplay: true;\n\tdetails: AsyncJobDeliveryDetails;\n}\n\nexport type AsyncJobDeliveryCallback = (message: AsyncJobDeliveryMessage) => void | Promise<void>;\n\nexport type AsyncJobDeliveryHandler = (message: AsyncJobDeliveryMessage) => void | Promise<void>;\n\nexport interface ManagedAsyncBashJob extends ManagedBashJob {\n\tstatus: AsyncJobStatus;\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CopilotModelContext } from "./copilot-model-catalog.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Static CAPI-derived limit snapshot for bundled GitHub Copilot models.
|
|
4
|
+
*
|
|
5
|
+
* CAPI is the source of truth for what GitHub actually enforces, and the live
|
|
6
|
+
* `/models` catalog (or its disk cache) is always authoritative when
|
|
7
|
+
* available — this table is consulted **only when the active catalog has no
|
|
8
|
+
* entry for the model** (cold start without cache, catalog fetch failure,
|
|
9
|
+
* network-restricted environments such as eval containers or CI). Without it,
|
|
10
|
+
* the bundled pi-ai metadata wins, and several bundled entries disagree with
|
|
11
|
+
* CAPI (snapshotted 2026-07-02): the gpt-5.x family claims a 400k window vs
|
|
12
|
+
* CAPI's enforced 272k default tier, claude-opus-4.6/claude-sonnet-4.6 claim
|
|
13
|
+
* the branded 1M window as their base tier vs CAPI's 200k default, and
|
|
14
|
+
* claude-opus-4.6/4.7 ship a 32k output cap vs CAPI's real 64k.
|
|
15
|
+
*
|
|
16
|
+
* Why this matters (issue #1608): auto-compaction thresholds and overflow
|
|
17
|
+
* recovery are driven by `model.contextWindow`, and request output caps by
|
|
18
|
+
* `model.maxTokens`. With an overstated window a session never compacts,
|
|
19
|
+
* sails past the server-side cap, and CAPI starts intercepting requests with
|
|
20
|
+
* canned zero-usage refusals ("I'm sorry, but I cannot assist with that
|
|
21
|
+
* request.") reported as `stopReason: "length"`. Understated output caps
|
|
22
|
+
* instead truncate long responses below the real limit.
|
|
23
|
+
*
|
|
24
|
+
* Entries mirror the shape `resolveCopilotModelContext` derives from CAPI
|
|
25
|
+
* `capabilities.limits` / `billing.token_prices`, including the long-context
|
|
26
|
+
* tier where CAPI advertises one (`contextWindowOptions` with the branded
|
|
27
|
+
* window plus the hard `maxInputTokens` prompt cap, e.g. 922k input + 128k
|
|
28
|
+
* output for gpt-5.5's 1.05M tier). This keeps a returning user's persisted
|
|
29
|
+
* long-context selection valid on a cold start without a catalog; actual long
|
|
30
|
+
* tier use is still gated server-side by entitlement, with the existing
|
|
31
|
+
* friendly-error handling on rejection. The snapshot covers every bundled
|
|
32
|
+
* model present in the CAPI catalog — also the currently-agreeing ones — so
|
|
33
|
+
* upstream metadata drift cannot silently reintroduce the failure. Bundled
|
|
34
|
+
* models absent from the CAPI catalog (retired or not-yet-listed ids) are
|
|
35
|
+
* intentionally omitted — there is no CAPI ground truth to snapshot for them.
|
|
36
|
+
*/
|
|
37
|
+
export type StaticCopilotModelFallback = Pick<CopilotModelContext, "contextWindow" | "contextWindowOptions" | "maxInputTokens" | "maxTokens">;
|
|
38
|
+
/**
|
|
39
|
+
* Look up the static CAPI-derived limits for a bundled Copilot model. Returns
|
|
40
|
+
* undefined for models with no CAPI catalog presence to snapshot.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getStaticCopilotModelFallback(modelId: string): StaticCopilotModelFallback | undefined;
|
|
43
|
+
//# sourceMappingURL=copilot-model-static-fallbacks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copilot-model-static-fallbacks.d.ts","sourceRoot":"","sources":["../../src/core/copilot-model-static-fallbacks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAC5C,mBAAmB,EACnB,eAAe,GAAG,sBAAsB,GAAG,gBAAgB,GAAG,WAAW,CACzE,CAAC;AA+CF;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,0BAA0B,GAAG,SAAS,CAErG","sourcesContent":["import type { CopilotModelContext } from \"./copilot-model-catalog.ts\";\n\n/**\n * Static CAPI-derived limit snapshot for bundled GitHub Copilot models.\n *\n * CAPI is the source of truth for what GitHub actually enforces, and the live\n * `/models` catalog (or its disk cache) is always authoritative when\n * available — this table is consulted **only when the active catalog has no\n * entry for the model** (cold start without cache, catalog fetch failure,\n * network-restricted environments such as eval containers or CI). Without it,\n * the bundled pi-ai metadata wins, and several bundled entries disagree with\n * CAPI (snapshotted 2026-07-02): the gpt-5.x family claims a 400k window vs\n * CAPI's enforced 272k default tier, claude-opus-4.6/claude-sonnet-4.6 claim\n * the branded 1M window as their base tier vs CAPI's 200k default, and\n * claude-opus-4.6/4.7 ship a 32k output cap vs CAPI's real 64k.\n *\n * Why this matters (issue #1608): auto-compaction thresholds and overflow\n * recovery are driven by `model.contextWindow`, and request output caps by\n * `model.maxTokens`. With an overstated window a session never compacts,\n * sails past the server-side cap, and CAPI starts intercepting requests with\n * canned zero-usage refusals (\"I'm sorry, but I cannot assist with that\n * request.\") reported as `stopReason: \"length\"`. Understated output caps\n * instead truncate long responses below the real limit.\n *\n * Entries mirror the shape `resolveCopilotModelContext` derives from CAPI\n * `capabilities.limits` / `billing.token_prices`, including the long-context\n * tier where CAPI advertises one (`contextWindowOptions` with the branded\n * window plus the hard `maxInputTokens` prompt cap, e.g. 922k input + 128k\n * output for gpt-5.5's 1.05M tier). This keeps a returning user's persisted\n * long-context selection valid on a cold start without a catalog; actual long\n * tier use is still gated server-side by entitlement, with the existing\n * friendly-error handling on rejection. The snapshot covers every bundled\n * model present in the CAPI catalog — also the currently-agreeing ones — so\n * upstream metadata drift cannot silently reintroduce the failure. Bundled\n * models absent from the CAPI catalog (retired or not-yet-listed ids) are\n * intentionally omitted — there is no CAPI ground truth to snapshot for them.\n */\n\nexport type StaticCopilotModelFallback = Pick<\n\tCopilotModelContext,\n\t\"contextWindow\" | \"contextWindowOptions\" | \"maxInputTokens\" | \"maxTokens\"\n>;\n\n/** CAPI 1.05M long-context tier shared by the tiered OpenAI gpt-5.x models. */\nconst OPENAI_LONG_TIER: StaticCopilotModelFallback = {\n\tcontextWindow: 272_000,\n\tcontextWindowOptions: [272_000, 1_050_000],\n\tmaxInputTokens: 922_000,\n\tmaxTokens: 128_000,\n};\n\n/** CAPI 1M long-context tier shared by tiered Anthropic/Google models. */\nconst BRANDED_1M_TIER: StaticCopilotModelFallback = {\n\tcontextWindow: 200_000,\n\tcontextWindowOptions: [200_000, 1_000_000],\n\tmaxInputTokens: 936_000,\n\tmaxTokens: 64_000,\n};\n\nconst STATIC_COPILOT_MODEL_FALLBACKS: ReadonlyMap<string, StaticCopilotModelFallback> = new Map<string, StaticCopilotModelFallback>([\n\t// OpenAI: bundled metadata claims a 400k base window; CAPI's default tier is\n\t// 272k, with a 1.05M long tier (922k input cap) on gpt-5.5/gpt-5.4.\n\t[\"gpt-5.5\", OPENAI_LONG_TIER],\n\t[\"gpt-5.4\", OPENAI_LONG_TIER],\n\t[\"gpt-5.4-mini\", { contextWindow: 272_000, maxTokens: 128_000 }],\n\t[\"gpt-5.3-codex\", { contextWindow: 272_000, maxTokens: 128_000 }],\n\t// Bundled metadata claims 264k; CAPI enforces 128k.\n\t[\"gpt-5-mini\", { contextWindow: 128_000, maxTokens: 64_000 }],\n\t[\"gpt-4.1\", { contextWindow: 128_000, maxTokens: 16_384 }],\n\t// Anthropic: CAPI's default tier is 200k with a 1M long tier (936k input\n\t// cap). Bundled claude-opus-4.6/claude-sonnet-4.6 wrongly claim the branded\n\t// 1M window as the base tier, and bundled opus-4.6/4.7 output caps are half\n\t// of CAPI's real 64k.\n\t[\"claude-opus-4.6\", BRANDED_1M_TIER],\n\t[\"claude-opus-4.7\", BRANDED_1M_TIER],\n\t[\"claude-opus-4.8\", BRANDED_1M_TIER],\n\t[\"claude-sonnet-4.6\", BRANDED_1M_TIER],\n\t// Bundled metadata claims 200k; CAPI enforces smaller default windows, with\n\t// no long tier.\n\t[\"claude-haiku-4.5\", { contextWindow: 136_000, maxTokens: 64_000 }],\n\t[\"claude-sonnet-4.5\", { contextWindow: 168_000, maxTokens: 32_000 }],\n\t// Google: the pro/3.5 models share the branded 1M tier structure.\n\t[\"gemini-3.1-pro-preview\", BRANDED_1M_TIER],\n\t[\"gemini-3.5-flash\", BRANDED_1M_TIER],\n\t[\"gemini-2.5-pro\", { contextWindow: 128_000, maxTokens: 64_000 }],\n\t[\"gemini-3-flash-preview\", { contextWindow: 128_000, maxTokens: 64_000 }],\n]);\n\n/**\n * Look up the static CAPI-derived limits for a bundled Copilot model. Returns\n * undefined for models with no CAPI catalog presence to snapshot.\n */\nexport function getStaticCopilotModelFallback(modelId: string): StaticCopilotModelFallback | undefined {\n\treturn STATIC_COPILOT_MODEL_FALLBACKS.get(modelId);\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** CAPI 1.05M long-context tier shared by the tiered OpenAI gpt-5.x models. */
|
|
2
|
+
const OPENAI_LONG_TIER = {
|
|
3
|
+
contextWindow: 272_000,
|
|
4
|
+
contextWindowOptions: [272_000, 1_050_000],
|
|
5
|
+
maxInputTokens: 922_000,
|
|
6
|
+
maxTokens: 128_000,
|
|
7
|
+
};
|
|
8
|
+
/** CAPI 1M long-context tier shared by tiered Anthropic/Google models. */
|
|
9
|
+
const BRANDED_1M_TIER = {
|
|
10
|
+
contextWindow: 200_000,
|
|
11
|
+
contextWindowOptions: [200_000, 1_000_000],
|
|
12
|
+
maxInputTokens: 936_000,
|
|
13
|
+
maxTokens: 64_000,
|
|
14
|
+
};
|
|
15
|
+
const STATIC_COPILOT_MODEL_FALLBACKS = new Map([
|
|
16
|
+
// OpenAI: bundled metadata claims a 400k base window; CAPI's default tier is
|
|
17
|
+
// 272k, with a 1.05M long tier (922k input cap) on gpt-5.5/gpt-5.4.
|
|
18
|
+
["gpt-5.5", OPENAI_LONG_TIER],
|
|
19
|
+
["gpt-5.4", OPENAI_LONG_TIER],
|
|
20
|
+
["gpt-5.4-mini", { contextWindow: 272_000, maxTokens: 128_000 }],
|
|
21
|
+
["gpt-5.3-codex", { contextWindow: 272_000, maxTokens: 128_000 }],
|
|
22
|
+
// Bundled metadata claims 264k; CAPI enforces 128k.
|
|
23
|
+
["gpt-5-mini", { contextWindow: 128_000, maxTokens: 64_000 }],
|
|
24
|
+
["gpt-4.1", { contextWindow: 128_000, maxTokens: 16_384 }],
|
|
25
|
+
// Anthropic: CAPI's default tier is 200k with a 1M long tier (936k input
|
|
26
|
+
// cap). Bundled claude-opus-4.6/claude-sonnet-4.6 wrongly claim the branded
|
|
27
|
+
// 1M window as the base tier, and bundled opus-4.6/4.7 output caps are half
|
|
28
|
+
// of CAPI's real 64k.
|
|
29
|
+
["claude-opus-4.6", BRANDED_1M_TIER],
|
|
30
|
+
["claude-opus-4.7", BRANDED_1M_TIER],
|
|
31
|
+
["claude-opus-4.8", BRANDED_1M_TIER],
|
|
32
|
+
["claude-sonnet-4.6", BRANDED_1M_TIER],
|
|
33
|
+
// Bundled metadata claims 200k; CAPI enforces smaller default windows, with
|
|
34
|
+
// no long tier.
|
|
35
|
+
["claude-haiku-4.5", { contextWindow: 136_000, maxTokens: 64_000 }],
|
|
36
|
+
["claude-sonnet-4.5", { contextWindow: 168_000, maxTokens: 32_000 }],
|
|
37
|
+
// Google: the pro/3.5 models share the branded 1M tier structure.
|
|
38
|
+
["gemini-3.1-pro-preview", BRANDED_1M_TIER],
|
|
39
|
+
["gemini-3.5-flash", BRANDED_1M_TIER],
|
|
40
|
+
["gemini-2.5-pro", { contextWindow: 128_000, maxTokens: 64_000 }],
|
|
41
|
+
["gemini-3-flash-preview", { contextWindow: 128_000, maxTokens: 64_000 }],
|
|
42
|
+
]);
|
|
43
|
+
/**
|
|
44
|
+
* Look up the static CAPI-derived limits for a bundled Copilot model. Returns
|
|
45
|
+
* undefined for models with no CAPI catalog presence to snapshot.
|
|
46
|
+
*/
|
|
47
|
+
export function getStaticCopilotModelFallback(modelId) {
|
|
48
|
+
return STATIC_COPILOT_MODEL_FALLBACKS.get(modelId);
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=copilot-model-static-fallbacks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copilot-model-static-fallbacks.js","sourceRoot":"","sources":["../../src/core/copilot-model-static-fallbacks.ts"],"names":[],"mappings":"AA2CA,+EAA+E;AAC/E,MAAM,gBAAgB,GAA+B;IACpD,aAAa,EAAE,OAAO;IACtB,oBAAoB,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;IAC1C,cAAc,EAAE,OAAO;IACvB,SAAS,EAAE,OAAO;CAClB,CAAC;AAEF,0EAA0E;AAC1E,MAAM,eAAe,GAA+B;IACnD,aAAa,EAAE,OAAO;IACtB,oBAAoB,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;IAC1C,cAAc,EAAE,OAAO;IACvB,SAAS,EAAE,MAAM;CACjB,CAAC;AAEF,MAAM,8BAA8B,GAAoD,IAAI,GAAG,CAAqC;IACnI,6EAA6E;IAC7E,oEAAoE;IACpE,CAAC,SAAS,EAAE,gBAAgB,CAAC;IAC7B,CAAC,SAAS,EAAE,gBAAgB,CAAC;IAC7B,CAAC,cAAc,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAChE,CAAC,eAAe,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IACjE,oDAAoD;IACpD,CAAC,YAAY,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAC7D,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAC1D,yEAAyE;IACzE,4EAA4E;IAC5E,4EAA4E;IAC5E,sBAAsB;IACtB,CAAC,iBAAiB,EAAE,eAAe,CAAC;IACpC,CAAC,iBAAiB,EAAE,eAAe,CAAC;IACpC,CAAC,iBAAiB,EAAE,eAAe,CAAC;IACpC,CAAC,mBAAmB,EAAE,eAAe,CAAC;IACtC,4EAA4E;IAC5E,gBAAgB;IAChB,CAAC,kBAAkB,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IACnE,CAAC,mBAAmB,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpE,kEAAkE;IAClE,CAAC,wBAAwB,EAAE,eAAe,CAAC;IAC3C,CAAC,kBAAkB,EAAE,eAAe,CAAC;IACrC,CAAC,gBAAgB,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IACjE,CAAC,wBAAwB,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;CACzE,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,6BAA6B,CAAC,OAAe;IAC5D,OAAO,8BAA8B,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACpD,CAAC","sourcesContent":["import type { CopilotModelContext } from \"./copilot-model-catalog.ts\";\n\n/**\n * Static CAPI-derived limit snapshot for bundled GitHub Copilot models.\n *\n * CAPI is the source of truth for what GitHub actually enforces, and the live\n * `/models` catalog (or its disk cache) is always authoritative when\n * available — this table is consulted **only when the active catalog has no\n * entry for the model** (cold start without cache, catalog fetch failure,\n * network-restricted environments such as eval containers or CI). Without it,\n * the bundled pi-ai metadata wins, and several bundled entries disagree with\n * CAPI (snapshotted 2026-07-02): the gpt-5.x family claims a 400k window vs\n * CAPI's enforced 272k default tier, claude-opus-4.6/claude-sonnet-4.6 claim\n * the branded 1M window as their base tier vs CAPI's 200k default, and\n * claude-opus-4.6/4.7 ship a 32k output cap vs CAPI's real 64k.\n *\n * Why this matters (issue #1608): auto-compaction thresholds and overflow\n * recovery are driven by `model.contextWindow`, and request output caps by\n * `model.maxTokens`. With an overstated window a session never compacts,\n * sails past the server-side cap, and CAPI starts intercepting requests with\n * canned zero-usage refusals (\"I'm sorry, but I cannot assist with that\n * request.\") reported as `stopReason: \"length\"`. Understated output caps\n * instead truncate long responses below the real limit.\n *\n * Entries mirror the shape `resolveCopilotModelContext` derives from CAPI\n * `capabilities.limits` / `billing.token_prices`, including the long-context\n * tier where CAPI advertises one (`contextWindowOptions` with the branded\n * window plus the hard `maxInputTokens` prompt cap, e.g. 922k input + 128k\n * output for gpt-5.5's 1.05M tier). This keeps a returning user's persisted\n * long-context selection valid on a cold start without a catalog; actual long\n * tier use is still gated server-side by entitlement, with the existing\n * friendly-error handling on rejection. The snapshot covers every bundled\n * model present in the CAPI catalog — also the currently-agreeing ones — so\n * upstream metadata drift cannot silently reintroduce the failure. Bundled\n * models absent from the CAPI catalog (retired or not-yet-listed ids) are\n * intentionally omitted — there is no CAPI ground truth to snapshot for them.\n */\n\nexport type StaticCopilotModelFallback = Pick<\n\tCopilotModelContext,\n\t\"contextWindow\" | \"contextWindowOptions\" | \"maxInputTokens\" | \"maxTokens\"\n>;\n\n/** CAPI 1.05M long-context tier shared by the tiered OpenAI gpt-5.x models. */\nconst OPENAI_LONG_TIER: StaticCopilotModelFallback = {\n\tcontextWindow: 272_000,\n\tcontextWindowOptions: [272_000, 1_050_000],\n\tmaxInputTokens: 922_000,\n\tmaxTokens: 128_000,\n};\n\n/** CAPI 1M long-context tier shared by tiered Anthropic/Google models. */\nconst BRANDED_1M_TIER: StaticCopilotModelFallback = {\n\tcontextWindow: 200_000,\n\tcontextWindowOptions: [200_000, 1_000_000],\n\tmaxInputTokens: 936_000,\n\tmaxTokens: 64_000,\n};\n\nconst STATIC_COPILOT_MODEL_FALLBACKS: ReadonlyMap<string, StaticCopilotModelFallback> = new Map<string, StaticCopilotModelFallback>([\n\t// OpenAI: bundled metadata claims a 400k base window; CAPI's default tier is\n\t// 272k, with a 1.05M long tier (922k input cap) on gpt-5.5/gpt-5.4.\n\t[\"gpt-5.5\", OPENAI_LONG_TIER],\n\t[\"gpt-5.4\", OPENAI_LONG_TIER],\n\t[\"gpt-5.4-mini\", { contextWindow: 272_000, maxTokens: 128_000 }],\n\t[\"gpt-5.3-codex\", { contextWindow: 272_000, maxTokens: 128_000 }],\n\t// Bundled metadata claims 264k; CAPI enforces 128k.\n\t[\"gpt-5-mini\", { contextWindow: 128_000, maxTokens: 64_000 }],\n\t[\"gpt-4.1\", { contextWindow: 128_000, maxTokens: 16_384 }],\n\t// Anthropic: CAPI's default tier is 200k with a 1M long tier (936k input\n\t// cap). Bundled claude-opus-4.6/claude-sonnet-4.6 wrongly claim the branded\n\t// 1M window as the base tier, and bundled opus-4.6/4.7 output caps are half\n\t// of CAPI's real 64k.\n\t[\"claude-opus-4.6\", BRANDED_1M_TIER],\n\t[\"claude-opus-4.7\", BRANDED_1M_TIER],\n\t[\"claude-opus-4.8\", BRANDED_1M_TIER],\n\t[\"claude-sonnet-4.6\", BRANDED_1M_TIER],\n\t// Bundled metadata claims 200k; CAPI enforces smaller default windows, with\n\t// no long tier.\n\t[\"claude-haiku-4.5\", { contextWindow: 136_000, maxTokens: 64_000 }],\n\t[\"claude-sonnet-4.5\", { contextWindow: 168_000, maxTokens: 32_000 }],\n\t// Google: the pro/3.5 models share the branded 1M tier structure.\n\t[\"gemini-3.1-pro-preview\", BRANDED_1M_TIER],\n\t[\"gemini-3.5-flash\", BRANDED_1M_TIER],\n\t[\"gemini-2.5-pro\", { contextWindow: 128_000, maxTokens: 64_000 }],\n\t[\"gemini-3-flash-preview\", { contextWindow: 128_000, maxTokens: 64_000 }],\n]);\n\n/**\n * Look up the static CAPI-derived limits for a bundled Copilot model. Returns\n * undefined for models with no CAPI catalog presence to snapshot.\n */\nexport function getStaticCopilotModelFallback(modelId: string): StaticCopilotModelFallback | undefined {\n\treturn STATIC_COPILOT_MODEL_FALLBACKS.get(modelId);\n}\n"]}
|
|
@@ -6,6 +6,16 @@ export interface ExtensionCacheToken {
|
|
|
6
6
|
}
|
|
7
7
|
export declare function clearExtensionCache(): void;
|
|
8
8
|
export declare function useExtensionCacheCwd(cwd: string): ExtensionCacheToken;
|
|
9
|
+
/**
|
|
10
|
+
* Locate an installed package's root directory without consulting its
|
|
11
|
+
* "exports" map: require.resolve("<pkg>/package.json") throws
|
|
12
|
+
* ERR_PACKAGE_PATH_NOT_EXPORTED under Node for packages that do not export
|
|
13
|
+
* "./package.json" (pi-ai does not), and import.meta.resolve() cannot be
|
|
14
|
+
* used because its mere presence silently disables bytecode generation for
|
|
15
|
+
* the compiled binary (CJS bundle). Scanning require.resolve.paths() walks
|
|
16
|
+
* the same node_modules chain Node would, without exports-map encapsulation.
|
|
17
|
+
*/
|
|
18
|
+
declare function findPackageRoot(packageName: string, searchPaths?: string[]): string;
|
|
9
19
|
/**
|
|
10
20
|
* Get aliases for jiti (used in Node.js/development mode).
|
|
11
21
|
* In Bun binary mode, virtualModules is used instead.
|
|
@@ -15,6 +25,7 @@ declare function getAliases(): Record<string, string>;
|
|
|
15
25
|
export declare const extensionLoaderTestHooks: {
|
|
16
26
|
loadVirtualModules: typeof loadVirtualModules;
|
|
17
27
|
getAliases: typeof getAliases;
|
|
28
|
+
findPackageRoot: typeof findPackageRoot;
|
|
18
29
|
};
|
|
19
30
|
export declare function loadExtensionModule(extensionPath: string, cacheToken?: ExtensionCacheToken): Promise<ExtensionFactory | undefined>;
|
|
20
31
|
export {};
|