newmark-agent 0.5.14 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversation-utility-host.bundle.cjs +4565 -2998
- package/dist/conversation-utility-host.js +1 -1
- package/dist/core/agent.d.ts +108 -16
- package/dist/core/agent.js +698 -195
- package/dist/core/agentKernel/agent.d.ts +1 -0
- package/dist/core/agentKernel/agent.js +9 -0
- package/dist/core/agentKernelDiagnostics.d.ts +4 -6
- package/dist/core/agentKernelDiagnostics.js +12 -9
- package/dist/core/agentKernelRunner.d.ts +5 -0
- package/dist/core/agentKernelRunner.js +250 -107
- package/dist/core/autoRouter.js +17 -27
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.js +7 -1
- package/dist/core/conversationCommandState.d.ts +17 -0
- package/dist/core/conversationCommandState.js +140 -0
- package/dist/core/conversationKernel.d.ts +25 -4
- package/dist/core/conversationKernel.js +360 -89
- package/dist/core/conversationListEvent.d.ts +6 -0
- package/dist/core/conversationListEvent.js +14 -0
- package/dist/core/electronUtilityAgentClient.d.ts +4 -1
- package/dist/core/electronUtilityAgentClient.js +4 -4
- package/dist/core/electronUtilityRuntimePool.d.ts +8 -2
- package/dist/core/electronUtilityRuntimePool.js +11 -5
- package/dist/core/installUpdate.js +41 -29
- package/dist/core/modelResponseHealth.d.ts +17 -0
- package/dist/core/modelResponseHealth.js +193 -0
- package/dist/core/providerUsageAccounting.d.ts +47 -0
- package/dist/core/providerUsageAccounting.js +71 -0
- package/dist/core/requestContextEstimate.d.ts +18 -0
- package/dist/core/requestContextEstimate.js +54 -0
- package/dist/core/subagent.d.ts +89 -5
- package/dist/core/subagent.js +264 -41
- package/dist/core/subagentCommunication.d.ts +43 -0
- package/dist/core/subagentCommunication.js +167 -0
- package/dist/core/types.d.ts +34 -1
- package/dist/core/utilityAgentProtocol.d.ts +4 -0
- package/dist/core/workEventCoalescer.js +4 -1
- package/dist/core/wslAgentClient.d.ts +18 -5
- package/dist/core/wslAgentClient.js +79 -27
- package/dist/core/wslAgentProtocol.d.ts +7 -0
- package/dist/core/wslAgentRuntimePool.d.ts +8 -2
- package/dist/core/wslAgentRuntimePool.js +19 -6
- package/dist/core/wslRuntimeProcessTree.d.ts +10 -0
- package/dist/core/wslRuntimeProcessTree.js +104 -0
- package/dist/llm/provider.d.ts +5 -12
- package/dist/llm/provider.js +150 -269
- package/dist/main.js +582 -356
- package/dist/preload.js +3 -3
- package/dist/providers/chat-completions.adapter.d.ts +2 -0
- package/dist/providers/chat-completions.adapter.js +84 -36
- package/dist/providers/provider-adapter.d.ts +2 -0
- package/dist/providers/provider-events.d.ts +23 -14
- package/dist/providers/provider-events.js +148 -43
- package/dist/providers/provider-headers.d.ts +7 -3
- package/dist/providers/provider-headers.js +109 -39
- package/dist/providers/provider-request-compat.d.ts +6 -0
- package/dist/providers/provider-request-compat.js +37 -0
- package/dist/providers/responses.adapter.d.ts +2 -0
- package/dist/providers/responses.adapter.js +192 -126
- package/dist/server.d.ts +9 -2
- package/dist/server.js +100 -24
- package/dist/tools/index.js +17 -1
- package/dist/ui/index.html +2515 -693
- package/dist/ui/lucide-sprite.svg +7 -0
- package/dist/ui/startup.html +6 -6
- package/dist/wsl-agent-host.bundle.cjs +4575 -3006
- package/dist/wsl-agent-host.js +9 -4
- package/package.json +23 -4
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Agent } from './agent';
|
|
2
|
+
import type { AgentWorkEvent } from './types';
|
|
3
|
+
import type { WorkspaceInfo } from './workspace';
|
|
4
|
+
/** Directory changes are workspace metadata, never a turn or selection command. */
|
|
5
|
+
export declare function conversationListEvent(agent: Pick<Agent, 'listWorkspaceConversationStates'>, workspace: WorkspaceInfo): AgentWorkEvent;
|
|
6
|
+
//# sourceMappingURL=conversationListEvent.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.conversationListEvent = conversationListEvent;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
/** Directory changes are workspace metadata, never a turn or selection command. */
|
|
6
|
+
function conversationListEvent(agent, workspace) {
|
|
7
|
+
return {
|
|
8
|
+
id: (0, crypto_1.randomUUID)(), type: 'conversation_list', stateScope: 'workspace',
|
|
9
|
+
workspaceId: String(workspace.id || workspace.path), conversationId: '',
|
|
10
|
+
conversations: agent.listWorkspaceConversationStates(workspace),
|
|
11
|
+
content: '', mode: '', model: '', timestamp: new Date().toISOString(),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=conversationListEvent.js.map
|
|
@@ -92,7 +92,10 @@ export declare class ElectronUtilityAgentClient {
|
|
|
92
92
|
start(): Promise<void>;
|
|
93
93
|
private startNewChild;
|
|
94
94
|
prompt(params: UtilityPromptRequest): Promise<UtilityAgentPromptResult>;
|
|
95
|
-
snapshot(
|
|
95
|
+
snapshot(options?: {
|
|
96
|
+
window?: number;
|
|
97
|
+
before?: number;
|
|
98
|
+
}): Promise<UtilityAgentSnapshotResult>;
|
|
96
99
|
rewind(messageIndex: number): Promise<UtilityConversationRewindResult>;
|
|
97
100
|
requestStop(runId?: string): Promise<UtilityAgentStopResult>;
|
|
98
101
|
enqueueGuide(envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
|
|
@@ -1010,9 +1010,9 @@ class ElectronUtilityAgentClient {
|
|
|
1010
1010
|
await this.start();
|
|
1011
1011
|
return await this.request('prompt', { ...params, target: this.checkedTarget(params.target) }, 0);
|
|
1012
1012
|
}
|
|
1013
|
-
async snapshot() {
|
|
1013
|
+
async snapshot(options = {}) {
|
|
1014
1014
|
await this.start();
|
|
1015
|
-
return await this.requestTargetSnapshot();
|
|
1015
|
+
return await this.requestTargetSnapshot(options);
|
|
1016
1016
|
}
|
|
1017
1017
|
async rewind(messageIndex) {
|
|
1018
1018
|
await this.start();
|
|
@@ -1222,8 +1222,8 @@ class ElectronUtilityAgentClient {
|
|
|
1222
1222
|
]);
|
|
1223
1223
|
return confirmed || this.child !== child;
|
|
1224
1224
|
}
|
|
1225
|
-
async requestTargetSnapshot() {
|
|
1226
|
-
const snapshot = await this.request('snapshot', { target: this.target }, 15_000);
|
|
1225
|
+
async requestTargetSnapshot(options = {}) {
|
|
1226
|
+
const snapshot = await this.request('snapshot', { target: this.target, options }, 15_000);
|
|
1227
1227
|
if (snapshot?.target?.runtimeKey !== this.target.runtimeKey) {
|
|
1228
1228
|
throw new Error('Electron utility runtime snapshot target mismatch');
|
|
1229
1229
|
}
|
|
@@ -7,7 +7,10 @@ export interface ElectronTargetRuntimeClient {
|
|
|
7
7
|
subscribe(listener: (event: AgentWorkEvent) => void): () => void;
|
|
8
8
|
setHostToolHandler(handler: UtilityHostToolHandler | null): void;
|
|
9
9
|
prompt(params: UtilityPromptRequest): Promise<UtilityAgentPromptResult>;
|
|
10
|
-
snapshot(
|
|
10
|
+
snapshot(options?: {
|
|
11
|
+
window?: number;
|
|
12
|
+
before?: number;
|
|
13
|
+
}): Promise<UtilityAgentSnapshotResult>;
|
|
11
14
|
rewind(messageIndex: number): Promise<UtilityConversationRewindResult>;
|
|
12
15
|
requestStop(runId?: string): Promise<UtilityAgentStopResult>;
|
|
13
16
|
enqueueGuide(envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
|
|
@@ -69,7 +72,10 @@ export declare class ElectronUtilityRuntimePool {
|
|
|
69
72
|
subscribe(listener: (event: AgentWorkEvent) => void): () => void;
|
|
70
73
|
setHostToolHandler(handler: UtilityHostToolHandler | null): void;
|
|
71
74
|
prompt(params: UtilityPromptRequest): Promise<UtilityAgentPromptResult>;
|
|
72
|
-
snapshot(target: ConversationRuntimeTarget
|
|
75
|
+
snapshot(target: ConversationRuntimeTarget, options?: {
|
|
76
|
+
window?: number;
|
|
77
|
+
before?: number;
|
|
78
|
+
}): Promise<UtilityAgentSnapshotResult>;
|
|
73
79
|
rewind(target: ConversationRuntimeTarget, messageIndex: number): Promise<UtilityConversationRewindResult>;
|
|
74
80
|
requestStop(target: ConversationRuntimeTarget, runId?: string): Promise<ElectronPoolStopResult>;
|
|
75
81
|
enqueueGuide(envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
|
|
@@ -50,14 +50,20 @@ class ElectronUtilityRuntimePool {
|
|
|
50
50
|
this.release(entry, true);
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
async snapshot(target) {
|
|
53
|
+
async snapshot(target, options = {}) {
|
|
54
54
|
const entry = await this.acquire((0, conversationTarget_1.normalizeConversationTarget)(target));
|
|
55
55
|
let scheduleIdle = false;
|
|
56
56
|
try {
|
|
57
|
-
|
|
57
|
+
const customWindow = options.before != null || (options.window != null && options.window !== 200);
|
|
58
|
+
if (entry.stopIntent) {
|
|
59
|
+
if (customWindow)
|
|
60
|
+
throw new Error('Earlier history is temporarily unavailable while this conversation is stopping. Retry after it stops.');
|
|
58
61
|
return this.supervisorSnapshot(entry);
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
}
|
|
63
|
+
const result = await entry.client.snapshot(options);
|
|
64
|
+
// A fetched older page must not replace the latest supervisor snapshot.
|
|
65
|
+
if (!customWindow)
|
|
66
|
+
entry.lastSnapshot = result;
|
|
61
67
|
if (result.runtime?.runId)
|
|
62
68
|
entry.lastRunId = result.runtime.runId;
|
|
63
69
|
if (result.runtime?.generation)
|
|
@@ -185,7 +191,7 @@ class ElectronUtilityRuntimePool {
|
|
|
185
191
|
}
|
|
186
192
|
}
|
|
187
193
|
async queueAction(target, action, input = {}) {
|
|
188
|
-
const entry = await this.
|
|
194
|
+
const entry = await this.acquire((0, conversationTarget_1.normalizeConversationTarget)(target));
|
|
189
195
|
if (!entry || !entry.client.queueAction)
|
|
190
196
|
throw new Error('Target conversation is not running');
|
|
191
197
|
try {
|
|
@@ -601,11 +601,12 @@ function runMsiExec(args) {
|
|
|
601
601
|
}
|
|
602
602
|
function windowsCommandQuote(value) {
|
|
603
603
|
const text = String(value);
|
|
604
|
-
return
|
|
604
|
+
return `"${text.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/g, '$1$1')}"`;
|
|
605
605
|
}
|
|
606
606
|
function runElevatedMsiExec(args) {
|
|
607
607
|
const argumentString = args.map(arg => windowsCommandQuote(arg)).join(' ');
|
|
608
608
|
const script = [
|
|
609
|
+
'$ErrorActionPreference = "Stop"',
|
|
609
610
|
`$argumentList = '${argumentString.replace(/'/g, "''")}'`,
|
|
610
611
|
'$process = Start-Process -FilePath "msiexec.exe" -ArgumentList $argumentList -Verb RunAs -Wait -PassThru',
|
|
611
612
|
'Write-Output ("EXITCODE:" + $process.ExitCode)',
|
|
@@ -614,7 +615,8 @@ function runElevatedMsiExec(args) {
|
|
|
614
615
|
const stdout = String(result.stdout || '');
|
|
615
616
|
const match = stdout.match(/EXITCODE:(\d+)/);
|
|
616
617
|
return {
|
|
617
|
-
|
|
618
|
+
// A shell exit of 0 without the MSI completion marker is not installation.
|
|
619
|
+
exitCode: match ? Number(match[1]) : -1,
|
|
618
620
|
stdout,
|
|
619
621
|
stderr: String(result.stderr || ''),
|
|
620
622
|
};
|
|
@@ -633,17 +635,39 @@ function uninstallNewmarkProduct(productCode, logPath) {
|
|
|
633
635
|
};
|
|
634
636
|
}
|
|
635
637
|
function installMsiPackage(msiPath, options = {}) {
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
638
|
+
const logDir = path.resolve(options.logDir || os.tmpdir());
|
|
639
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
640
|
+
const evidence = fs.mkdtempSync(path.join(logDir, 'newmark-msi-install-'));
|
|
641
|
+
const logPath = path.join(evidence, 'result.json');
|
|
642
|
+
// The same verified workflow owns CLI, GUI, and developer installs. MSI major
|
|
643
|
+
// upgrades handle old ProductCodes; only an already-installed exact ProductCode
|
|
644
|
+
// uses REINSTALL=ALL + vamus. Fresh installs must select ADDLOCAL=ALL instead.
|
|
645
|
+
const helperCandidates = [
|
|
646
|
+
path.join(process.resourcesPath || path.dirname(process.execPath), 'installer', 'install-windows-msi.ps1'),
|
|
647
|
+
path.resolve(__dirname, '..', '..', 'scripts', 'install-windows-msi.ps1'),
|
|
648
|
+
];
|
|
649
|
+
const helper = helperCandidates.find(candidate => fs.existsSync(candidate) && !candidate.includes(`${path.sep}app.asar${path.sep}`));
|
|
650
|
+
if (!helper)
|
|
651
|
+
return { ok: false, exitCode: -1, logPath, error: 'Verified MSI installer helper is missing from this application.' };
|
|
652
|
+
const args = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', helper,
|
|
653
|
+
'-MsiPath', path.resolve(msiPath), '-EvidenceDirectory', evidence];
|
|
654
|
+
if (options.allowElevate === false)
|
|
655
|
+
args.push('-NoElevate');
|
|
656
|
+
// Node passes argv directly; no Start-Process ArgumentList array re-parsing.
|
|
657
|
+
// Do not detach or impose a timeout which abandons an active MSI transaction.
|
|
658
|
+
const result = (0, child_process_1.spawnSync)('powershell.exe', args, { encoding: 'utf-8', windowsHide: true });
|
|
659
|
+
let completion = {};
|
|
660
|
+
try {
|
|
661
|
+
completion = JSON.parse(fs.readFileSync(logPath, 'utf-8').replace(/^\uFEFF/, ''));
|
|
662
|
+
}
|
|
663
|
+
catch { /* Missing evidence is failure. */ }
|
|
664
|
+
const ok = result.status === 0 && completion.Success === true;
|
|
665
|
+
const exitCode = ok ? (completion.Steps?.some(step => step.ExitCode === 3010) ? 3010 : 0) : (result.status || -1);
|
|
642
666
|
return {
|
|
643
667
|
ok,
|
|
644
|
-
exitCode
|
|
668
|
+
exitCode,
|
|
645
669
|
logPath,
|
|
646
|
-
error: ok ? undefined :
|
|
670
|
+
error: ok ? undefined : (completion.Error || result.error?.message || String(result.stderr || '').trim() || 'MSI installation has no verified completion receipt.'),
|
|
647
671
|
};
|
|
648
672
|
}
|
|
649
673
|
function findLegacyNewmarkExecutables(excludeRoots = []) {
|
|
@@ -752,29 +776,17 @@ function executeManagedMsiInstall(msiPath, options = {}) {
|
|
|
752
776
|
? stopNewmarkProcesses(plan.runningProcesses.map(process => process.pid))
|
|
753
777
|
: { stopped: [], errors: [] };
|
|
754
778
|
const uninstalled = [];
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
const uninstallResult = uninstallNewmarkProduct(product.productCode, logPath);
|
|
759
|
-
if (!uninstallResult.ok) {
|
|
760
|
-
return {
|
|
761
|
-
ok: false,
|
|
762
|
-
plan,
|
|
763
|
-
stopped: stopResult.stopped,
|
|
764
|
-
uninstalled,
|
|
765
|
-
removedLegacy: [],
|
|
766
|
-
exitCode: uninstallResult.exitCode,
|
|
767
|
-
logPath: uninstallResult.logPath,
|
|
768
|
-
error: `Failed to uninstall previous Newmark version ${product.productCode}: ${uninstallResult.error}`,
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
uninstalled.push(product.productCode);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
779
|
+
// AllowSameVersionUpgrades removes related old products within one MSI/UAC
|
|
780
|
+
// transaction. Pre-uninstalling here caused multiple prompts and a fresh
|
|
781
|
+
// product incorrectly sent through the old REINSTALL-only path.
|
|
774
782
|
const removeResult = plan.legacyExecutables.length
|
|
775
783
|
? removeLegacyNewmarkExecutables(plan.legacyExecutables)
|
|
776
784
|
: { removed: [], errors: [] };
|
|
777
785
|
const installResult = installMsiPackage(plan.msiPath, { logDir: options.logDir, allowElevate: options.allowElevate });
|
|
786
|
+
if (installResult.ok) {
|
|
787
|
+
const remaining = new Set(listInstalledNewmarkProducts().map(product => product.productCode));
|
|
788
|
+
uninstalled.push(...plan.installedProducts.filter(product => !remaining.has(product.productCode)).map(product => product.productCode));
|
|
789
|
+
}
|
|
778
790
|
return {
|
|
779
791
|
ok: installResult.ok,
|
|
780
792
|
plan,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { LLMProvider } from '../llm/provider';
|
|
2
|
+
export type ResponseFacet = 'text' | 'vision' | 'tools';
|
|
3
|
+
export type ModelResponseHealth = Partial<Record<ResponseFacet, {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
at: string;
|
|
6
|
+
}>>;
|
|
7
|
+
export interface ResponseHealthIdentity {
|
|
8
|
+
providerId: string;
|
|
9
|
+
modelId: string;
|
|
10
|
+
endpoint: string;
|
|
11
|
+
protocol: string;
|
|
12
|
+
credential: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function readModelResponseHealth(root: string, identity: ResponseHealthIdentity): ModelResponseHealth;
|
|
15
|
+
export declare function recordModelResponseHealth(root: string, identity: ResponseHealthIdentity, updates: Partial<Record<ResponseFacet, boolean>>): void;
|
|
16
|
+
export declare function observeModelResponses(provider: LLMProvider, record: (updates: Partial<Record<ResponseFacet, boolean>>) => void): LLMProvider;
|
|
17
|
+
//# sourceMappingURL=modelResponseHealth.d.ts.map
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readModelResponseHealth = readModelResponseHealth;
|
|
37
|
+
exports.recordModelResponseHealth = recordModelResponseHealth;
|
|
38
|
+
exports.observeModelResponses = observeModelResponses;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const crypto_1 = require("crypto");
|
|
42
|
+
const facets = ['text', 'vision', 'tools'];
|
|
43
|
+
let lastReceiptStamp = 0;
|
|
44
|
+
const cache = new Map();
|
|
45
|
+
function directory(root, identity) {
|
|
46
|
+
const key = (0, crypto_1.createHash)('sha256').update(JSON.stringify(identity)).digest('hex');
|
|
47
|
+
return path.join(root, 'Runtime', 'model-response-health', key);
|
|
48
|
+
}
|
|
49
|
+
function records(dir) {
|
|
50
|
+
try {
|
|
51
|
+
return fs.readdirSync(dir).filter(name => /^\d{16}-[a-f0-9-]+\.json$/.test(name)).sort().reverse().flatMap(name => {
|
|
52
|
+
try {
|
|
53
|
+
return [{ name, health: JSON.parse(fs.readFileSync(path.join(dir, name), 'utf8')) }];
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function readModelResponseHealth(root, identity) {
|
|
65
|
+
const dir = directory(root, identity), saved = cache.get(dir);
|
|
66
|
+
let revision = -1;
|
|
67
|
+
try {
|
|
68
|
+
revision = fs.statSync(dir).mtimeMs;
|
|
69
|
+
}
|
|
70
|
+
catch { /* no observations yet */ }
|
|
71
|
+
if (saved && saved.until > Date.now() && saved.revision === revision)
|
|
72
|
+
return saved.health;
|
|
73
|
+
const health = {};
|
|
74
|
+
for (const record of records(dir))
|
|
75
|
+
for (const facet of facets) {
|
|
76
|
+
const entry = record.health[facet];
|
|
77
|
+
if (!health[facet] && entry && typeof entry.ok === 'boolean')
|
|
78
|
+
health[facet] = entry;
|
|
79
|
+
}
|
|
80
|
+
if (cache.size > 512)
|
|
81
|
+
cache.clear();
|
|
82
|
+
cache.set(dir, { until: Date.now() + 150, revision, health });
|
|
83
|
+
return health;
|
|
84
|
+
}
|
|
85
|
+
function recordModelResponseHealth(root, identity, updates) {
|
|
86
|
+
// Immutable, atomic receipts merge independently across GUI/utility/WSL
|
|
87
|
+
// workers. A text result never overwrites a vision/tool result. No secrets,
|
|
88
|
+
// request bodies or server error text enter the observation files.
|
|
89
|
+
const dir = directory(root, identity), now = Date.now(), health = {};
|
|
90
|
+
for (const facet of facets)
|
|
91
|
+
if (typeof updates[facet] === 'boolean')
|
|
92
|
+
health[facet] = { ok: updates[facet], at: new Date(now).toISOString() };
|
|
93
|
+
if (!Object.keys(health).length)
|
|
94
|
+
return;
|
|
95
|
+
try {
|
|
96
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
97
|
+
const latestStamp = Number(records(dir)[0]?.name.split('-')[0] || 0);
|
|
98
|
+
lastReceiptStamp = Math.max(now * 1000, lastReceiptStamp + 1, latestStamp + 1);
|
|
99
|
+
const file = path.join(dir, `${String(lastReceiptStamp).padStart(16, '0')}-${(0, crypto_1.randomUUID)()}.json`);
|
|
100
|
+
fs.writeFileSync(file + '.tmp', JSON.stringify(health));
|
|
101
|
+
fs.renameSync(file + '.tmp', file);
|
|
102
|
+
const counts = {};
|
|
103
|
+
for (const [index, record] of records(dir).entries()) {
|
|
104
|
+
let keep = index < 4;
|
|
105
|
+
for (const facet of facets)
|
|
106
|
+
if (record.health[facet]) {
|
|
107
|
+
counts[facet] = (counts[facet] || 0) + 1;
|
|
108
|
+
if (counts[facet] <= 2)
|
|
109
|
+
keep = true;
|
|
110
|
+
}
|
|
111
|
+
if (!keep) {
|
|
112
|
+
try {
|
|
113
|
+
fs.unlinkSync(path.join(dir, record.name));
|
|
114
|
+
}
|
|
115
|
+
catch { /* another writer may have pruned it */ }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
cache.delete(dir);
|
|
119
|
+
}
|
|
120
|
+
catch { /* Diagnostic persistence must never break input or transport. */ }
|
|
121
|
+
}
|
|
122
|
+
function hasImage(value) {
|
|
123
|
+
if (Array.isArray(value))
|
|
124
|
+
return value.some(hasImage);
|
|
125
|
+
if (!value || typeof value !== 'object')
|
|
126
|
+
return false;
|
|
127
|
+
const entry = value;
|
|
128
|
+
return ['image_url', 'input_image', 'image'].includes(String(entry.type || '')) || hasImage(entry.content);
|
|
129
|
+
}
|
|
130
|
+
function failedFacet(error, images) {
|
|
131
|
+
const text = String(error).toLowerCase();
|
|
132
|
+
if (/image|vision|multimodal/.test(text) && images)
|
|
133
|
+
return 'vision';
|
|
134
|
+
if (/tools?\b|tool[_ -]?(call|choice|use)|function[_ -]?call/.test(text))
|
|
135
|
+
return 'tools';
|
|
136
|
+
return 'text';
|
|
137
|
+
}
|
|
138
|
+
function observeModelResponses(provider, record) {
|
|
139
|
+
const report = (updates) => { try {
|
|
140
|
+
record(updates);
|
|
141
|
+
}
|
|
142
|
+
catch { /* observation only */ } };
|
|
143
|
+
const chat = provider.chat.bind(provider);
|
|
144
|
+
provider.chat = async (...args) => {
|
|
145
|
+
const images = hasImage(args[1]);
|
|
146
|
+
try {
|
|
147
|
+
const response = await chat(...args);
|
|
148
|
+
if (response.trim())
|
|
149
|
+
report({ text: true, ...(images ? { vision: true } : {}) });
|
|
150
|
+
else
|
|
151
|
+
report({ text: false });
|
|
152
|
+
return response;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
if (!args[5]?.aborted && error?.name !== 'AbortError')
|
|
156
|
+
report({ [failedFacet(error, images)]: false });
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
const stream = provider.chatStreamWithTools.bind(provider);
|
|
161
|
+
provider.chatStreamWithTools = async function* (...args) {
|
|
162
|
+
const images = hasImage(args[1]);
|
|
163
|
+
let meaningful = false, tools = false, failed = false;
|
|
164
|
+
try {
|
|
165
|
+
for await (const token of stream(...args)) {
|
|
166
|
+
if (token.providerError === true) {
|
|
167
|
+
failed = true;
|
|
168
|
+
report({ [failedFacet(token.text, images)]: false });
|
|
169
|
+
}
|
|
170
|
+
if (token.type === 'text' && token.text?.trim())
|
|
171
|
+
meaningful = true;
|
|
172
|
+
if (token.type === 'tool_call') {
|
|
173
|
+
meaningful = true;
|
|
174
|
+
tools = true;
|
|
175
|
+
}
|
|
176
|
+
yield token;
|
|
177
|
+
}
|
|
178
|
+
if (!failed && !args[6]?.aborted) {
|
|
179
|
+
if (meaningful)
|
|
180
|
+
report({ text: true, ...(images ? { vision: true } : {}), ...(tools || args[5].length ? { tools: true } : {}) });
|
|
181
|
+
else
|
|
182
|
+
report({ text: false });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (!failed && !args[6]?.aborted && error?.name !== 'AbortError')
|
|
187
|
+
report({ [failedFacet(error, images)]: false });
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
return provider;
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=modelResponseHealth.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Measured provider counters. Missing values are tracked separately from zero. */
|
|
2
|
+
export interface Counter {
|
|
3
|
+
input: number;
|
|
4
|
+
output: number;
|
|
5
|
+
cacheRead: number;
|
|
6
|
+
cacheWrite: number;
|
|
7
|
+
}
|
|
8
|
+
export interface Reported {
|
|
9
|
+
input: boolean;
|
|
10
|
+
output: boolean;
|
|
11
|
+
cacheRead: boolean;
|
|
12
|
+
cacheWrite: boolean;
|
|
13
|
+
}
|
|
14
|
+
export type UsageInput = Partial<Counter> & {
|
|
15
|
+
reported?: Partial<Reported>;
|
|
16
|
+
};
|
|
17
|
+
/** JSON-persistable conversation coverage; totals remain owned by the caller. */
|
|
18
|
+
export interface Accounting {
|
|
19
|
+
requests: number;
|
|
20
|
+
inputReportedRequests: number;
|
|
21
|
+
outputReportedRequests: number;
|
|
22
|
+
/** Requests with both input/cache read reported and cache read <= input. */
|
|
23
|
+
cacheReportedRequests: number;
|
|
24
|
+
cacheEligibleInputTokens: number;
|
|
25
|
+
cacheReadTokens: number;
|
|
26
|
+
hasLegacyTotals: boolean;
|
|
27
|
+
lastInputTokens: number | null;
|
|
28
|
+
}
|
|
29
|
+
/** Retain this handle for all cumulative usage events from one provider request. */
|
|
30
|
+
export interface AccountingRequest {
|
|
31
|
+
usage: Counter;
|
|
32
|
+
reported: Reported;
|
|
33
|
+
}
|
|
34
|
+
export declare function createAccounting(legacyTotals?: Partial<Counter> | null): Accounting;
|
|
35
|
+
export declare function beginAccountingRequest(accounting: Accounting): AccountingRequest;
|
|
36
|
+
/**
|
|
37
|
+
* Provider stream usage is cumulative per request, including partial updates.
|
|
38
|
+
* Replacing each reported field contributes only its delta, including negative
|
|
39
|
+
* corrections. Unreported/invalid fields never erase a previous measurement.
|
|
40
|
+
*/
|
|
41
|
+
export declare function applyAccountingUsage(accounting: Accounting, totals: Counter, request: AccountingRequest, input: UsageInput): void;
|
|
42
|
+
export declare function ratioSummary(accounting: Accounting): {
|
|
43
|
+
totalRatio: number | null;
|
|
44
|
+
knownRatio: number | null;
|
|
45
|
+
denominator: number;
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=providerUsageAccounting.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAccounting = createAccounting;
|
|
4
|
+
exports.beginAccountingRequest = beginAccountingRequest;
|
|
5
|
+
exports.applyAccountingUsage = applyAccountingUsage;
|
|
6
|
+
exports.ratioSummary = ratioSummary;
|
|
7
|
+
const fields = ['input', 'output', 'cacheRead', 'cacheWrite'];
|
|
8
|
+
function createAccounting(legacyTotals) {
|
|
9
|
+
return {
|
|
10
|
+
requests: 0,
|
|
11
|
+
inputReportedRequests: 0,
|
|
12
|
+
outputReportedRequests: 0,
|
|
13
|
+
cacheReportedRequests: 0,
|
|
14
|
+
cacheEligibleInputTokens: 0,
|
|
15
|
+
cacheReadTokens: 0,
|
|
16
|
+
// Old persisted zeros do not establish whether any cache fields were reported.
|
|
17
|
+
hasLegacyTotals: legacyTotals != null,
|
|
18
|
+
lastInputTokens: null,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function beginAccountingRequest(accounting) {
|
|
22
|
+
accounting.requests++;
|
|
23
|
+
return {
|
|
24
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
25
|
+
reported: { input: false, output: false, cacheRead: false, cacheWrite: false },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function validCachePair(request) {
|
|
29
|
+
return request.reported.input && request.reported.cacheRead
|
|
30
|
+
&& request.usage.cacheRead <= request.usage.input;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Provider stream usage is cumulative per request, including partial updates.
|
|
34
|
+
* Replacing each reported field contributes only its delta, including negative
|
|
35
|
+
* corrections. Unreported/invalid fields never erase a previous measurement.
|
|
36
|
+
*/
|
|
37
|
+
function applyAccountingUsage(accounting, totals, request, input) {
|
|
38
|
+
const hadInput = request.reported.input;
|
|
39
|
+
const hadOutput = request.reported.output;
|
|
40
|
+
const hadPair = validCachePair(request);
|
|
41
|
+
const previousEligibleInput = hadPair ? request.usage.input : 0;
|
|
42
|
+
const previousEligibleCache = hadPair ? request.usage.cacheRead : 0;
|
|
43
|
+
for (const field of fields) {
|
|
44
|
+
const value = input[field];
|
|
45
|
+
// Older internal callers supply actual numeric fields without metadata.
|
|
46
|
+
// Once metadata exists, only its explicit true flags represent reporting.
|
|
47
|
+
const reported = input.reported === undefined || input.reported[field] === true;
|
|
48
|
+
if (!reported || typeof value !== 'number' || !Number.isFinite(value) || value < 0)
|
|
49
|
+
continue;
|
|
50
|
+
totals[field] += value - request.usage[field];
|
|
51
|
+
request.usage[field] = value;
|
|
52
|
+
request.reported[field] = true;
|
|
53
|
+
if (field === 'input')
|
|
54
|
+
accounting.lastInputTokens = value;
|
|
55
|
+
}
|
|
56
|
+
accounting.inputReportedRequests += Number(request.reported.input) - Number(hadInput);
|
|
57
|
+
accounting.outputReportedRequests += Number(request.reported.output) - Number(hadOutput);
|
|
58
|
+
const hasPair = validCachePair(request);
|
|
59
|
+
accounting.cacheReportedRequests += Number(hasPair) - Number(hadPair);
|
|
60
|
+
accounting.cacheEligibleInputTokens += (hasPair ? request.usage.input : 0) - previousEligibleInput;
|
|
61
|
+
accounting.cacheReadTokens += (hasPair ? request.usage.cacheRead : 0) - previousEligibleCache;
|
|
62
|
+
}
|
|
63
|
+
function ratioSummary(accounting) {
|
|
64
|
+
const denominator = accounting.cacheEligibleInputTokens;
|
|
65
|
+
const knownRatio = denominator > 0 ? accounting.cacheReadTokens / denominator : null;
|
|
66
|
+
const complete = !accounting.hasLegacyTotals && accounting.requests > 0
|
|
67
|
+
&& accounting.inputReportedRequests === accounting.requests
|
|
68
|
+
&& accounting.cacheReportedRequests === accounting.requests;
|
|
69
|
+
return { totalRatio: complete ? knownRatio : null, knownRatio, denominator };
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=providerUsageAccounting.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** A local estimate of the actual submitted prompt, never a billing/tokenizer claim. */
|
|
2
|
+
export interface RequestContextEstimate {
|
|
3
|
+
requestId: string;
|
|
4
|
+
runId: string;
|
|
5
|
+
model: string;
|
|
6
|
+
at: string;
|
|
7
|
+
estimatedTokens: number;
|
|
8
|
+
longHistoryTokens: number;
|
|
9
|
+
buildBlockTokens: number;
|
|
10
|
+
systemPromptTokens: number;
|
|
11
|
+
toolSchemaTokens: number;
|
|
12
|
+
hasImages: boolean;
|
|
13
|
+
messageCount: number;
|
|
14
|
+
inputTokens?: number;
|
|
15
|
+
cacheReadTokens?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function estimateSubmittedContext(messages: Array<Record<string, unknown>>, systemPrompt: string, tools: unknown[], buildBlockStart: number): Omit<RequestContextEstimate, 'requestId' | 'runId' | 'model' | 'at'>;
|
|
18
|
+
//# sourceMappingURL=requestContextEstimate.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.estimateSubmittedContext = estimateSubmittedContext;
|
|
4
|
+
function textTokens(text) {
|
|
5
|
+
const nonAscii = text.length - text.replace(/[\u0080-\uFFFF]/g, '').length;
|
|
6
|
+
return Math.ceil((text.length - nonAscii) / 4 + nonAscii);
|
|
7
|
+
}
|
|
8
|
+
function estimateSubmittedContext(messages, systemPrompt, tools, buildBlockStart) {
|
|
9
|
+
let longHistoryTokens = 0;
|
|
10
|
+
let buildBlockTokens = 0;
|
|
11
|
+
let hasImages = false;
|
|
12
|
+
const boundary = Math.max(0, Math.min(messages.length, buildBlockStart));
|
|
13
|
+
for (let index = 0; index < messages.length; index++) {
|
|
14
|
+
const message = messages[index];
|
|
15
|
+
let content = '';
|
|
16
|
+
if (typeof message.content === 'string')
|
|
17
|
+
content = message.content;
|
|
18
|
+
else if (Array.isArray(message.content)) {
|
|
19
|
+
for (const item of message.content) {
|
|
20
|
+
if (!item || typeof item !== 'object')
|
|
21
|
+
continue;
|
|
22
|
+
const part = item;
|
|
23
|
+
if (part.type === 'image_url' || part.type === 'image' || part.type === 'input_image') {
|
|
24
|
+
// Image tokens depend on provider/model/resolution. Base64 size is
|
|
25
|
+
// not token usage; expose incomplete local estimation explicitly.
|
|
26
|
+
hasImages = true;
|
|
27
|
+
}
|
|
28
|
+
else if (typeof part.text === 'string')
|
|
29
|
+
content += part.text;
|
|
30
|
+
else
|
|
31
|
+
content += JSON.stringify(part);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else if (message.content)
|
|
35
|
+
content = JSON.stringify(message.content);
|
|
36
|
+
const calls = Array.isArray(message.tool_calls) ? JSON.stringify(message.tool_calls) : '';
|
|
37
|
+
const count = 6 + textTokens(content) + textTokens(calls)
|
|
38
|
+
+ (calls ? Math.ceil(calls.length / 6) : 0)
|
|
39
|
+
+ textTokens(String(message.name || '') + String(message.tool_call_id || ''));
|
|
40
|
+
if (index < boundary)
|
|
41
|
+
longHistoryTokens += count;
|
|
42
|
+
else
|
|
43
|
+
buildBlockTokens += count;
|
|
44
|
+
}
|
|
45
|
+
const systemPromptTokens = systemPrompt ? 6 + textTokens(systemPrompt) : 0;
|
|
46
|
+
const toolText = tools.length ? JSON.stringify(tools) : '';
|
|
47
|
+
const toolSchemaTokens = toolText ? textTokens(toolText) + Math.ceil(toolText.length / 6) : 0;
|
|
48
|
+
return {
|
|
49
|
+
estimatedTokens: longHistoryTokens + buildBlockTokens + systemPromptTokens + toolSchemaTokens,
|
|
50
|
+
longHistoryTokens, buildBlockTokens, systemPromptTokens, toolSchemaTokens,
|
|
51
|
+
hasImages, messageCount: messages.length,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=requestContextEstimate.js.map
|