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.
Files changed (68) hide show
  1. package/dist/conversation-utility-host.bundle.cjs +4565 -2998
  2. package/dist/conversation-utility-host.js +1 -1
  3. package/dist/core/agent.d.ts +108 -16
  4. package/dist/core/agent.js +698 -195
  5. package/dist/core/agentKernel/agent.d.ts +1 -0
  6. package/dist/core/agentKernel/agent.js +9 -0
  7. package/dist/core/agentKernelDiagnostics.d.ts +4 -6
  8. package/dist/core/agentKernelDiagnostics.js +12 -9
  9. package/dist/core/agentKernelRunner.d.ts +5 -0
  10. package/dist/core/agentKernelRunner.js +250 -107
  11. package/dist/core/autoRouter.js +17 -27
  12. package/dist/core/config.d.ts +2 -0
  13. package/dist/core/config.js +7 -1
  14. package/dist/core/conversationCommandState.d.ts +17 -0
  15. package/dist/core/conversationCommandState.js +140 -0
  16. package/dist/core/conversationKernel.d.ts +25 -4
  17. package/dist/core/conversationKernel.js +360 -89
  18. package/dist/core/conversationListEvent.d.ts +6 -0
  19. package/dist/core/conversationListEvent.js +14 -0
  20. package/dist/core/electronUtilityAgentClient.d.ts +4 -1
  21. package/dist/core/electronUtilityAgentClient.js +4 -4
  22. package/dist/core/electronUtilityRuntimePool.d.ts +8 -2
  23. package/dist/core/electronUtilityRuntimePool.js +11 -5
  24. package/dist/core/installUpdate.js +41 -29
  25. package/dist/core/modelResponseHealth.d.ts +17 -0
  26. package/dist/core/modelResponseHealth.js +193 -0
  27. package/dist/core/providerUsageAccounting.d.ts +47 -0
  28. package/dist/core/providerUsageAccounting.js +71 -0
  29. package/dist/core/requestContextEstimate.d.ts +18 -0
  30. package/dist/core/requestContextEstimate.js +54 -0
  31. package/dist/core/subagent.d.ts +89 -5
  32. package/dist/core/subagent.js +264 -41
  33. package/dist/core/subagentCommunication.d.ts +43 -0
  34. package/dist/core/subagentCommunication.js +167 -0
  35. package/dist/core/types.d.ts +34 -1
  36. package/dist/core/utilityAgentProtocol.d.ts +4 -0
  37. package/dist/core/workEventCoalescer.js +4 -1
  38. package/dist/core/wslAgentClient.d.ts +18 -5
  39. package/dist/core/wslAgentClient.js +79 -27
  40. package/dist/core/wslAgentProtocol.d.ts +7 -0
  41. package/dist/core/wslAgentRuntimePool.d.ts +8 -2
  42. package/dist/core/wslAgentRuntimePool.js +19 -6
  43. package/dist/core/wslRuntimeProcessTree.d.ts +10 -0
  44. package/dist/core/wslRuntimeProcessTree.js +104 -0
  45. package/dist/llm/provider.d.ts +5 -12
  46. package/dist/llm/provider.js +150 -269
  47. package/dist/main.js +582 -356
  48. package/dist/preload.js +3 -3
  49. package/dist/providers/chat-completions.adapter.d.ts +2 -0
  50. package/dist/providers/chat-completions.adapter.js +84 -36
  51. package/dist/providers/provider-adapter.d.ts +2 -0
  52. package/dist/providers/provider-events.d.ts +23 -14
  53. package/dist/providers/provider-events.js +148 -43
  54. package/dist/providers/provider-headers.d.ts +7 -3
  55. package/dist/providers/provider-headers.js +109 -39
  56. package/dist/providers/provider-request-compat.d.ts +6 -0
  57. package/dist/providers/provider-request-compat.js +37 -0
  58. package/dist/providers/responses.adapter.d.ts +2 -0
  59. package/dist/providers/responses.adapter.js +192 -126
  60. package/dist/server.d.ts +9 -2
  61. package/dist/server.js +100 -24
  62. package/dist/tools/index.js +17 -1
  63. package/dist/ui/index.html +2515 -693
  64. package/dist/ui/lucide-sprite.svg +7 -0
  65. package/dist/ui/startup.html +6 -6
  66. package/dist/wsl-agent-host.bundle.cjs +4575 -3006
  67. package/dist/wsl-agent-host.js +9 -4
  68. 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(): Promise<UtilityAgentSnapshotResult>;
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(): Promise<UtilityAgentSnapshotResult>;
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): Promise<UtilityAgentSnapshotResult>;
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
- if (entry.stopIntent)
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
- const result = await entry.client.snapshot();
60
- entry.lastSnapshot = result;
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.acquireExisting(target);
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 /\s/.test(text) ? `"${text.replace(/"/g, '\\"')}"` : text;
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
- exitCode: match ? Number(match[1]) : (result.status === null ? -1 : result.status),
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 logPath = path.join(options.logDir || os.tmpdir(), `newmark-msi-install-${process.pid}-${Date.now()}.log`);
637
- const args = ['/i', path.resolve(msiPath), '/qn', '/norestart', '/l*v', logPath];
638
- let result = runMsiExec(args);
639
- if (result.exitCode !== 0 && result.exitCode !== 3010 && options.allowElevate !== false)
640
- result = runElevatedMsiExec(args);
641
- const ok = result.exitCode === 0 || result.exitCode === 3010;
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: result.exitCode,
668
+ exitCode,
645
669
  logPath,
646
- error: ok ? undefined : `msiexec install exited ${result.exitCode}`,
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
- if (options.uninstallPrevious !== false) {
756
- for (const product of plan.installedProducts) {
757
- const logPath = path.join(options.logDir || os.tmpdir(), `newmark-msi-uninstall-${product.productCode}-${process.pid}-${Date.now()}.log`);
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