newmark-agent 0.5.15 → 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 +1261 -1167
- package/dist/core/agent.d.ts +3 -2
- package/dist/core/agent.js +42 -93
- package/dist/core/agentKernelRunner.js +14 -22
- package/dist/core/autoRouter.js +1 -24
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.js +7 -1
- package/dist/core/conversationKernel.js +15 -8
- package/dist/core/modelResponseHealth.d.ts +17 -0
- package/dist/core/modelResponseHealth.js +193 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/llm/provider.js +4 -4
- package/dist/ui/index.html +138 -74
- package/dist/wsl-agent-host.bundle.cjs +1264 -1170
- package/package.json +2 -2
package/dist/core/agent.d.ts
CHANGED
|
@@ -1097,6 +1097,7 @@ export declare class Agent {
|
|
|
1097
1097
|
evaluateAndSwitch(task: string, override?: AgentPromptMessage['routePolicy']): Promise<boolean>;
|
|
1098
1098
|
shouldExposeToolInterface(): boolean;
|
|
1099
1099
|
modelIsUnavailable(modelName: string): boolean;
|
|
1100
|
+
private failedDeploymentsThisRun;
|
|
1100
1101
|
switchToFallbackModel(errorText?: string): string | null;
|
|
1101
1102
|
isLlmErrorText(text: string): boolean;
|
|
1102
1103
|
private scopedSwitchModels;
|
|
@@ -1109,8 +1110,8 @@ export declare class Agent {
|
|
|
1109
1110
|
/**
|
|
1110
1111
|
* Final visual safety net: OCR each submitted image and ask a text-only
|
|
1111
1112
|
* request to conservatively repair the OCR. This is intentionally callable
|
|
1112
|
-
* only after a visual-input refusal
|
|
1113
|
-
*
|
|
1113
|
+
* only after a real visual-input refusal. Cached capabilities never bypass
|
|
1114
|
+
* the initial image request, and OCR does not disable later image attempts.
|
|
1114
1115
|
*/
|
|
1115
1116
|
finalVisualFallback(errorText: string, signal?: AbortSignal): Promise<string | null>;
|
|
1116
1117
|
engineModel(cache?: BuildProviderCache): LLMProvider | null;
|
package/dist/core/agent.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.Agent = exports.ROOT_AGENT_ACTOR_ID = void 0;
|
|
37
37
|
exports.normalizeIntelligenceTier = normalizeIntelligenceTier;
|
|
38
|
+
const modelResponseHealth_1 = require("./modelResponseHealth");
|
|
38
39
|
const fs = __importStar(require("fs"));
|
|
39
40
|
const path = __importStar(require("path"));
|
|
40
41
|
const crypto = __importStar(require("crypto"));
|
|
@@ -939,13 +940,12 @@ class Agent {
|
|
|
939
940
|
return this.modelSelectionValue();
|
|
940
941
|
}
|
|
941
942
|
defaultModelCandidate() {
|
|
942
|
-
const rejected = new Set(['unavailable', 'auth_error', 'invalid_config']);
|
|
943
943
|
return this.config.allModels()
|
|
944
944
|
.map((model, index) => {
|
|
945
945
|
const validationStatus = effectiveModelValidationStatus(model);
|
|
946
946
|
const evaluationStatus = validationStatus === 'degraded' ? 'degraded' : String(model.evaluation?.status || '').toLowerCase();
|
|
947
947
|
const level = String(model.validation?.level || '').toLowerCase();
|
|
948
|
-
if (model.enabled === false
|
|
948
|
+
if (model.enabled === false) {
|
|
949
949
|
return null;
|
|
950
950
|
}
|
|
951
951
|
let score = 0;
|
|
@@ -1420,8 +1420,8 @@ class Agent {
|
|
|
1420
1420
|
const reference = parsed.image;
|
|
1421
1421
|
const hydrated = this.hydrateDisplayImage(reference);
|
|
1422
1422
|
const model = this.activeModelConfig();
|
|
1423
|
-
const provider =
|
|
1424
|
-
if (!provider || !
|
|
1423
|
+
const provider = this.engineModel();
|
|
1424
|
+
if (!provider || !hydrated?.dataUrl || !hydrated.sha256)
|
|
1425
1425
|
return raw;
|
|
1426
1426
|
throwIfAgentAborted(signal);
|
|
1427
1427
|
const deployment = this.activeDeployment();
|
|
@@ -3905,7 +3905,9 @@ class Agent {
|
|
|
3905
3905
|
// The title request uses the same transport policy and cancellation owner
|
|
3906
3906
|
// as the formal response. A healthy slow model must not be turned into
|
|
3907
3907
|
// five failed requests by an unrelated 15-second title deadline.
|
|
3908
|
-
|
|
3908
|
+
// Reasoning tokens share the completion budget. A 64-token cap can
|
|
3909
|
+
// truncate even a short title and block every first conversation request.
|
|
3910
|
+
const generated = await this.chatWithConversationUsage(provider, modelName, [{ role: 'user', content: prompt }], system, temperature, 2048, signal, reasoningEffort);
|
|
3909
3911
|
if (signal?.aborted)
|
|
3910
3912
|
return '';
|
|
3911
3913
|
const title = this.normalizeConversationRenameTitle(generated);
|
|
@@ -7096,7 +7098,7 @@ class Agent {
|
|
|
7096
7098
|
preference: Number.isFinite(Number(routeMetadata.route_preference)) ? Number(routeMetadata.route_preference) : undefined,
|
|
7097
7099
|
fallbackOnly: !!model.fallback_only,
|
|
7098
7100
|
};
|
|
7099
|
-
})
|
|
7101
|
+
});
|
|
7100
7102
|
}
|
|
7101
7103
|
persistRouteDecision(decision) {
|
|
7102
7104
|
try {
|
|
@@ -7143,15 +7145,11 @@ class Agent {
|
|
|
7143
7145
|
catch { }
|
|
7144
7146
|
}
|
|
7145
7147
|
allModelNames() {
|
|
7146
|
-
const names = this.config.allModels().filter(m => {
|
|
7147
|
-
const status = String(m.evaluation?.status || 'unvalidated');
|
|
7148
|
-
const validationStatus = m.validation?.status;
|
|
7149
|
-
return status === 'available' || status === 'unvalidated' || validationStatus === 'verified' || validationStatus === 'degraded';
|
|
7150
|
-
}).map(m => {
|
|
7148
|
+
const names = this.config.allModels().filter(m => m.enabled !== false).map(m => {
|
|
7151
7149
|
const label = m.display || m.name;
|
|
7152
7150
|
return `${m.provider} / ${label}`;
|
|
7153
7151
|
});
|
|
7154
|
-
return this.config.autoSwitchEnabled() &&
|
|
7152
|
+
return this.config.autoSwitchEnabled() && names.length > 0 ? ['auto', ...names] : names;
|
|
7155
7153
|
}
|
|
7156
7154
|
async evaluateAndSwitch(task, override = undefined) {
|
|
7157
7155
|
if (!this.config.autoSwitchEnabled() || this.model !== 'auto')
|
|
@@ -7241,39 +7239,22 @@ class Agent {
|
|
|
7241
7239
|
return !!this.resolvedDeployment && before !== deploymentIdentity(this.resolvedDeployment);
|
|
7242
7240
|
}
|
|
7243
7241
|
shouldExposeToolInterface() {
|
|
7244
|
-
|
|
7245
|
-
// historical behavior. Auto can safely suppress schemas because its
|
|
7246
|
-
// eligible deployments have explicit Standard/Extended evidence.
|
|
7247
|
-
if (this.model !== 'auto')
|
|
7248
|
-
return true;
|
|
7249
|
-
const model = this.activeModelConfig();
|
|
7250
|
-
if (!model)
|
|
7251
|
-
return false;
|
|
7252
|
-
const validation = model.validation;
|
|
7253
|
-
if (validation?.level !== 'standard' && validation?.level !== 'extended')
|
|
7254
|
-
return true;
|
|
7255
|
-
return validation.capabilities?.tool_use === true || validation.capabilities?.tools === true;
|
|
7242
|
+
return true;
|
|
7256
7243
|
}
|
|
7257
7244
|
modelIsUnavailable(modelName) {
|
|
7258
7245
|
const model = modelName === this.model || modelName === 'auto'
|
|
7259
|
-
? this.activeModelConfig()
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
const validationStatus = effectiveModelValidationStatus(model);
|
|
7264
|
-
// `discovered` means unvalidated, not a failed endpoint. Explicit fixed
|
|
7265
|
-
// selections remain usable for backwards compatibility; only an executed
|
|
7266
|
-
// Basic/Standard/Extended validation may pre-emptively mark them bad.
|
|
7267
|
-
if (model.validation?.level !== 'discovered'
|
|
7268
|
-
&& (validationStatus === 'unavailable' || validationStatus === 'auth_error' || validationStatus === 'invalid_config'))
|
|
7269
|
-
return true;
|
|
7270
|
-
const status = validationStatus === 'degraded' ? 'degraded' : String(model?.evaluation?.status || '').toLowerCase();
|
|
7271
|
-
return status === 'unavailable' || status.startsWith('error');
|
|
7246
|
+
? this.activeModelConfig() : (parseDeploymentSelectionValue(modelName) ? this.config.findDeployment(parseDeploymentSelectionValue(modelName)) : this.config.findModel(modelName));
|
|
7247
|
+
// Only configuration/user switches can prevent a request. Response and
|
|
7248
|
+
// validation observations are labels, never permission to disable a model.
|
|
7249
|
+
return !model || model.enabled === false;
|
|
7272
7250
|
}
|
|
7251
|
+
failedDeploymentsThisRun = new Set();
|
|
7273
7252
|
switchToFallbackModel(errorText = 'transport failure') {
|
|
7274
7253
|
const fallbackEnabled = this.config.getBool('models', 'fallback_on_unavailable');
|
|
7275
7254
|
const observedFailure = (0, autoRouter_1.classifyRouteFailure)(errorText);
|
|
7276
7255
|
const observedDeployment = this.activeDeployment();
|
|
7256
|
+
if (observedDeployment)
|
|
7257
|
+
this.failedDeploymentsThisRun.add(deploymentIdentity(observedDeployment));
|
|
7277
7258
|
// Keep balance exhaustion scoped to the deployment that actually failed.
|
|
7278
7259
|
// Provider adapters normally record this before returning an error, but
|
|
7279
7260
|
// fallback callers are also a public recovery boundary and must not rely
|
|
@@ -7345,7 +7326,7 @@ class Agent {
|
|
|
7345
7326
|
|| deploymentIdentity(this.deploymentRef(m)) !== deploymentIdentity(currentDeployment));
|
|
7346
7327
|
if (!all.length)
|
|
7347
7328
|
return null;
|
|
7348
|
-
const usable = all.filter(m => !this.
|
|
7329
|
+
const usable = all.filter(m => !modelConfigIsUnavailable(m) && !this.failedDeploymentsThisRun.has(deploymentIdentity(this.deploymentRef(m))));
|
|
7349
7330
|
if (!usable.length)
|
|
7350
7331
|
return null;
|
|
7351
7332
|
const pref = this.config.autoSwitchPreference();
|
|
@@ -7577,8 +7558,8 @@ class Agent {
|
|
|
7577
7558
|
/**
|
|
7578
7559
|
* Final visual safety net: OCR each submitted image and ask a text-only
|
|
7579
7560
|
* request to conservatively repair the OCR. This is intentionally callable
|
|
7580
|
-
* only after a visual-input refusal
|
|
7581
|
-
*
|
|
7561
|
+
* only after a real visual-input refusal. Cached capabilities never bypass
|
|
7562
|
+
* the initial image request, and OCR does not disable later image attempts.
|
|
7582
7563
|
*/
|
|
7583
7564
|
async finalVisualFallback(errorText, signal) {
|
|
7584
7565
|
if (!/(?:vision|image|multimodal|image_url|input_image).*(?:not supported|unsupported|拒绝|不支持|failed|failure|invalid)|(?:not supported|unsupported|拒绝|不支持).*(?:vision|image|multimodal|image_url|input_image)/i.test(String(errorText || '')))
|
|
@@ -7586,11 +7567,6 @@ class Agent {
|
|
|
7586
7567
|
const current = this.activeModelConfig();
|
|
7587
7568
|
if (!current)
|
|
7588
7569
|
return null;
|
|
7589
|
-
const alternateVision = this.config.allModels().some(model => model.enabled !== false && model.provider_id === current.provider_id &&
|
|
7590
|
-
model.name !== current.name && !!model.vision && !!model.api_key && !!model.provider_url &&
|
|
7591
|
-
!['unavailable', 'auth_error', 'invalid_config'].includes(String(model.evaluation?.status || model.validation?.status || '').toLowerCase()));
|
|
7592
|
-
if (alternateVision)
|
|
7593
|
-
return null;
|
|
7594
7570
|
const latest = [...this.history].reverse().find(item => item?.role === 'user');
|
|
7595
7571
|
const parts = latest?.content && Array.isArray(latest.content) ? latest.content : [];
|
|
7596
7572
|
const images = parts.map(part => {
|
|
@@ -7606,11 +7582,16 @@ class Agent {
|
|
|
7606
7582
|
if (result.ok && result.text.trim())
|
|
7607
7583
|
ocr.push({ index: index + 1, text: result.text.slice(0, 50_000), confidence: result.confidence });
|
|
7608
7584
|
}
|
|
7609
|
-
catch {
|
|
7585
|
+
catch (error) {
|
|
7586
|
+
if (signal?.aborted)
|
|
7587
|
+
throw error;
|
|
7588
|
+
}
|
|
7610
7589
|
}
|
|
7590
|
+
if (signal?.aborted)
|
|
7591
|
+
throw signal.reason || new Error('Aborted');
|
|
7611
7592
|
if (!ocr.length)
|
|
7612
7593
|
return JSON.stringify({ ok: false, fallback: 'mini_ocr_llm', error: 'Local OCR returned no readable text; no visual content was fabricated.' });
|
|
7613
|
-
const task = typeof latest?.content === 'string' ? latest.content : '';
|
|
7594
|
+
const task = typeof latest?.content === 'string' ? latest.content : parts.filter(part => part.type === 'text').map(part => String(part.text || '')).join('\n');
|
|
7614
7595
|
const evidence = ocr.map(item => `Image ${item.index} (OCR confidence ${item.confidence.toFixed(1)}):\n${item.text}`).join('\n\n');
|
|
7615
7596
|
const prompt = `The provider rejected image input. Answer the user's task using only this approximate OCR evidence. Correct obvious character, spacing, and line-break errors only when supported by context. Preserve [uncertain] markers for ambiguity and never invent missing visual content.\nUser task:\n${task.slice(0, 12_000)}\nOCR evidence:\n${evidence}`;
|
|
7616
7597
|
let corrected = '';
|
|
@@ -7619,12 +7600,15 @@ class Agent {
|
|
|
7619
7600
|
if (provider)
|
|
7620
7601
|
corrected = String(await this.chatWithConversationUsage(provider, this.activeModelName(), [{ role: 'user', content: prompt }], 'You are a text-only OCR correction assistant. Be conservative and explicit about uncertainty.', 0.05, 3000, signal) || '').trim();
|
|
7621
7602
|
}
|
|
7622
|
-
catch {
|
|
7603
|
+
catch (error) {
|
|
7604
|
+
if (signal?.aborted)
|
|
7605
|
+
throw error;
|
|
7606
|
+
}
|
|
7623
7607
|
return JSON.stringify({
|
|
7624
7608
|
ok: !!(corrected || ocr.length),
|
|
7625
7609
|
fallback: 'mini_ocr_llm',
|
|
7626
7610
|
approximate: true,
|
|
7627
|
-
warning: '视觉输入被拒绝;以下内容来自本地 OCR,并经文本模型保守校正,可能不完整。',
|
|
7611
|
+
warning: corrected ? '视觉输入被拒绝;以下内容来自本地 OCR,并经文本模型保守校正,可能不完整。' : '视觉输入被拒绝,文本校正请求也未成功;以下为未经校正的本地 OCR 结果,可能不完整。',
|
|
7628
7612
|
raw_ocr: ocr,
|
|
7629
7613
|
corrected: corrected || ocr.map(item => item.text).join('\n\n'),
|
|
7630
7614
|
uncertainty: corrected ? 'preserved' : 'raw_ocr_only',
|
|
@@ -7657,7 +7641,9 @@ class Agent {
|
|
|
7657
7641
|
const adapters = this.config.contextFlag('provider_adapters_v2');
|
|
7658
7642
|
const thinkingMaps = this.modelThinkingTierMaps(m);
|
|
7659
7643
|
const proxy = this.providerProxyConfig();
|
|
7660
|
-
const create = () => new provider_1.LLMProvider(m.provider, m.provider_url, m.api_key, m.provider_protocol, apiMode, adapters, undefined, thinkingMaps, proxy)
|
|
7644
|
+
const create = () => (0, modelResponseHealth_1.observeModelResponses)(new provider_1.LLMProvider(m.provider, m.provider_url, m.api_key, m.provider_protocol, apiMode, adapters, undefined, thinkingMaps, proxy), updates => (0, modelResponseHealth_1.recordModelResponseHealth)(this.config.rootPath, {
|
|
7645
|
+
providerId: m.provider_id, modelId: m.name, endpoint: m.provider_url, protocol: m.provider_protocol, credential: m.api_key,
|
|
7646
|
+
}, updates));
|
|
7661
7647
|
if (!cache)
|
|
7662
7648
|
return create();
|
|
7663
7649
|
// Match the provider's configured-proxy/environment precedence. A changed
|
|
@@ -7974,6 +7960,7 @@ class Agent {
|
|
|
7974
7960
|
return [{ type: 'text', text: '[Workspace required] Select or create a workspace before starting a conversation.' }];
|
|
7975
7961
|
}
|
|
7976
7962
|
if (this.processDepth === 0) {
|
|
7963
|
+
this.failedDeploymentsThisRun.clear();
|
|
7977
7964
|
this.processingConversationId = this.activeConversationId || 'default';
|
|
7978
7965
|
this.activeProcessAbortController = new AbortController();
|
|
7979
7966
|
this.subagents.resumeScheduling();
|
|
@@ -7992,27 +7979,6 @@ class Agent {
|
|
|
7992
7979
|
this.fileDiffs = [];
|
|
7993
7980
|
this.pendingOptions = [];
|
|
7994
7981
|
try {
|
|
7995
|
-
if (this.model === 'auto') {
|
|
7996
|
-
// Auto routing re-resolves each turn; drop a stale blocked deployment
|
|
7997
|
-
// so the router can pick an unblocked candidate instead of failing here.
|
|
7998
|
-
if (this.resolvedDeployment && this.isBalanceBlockedDeployment(this.resolvedDeployment)) {
|
|
7999
|
-
this.resolvedDeployment = null;
|
|
8000
|
-
this.lastRouteDecision = null;
|
|
8001
|
-
this.pendingAutoAttempts = [];
|
|
8002
|
-
}
|
|
8003
|
-
}
|
|
8004
|
-
else {
|
|
8005
|
-
const blockedMs = this.balanceBlockedMs();
|
|
8006
|
-
if (blockedMs > 0) {
|
|
8007
|
-
const waitSeconds = Math.max(1, Math.ceil(blockedMs / 1000));
|
|
8008
|
-
const deployment = this.activeDeployment();
|
|
8009
|
-
const label = deployment ? `${deployment.providerId}/${deployment.modelId}` : this.model;
|
|
8010
|
-
const message = `Provider balance exhausted (HTTP 402) for ${label}. Requests on this deployment are paused for ${waitSeconds}s; switch provider or model to continue immediately.`;
|
|
8011
|
-
this.status = 'error';
|
|
8012
|
-
this.emitWorkEvent({ type: 'error', content: message });
|
|
8013
|
-
throw new Error(message);
|
|
8014
|
-
}
|
|
8015
|
-
}
|
|
8016
7982
|
let text = typeof input === 'string' ? input : String(input.text || '');
|
|
8017
7983
|
const inputEnvelope = typeof input === 'string' ? null : input;
|
|
8018
7984
|
let hiddenUserInput = inputEnvelope?.hiddenUserInput === true;
|
|
@@ -8109,16 +8075,6 @@ class Agent {
|
|
|
8109
8075
|
await this.evaluateAndSwitch(`${text}\n[image attachment]`, inputEnvelope?.routePolicy);
|
|
8110
8076
|
autoRouteEvaluated = true;
|
|
8111
8077
|
}
|
|
8112
|
-
const selectedModel = this.activeModelConfig();
|
|
8113
|
-
if (images.length && !selectedModel?.vision) {
|
|
8114
|
-
// Give the normal route planner first chance to select another
|
|
8115
|
-
// same-provider vision deployment. If none is available, the kernel
|
|
8116
|
-
// preflight invokes the final mini-OCR + text-only correction path.
|
|
8117
|
-
const hasSameProviderVision = selectedModel && this.config.allModels().some(model => model.enabled !== false && model.provider_id === selectedModel.provider_id &&
|
|
8118
|
-
model.name !== selectedModel.name && !!model.vision);
|
|
8119
|
-
if (hasSameProviderVision)
|
|
8120
|
-
this.switchToFallbackModel('vision input not supported by the selected model');
|
|
8121
|
-
}
|
|
8122
8078
|
const now = this.nowLabel();
|
|
8123
8079
|
const visibleUserInput = inputEnvelope?.visibleUserInput === undefined
|
|
8124
8080
|
? text
|
|
@@ -9217,9 +9173,7 @@ class Agent {
|
|
|
9217
9173
|
}
|
|
9218
9174
|
subagentToolDefinitions(defs) {
|
|
9219
9175
|
const modelCapabilities = this.activeModelConfig();
|
|
9220
|
-
const visionFiltered =
|
|
9221
|
-
? defs
|
|
9222
|
-
: defs.filter((tool) => tool.function?.name !== 'image_inspect');
|
|
9176
|
+
const visionFiltered = defs;
|
|
9223
9177
|
const withImageGeneration = modelCapabilities?.image_output
|
|
9224
9178
|
? [...visionFiltered, {
|
|
9225
9179
|
type: 'function',
|
|
@@ -9263,8 +9217,6 @@ class Agent {
|
|
|
9263
9217
|
}
|
|
9264
9218
|
}
|
|
9265
9219
|
async handleImageInspect(args) {
|
|
9266
|
-
if (!this.activeModelConfig()?.vision)
|
|
9267
|
-
return '[Image inspect unavailable] The selected model has not passed vision validation.';
|
|
9268
9220
|
let input = {};
|
|
9269
9221
|
try {
|
|
9270
9222
|
input = JSON.parse(args);
|
|
@@ -10562,7 +10514,9 @@ function routeProviderFingerprint(provider) {
|
|
|
10562
10514
|
});
|
|
10563
10515
|
}
|
|
10564
10516
|
function modelConfigurationFingerprint(model) {
|
|
10565
|
-
const { validation, evaluation, _previous_name, previous_name, ...configuration } = model;
|
|
10517
|
+
const { validation, evaluation, response_health, enabled, _previous_name, previous_name, ...configuration } = model;
|
|
10518
|
+
void response_health;
|
|
10519
|
+
void enabled;
|
|
10566
10520
|
void validation;
|
|
10567
10521
|
void evaluation;
|
|
10568
10522
|
void _previous_name;
|
|
@@ -10604,12 +10558,7 @@ function resetEditedModelValidationEvidence(incomingProviders, existingProviders
|
|
|
10604
10558
|
}
|
|
10605
10559
|
}
|
|
10606
10560
|
function modelConfigIsUnavailable(model) {
|
|
10607
|
-
|
|
10608
|
-
if (model.validation?.level !== 'discovered'
|
|
10609
|
-
&& (validationStatus === 'unavailable' || validationStatus === 'auth_error' || validationStatus === 'invalid_config'))
|
|
10610
|
-
return true;
|
|
10611
|
-
const evaluationStatus = validationStatus === 'degraded' ? 'degraded' : String(model.evaluation?.status || '').toLowerCase();
|
|
10612
|
-
return evaluationStatus === 'unavailable' || evaluationStatus.startsWith('error');
|
|
10561
|
+
return model.enabled === false;
|
|
10613
10562
|
}
|
|
10614
10563
|
function parseDeploymentSelectionValue(value) {
|
|
10615
10564
|
const marker = String(value || '').trim();
|
|
@@ -480,15 +480,18 @@ async function runAgentKernel(agent) {
|
|
|
480
480
|
try {
|
|
481
481
|
const linkedPlanRevisionBeforeRun = agent.getLinkedPlan().revision;
|
|
482
482
|
const modelBeforeKernelRun = agent.model;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
483
|
+
let lastTurn = await runWithCompressionResume([], false);
|
|
484
|
+
if (kernelTurnFailed(agent, lastTurn)) {
|
|
485
|
+
const visualFallback = await agent.finalVisualFallback(lastTurn.errorMessage || lastTurn.text, processSignal);
|
|
486
|
+
if (visualFallback) {
|
|
487
|
+
tokens.push({ type: 'text', text: visualFallback });
|
|
488
|
+
agent.emitWorkEvent({ type: 'final_response', content: visualFallback });
|
|
489
|
+
agent.chatMessages.push({ role: 'assistant', content: visualFallback, mode: agent.modeName(), model: agent.model, timestamp: agent.nowLabel(), runId: agent.currentWorkRunId() || undefined });
|
|
490
|
+
agent.history.push({ role: 'assistant', content: visualFallback, run_id: agent.currentWorkRunId() || undefined });
|
|
491
|
+
agent.saveWorkspaceConversationState();
|
|
492
|
+
agent.recordWorkStatus('Final visual fallback used: local mini OCR plus conservative text correction.');
|
|
493
|
+
lastTurn = { ...lastTurn, text: visualFallback, errorMessage: '', stopReason: 'stop' };
|
|
494
|
+
}
|
|
492
495
|
}
|
|
493
496
|
if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some(t => t.text?.includes('[Model fallback]'))) {
|
|
494
497
|
const notice = `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.`;
|
|
@@ -601,14 +604,6 @@ async function runAgentKernel(agent) {
|
|
|
601
604
|
await agent.waitForPlannedRouteRetry();
|
|
602
605
|
lastTurn = await runWithCompressionResume([], false);
|
|
603
606
|
}
|
|
604
|
-
if (kernelTurnFailed(agent, lastTurn)) {
|
|
605
|
-
const visualFallback = await agent.finalVisualFallback(lastTurn.errorMessage || lastTurn.text, processSignal);
|
|
606
|
-
if (visualFallback) {
|
|
607
|
-
tokens.push({ type: 'text', text: visualFallback });
|
|
608
|
-
agent.recordWorkStatus('Final visual fallback used: local mini OCR plus conservative text correction.');
|
|
609
|
-
lastTurn = { ...lastTurn, text: visualFallback, errorMessage: '', stopReason: 'stop' };
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
607
|
if (kernelTurnFailed(agent, lastTurn)) {
|
|
613
608
|
throw new ProviderRunError(normalizePublicProviderError(lastTurn.errorMessage || lastTurn.text, [agent.activeModelConfig()?.api_key]));
|
|
614
609
|
}
|
|
@@ -1167,7 +1162,7 @@ function toKernelModel(agent) {
|
|
|
1167
1162
|
provider: m?.provider || 'newmark',
|
|
1168
1163
|
baseUrl: m?.provider_url || '',
|
|
1169
1164
|
reasoning: !!m?.thinking,
|
|
1170
|
-
input:
|
|
1165
|
+
input: ['text', 'image'],
|
|
1171
1166
|
cost: {
|
|
1172
1167
|
input: Number(m?.cost_per_1k_input || 0) * 1000,
|
|
1173
1168
|
output: Number(m?.cost_per_1k_output || 0) * 1000,
|
|
@@ -1752,8 +1747,6 @@ function visualFallbackImageInput(agent, name, text) {
|
|
|
1752
1747
|
if (name !== 'screen_capture' && name !== 'computer_use' && name !== 'browser_use' && name !== 'pdf_read')
|
|
1753
1748
|
return {};
|
|
1754
1749
|
const model = agent.activeModelConfig();
|
|
1755
|
-
if (!model?.vision)
|
|
1756
|
-
return {};
|
|
1757
1750
|
try {
|
|
1758
1751
|
const parsed = JSON.parse(text);
|
|
1759
1752
|
const nested = name === 'pdf_read' && parsed.result && typeof parsed.result === 'object'
|
|
@@ -1917,8 +1910,7 @@ async function executeNewmarkTool(agent, name, args, inputSchema, signal, onSett
|
|
|
1917
1910
|
actorId: agent.runtimeActorId,
|
|
1918
1911
|
workspaceId: (0, terminalTakeover_1.terminalTakeoverWorkspaceId)(wsDir),
|
|
1919
1912
|
backend: process.env.NEWMARK_WSL_DISTRO ? 'wsl' : (process.platform === 'win32' ? 'windows' : process.platform),
|
|
1920
|
-
allowEphemeralVisionImage: (name === 'screen_capture' || name === 'computer_use' || name === 'browser_use' || name === 'pdf_read' || name === 'ocr_read')
|
|
1921
|
-
&& !!agent.activeModelConfig()?.vision,
|
|
1913
|
+
allowEphemeralVisionImage: (name === 'screen_capture' || name === 'computer_use' || name === 'browser_use' || name === 'pdf_read' || name === 'ocr_read'),
|
|
1922
1914
|
signal,
|
|
1923
1915
|
});
|
|
1924
1916
|
if (signal?.aborted)
|
package/dist/core/autoRouter.js
CHANGED
|
@@ -34,19 +34,6 @@ function inSubset(deployment, subset) {
|
|
|
34
34
|
function inScope(deployment, scope) {
|
|
35
35
|
return scope.kind === 'global' || deployment.providerId === scope.providerId;
|
|
36
36
|
}
|
|
37
|
-
function validationEligible(candidate, now) {
|
|
38
|
-
const reasons = [];
|
|
39
|
-
if (candidate.validation.level !== 'standard' && candidate.validation.level !== 'extended') {
|
|
40
|
-
reasons.push(`validation_level:${candidate.validation.level}`);
|
|
41
|
-
}
|
|
42
|
-
if (candidate.validation.status !== 'verified' && candidate.validation.status !== 'degraded') {
|
|
43
|
-
reasons.push(`validation_status:${candidate.validation.status}`);
|
|
44
|
-
}
|
|
45
|
-
const checkedAt = Date.parse(candidate.validation.checkedAt);
|
|
46
|
-
if (!Number.isFinite(checkedAt) || now - checkedAt > VALIDATION_TTL_MS)
|
|
47
|
-
reasons.push('validation_expired');
|
|
48
|
-
return reasons;
|
|
49
|
-
}
|
|
50
37
|
function normalizeAutoPreference(value) {
|
|
51
38
|
switch (String(value || '').toLowerCase()) {
|
|
52
39
|
case 'performance':
|
|
@@ -191,7 +178,6 @@ class AutoRouter {
|
|
|
191
178
|
this.claimEndpointAttempt(fixed.deployment);
|
|
192
179
|
return decision;
|
|
193
180
|
}
|
|
194
|
-
const requiredCapabilities = new Set([...policy.requiredCapabilities, ...request.requiredCapabilities].map(item => String(item).toLowerCase()));
|
|
195
181
|
const eligible = [];
|
|
196
182
|
for (const candidate of candidates) {
|
|
197
183
|
const reasons = [];
|
|
@@ -205,13 +191,8 @@ class AutoRouter {
|
|
|
205
191
|
reasons.push('fallback_only');
|
|
206
192
|
if (candidate.preview && !policy.allowPreview)
|
|
207
193
|
reasons.push('preview_disallowed');
|
|
208
|
-
reasons.push(...validationEligible(candidate, now));
|
|
209
194
|
if (request.estimatedInputTokens + request.expectedOutputTokens > Math.max(0, candidate.maxContextTokens || 0))
|
|
210
195
|
reasons.push('context_too_small');
|
|
211
|
-
const capabilities = new Set(candidate.capabilities.map(item => String(item).toLowerCase()));
|
|
212
|
-
for (const capability of requiredCapabilities)
|
|
213
|
-
if (!capabilities.has(capability))
|
|
214
|
-
reasons.push(`missing_capability:${capability}`);
|
|
215
196
|
if (policy.privacy !== 'default' && !candidate.privacy.includes(policy.privacy))
|
|
216
197
|
reasons.push(`privacy:${policy.privacy}`);
|
|
217
198
|
if (policy.dataRegion) {
|
|
@@ -230,8 +211,6 @@ class AutoRouter {
|
|
|
230
211
|
if (policy.maxExpectedCostUsd !== undefined && (expectedCost === undefined || expectedCost > policy.maxExpectedCostUsd)) {
|
|
231
212
|
reasons.push(expectedCost === undefined ? 'unknown_cost' : 'budget_exceeded');
|
|
232
213
|
}
|
|
233
|
-
if (this.circuitState(candidate.deployment, now, false) === 'open')
|
|
234
|
-
reasons.push('circuit_open');
|
|
235
214
|
if (reasons.length)
|
|
236
215
|
decision.excludedCandidates.push({ deployment: { ...candidate.deployment }, reasons: [...new Set(reasons)] });
|
|
237
216
|
else
|
|
@@ -325,9 +304,7 @@ class AutoRouter {
|
|
|
325
304
|
&& inSubset(candidate.deployment, subset)
|
|
326
305
|
&& !sameDeployment(candidate.deployment, current)
|
|
327
306
|
&& !attemptedDeployments.some(attempted => sameDeployment(candidate.deployment, attempted))
|
|
328
|
-
&&
|
|
329
|
-
&& this.passedInitialHardFilters(decision, candidate)
|
|
330
|
-
&& this.circuitState(candidate.deployment, now, false) !== 'open');
|
|
307
|
+
&& this.passedInitialHardFilters(decision, candidate));
|
|
331
308
|
const equivalent = currentGroup
|
|
332
309
|
? eligible.find(candidate => candidate.deployment.logicalModelGroupId === currentGroup && !candidate.fallbackOnly)
|
|
333
310
|
: undefined;
|
package/dist/core/config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ModelResponseHealth } from './modelResponseHealth';
|
|
1
2
|
export interface JsonValue {
|
|
2
3
|
[key: string]: unknown;
|
|
3
4
|
}
|
|
@@ -20,6 +21,7 @@ export interface ProviderConfig {
|
|
|
20
21
|
models: ModelConfig[];
|
|
21
22
|
}
|
|
22
23
|
export interface ModelConfig {
|
|
24
|
+
response_health?: ModelResponseHealth;
|
|
23
25
|
name: string;
|
|
24
26
|
display: string;
|
|
25
27
|
description: string;
|
package/dist/core/config.js
CHANGED
|
@@ -44,6 +44,7 @@ exports.mergeProviderSecrets = mergeProviderSecrets;
|
|
|
44
44
|
exports.defaultModelConfig = defaultModelConfig;
|
|
45
45
|
exports.stableProviderId = stableProviderId;
|
|
46
46
|
exports.defaultConfig = defaultConfig;
|
|
47
|
+
const modelResponseHealth_1 = require("./modelResponseHealth");
|
|
47
48
|
const fs = __importStar(require("fs"));
|
|
48
49
|
const path = __importStar(require("path"));
|
|
49
50
|
const crypto_1 = require("crypto");
|
|
@@ -191,7 +192,12 @@ class ConfigManager {
|
|
|
191
192
|
this.workspaceOverrides.clear();
|
|
192
193
|
}
|
|
193
194
|
providers() {
|
|
194
|
-
return this.normalizeProviders((this.getGlobal('models', 'providers')) || [])
|
|
195
|
+
return this.normalizeProviders((this.getGlobal('models', 'providers')) || []).map(provider => ({
|
|
196
|
+
...provider,
|
|
197
|
+
models: provider.models.map(model => ({ ...model, response_health: (0, modelResponseHealth_1.readModelResponseHealth)(this.rootPath, {
|
|
198
|
+
providerId: provider.id, modelId: model.name, endpoint: provider.base_url, protocol: provider.protocol, credential: provider.api_key,
|
|
199
|
+
}) })),
|
|
200
|
+
}));
|
|
195
201
|
}
|
|
196
202
|
allModels() {
|
|
197
203
|
const models = [];
|
|
@@ -384,7 +384,7 @@ class ConversationKernel {
|
|
|
384
384
|
queueAction(target, action, input = {}) {
|
|
385
385
|
const normalized = this.normalizeTarget(target);
|
|
386
386
|
const runtime = this.findRuntime(normalized) || this.runtime(normalized, {
|
|
387
|
-
mode: this.host.mode, model: this.host.
|
|
387
|
+
mode: this.host.mode, model: this.host.modelSelectionValue(), intelligence: this.host.intelligence,
|
|
388
388
|
inputMode: this.host.inputMode, engine: this.host.engine,
|
|
389
389
|
});
|
|
390
390
|
runtime.runId ||= (0, crypto_1.randomUUID)();
|
|
@@ -474,7 +474,7 @@ class ConversationKernel {
|
|
|
474
474
|
let runtime = this.findRuntime(normalized);
|
|
475
475
|
const runner = runtime?.runner || this.createRunner(normalized);
|
|
476
476
|
if (!runtime && runner.conversationContinuations().length) {
|
|
477
|
-
runtime = this.runtime(normalized, { mode: runner.mode, model: runner.
|
|
477
|
+
runtime = this.runtime(normalized, { mode: runner.mode, model: runner.modelSelectionValue(), intelligence: runner.intelligence, inputMode: runner.inputMode, engine: runner.engine }, runner);
|
|
478
478
|
// Recovered user input remains visible and manageable until its owner
|
|
479
479
|
// explicitly restores the persisted queue policy or resumes the queue.
|
|
480
480
|
runtime.queuePaused = true;
|
|
@@ -491,7 +491,7 @@ class ConversationKernel {
|
|
|
491
491
|
workEvents: this.events(normalized),
|
|
492
492
|
runtime: this.runtimeState(normalized),
|
|
493
493
|
mode: runner.mode,
|
|
494
|
-
model: runner.
|
|
494
|
+
model: runner.modelSelectionValue(),
|
|
495
495
|
intelligence: runner.intelligence,
|
|
496
496
|
status: runner.status,
|
|
497
497
|
goal: conversationSnapshot.goal,
|
|
@@ -783,7 +783,9 @@ class ConversationKernel {
|
|
|
783
783
|
const runner = runtime?.runner || this.createRunner(normalized);
|
|
784
784
|
if (!runtime || !runtime.activePromise) {
|
|
785
785
|
// No Build block is running: the selection applies immediately.
|
|
786
|
-
runner.setModel(model);
|
|
786
|
+
runner.setModel(model, true);
|
|
787
|
+
if (runtime)
|
|
788
|
+
runtime.options.model = runner.modelSelectionValue();
|
|
787
789
|
}
|
|
788
790
|
else {
|
|
789
791
|
// A Build block is running. The in-flight block keeps its current model
|
|
@@ -793,7 +795,7 @@ class ConversationKernel {
|
|
|
793
795
|
runtime.options.model = model;
|
|
794
796
|
}
|
|
795
797
|
runner.saveWorkspaceConversationState(true);
|
|
796
|
-
return runner.
|
|
798
|
+
return runner.modelSelectionValue();
|
|
797
799
|
}
|
|
798
800
|
async toggleGoalPause(target) {
|
|
799
801
|
const normalized = this.normalizeTarget(target);
|
|
@@ -802,7 +804,7 @@ class ConversationKernel {
|
|
|
802
804
|
const runner = this.createRunner(normalized);
|
|
803
805
|
runtime = this.runtime(normalized, {
|
|
804
806
|
mode: runner.mode,
|
|
805
|
-
model: runner.
|
|
807
|
+
model: runner.modelSelectionValue(),
|
|
806
808
|
intelligence: runner.intelligence,
|
|
807
809
|
inputMode: runner.inputMode,
|
|
808
810
|
engine: runner.engine,
|
|
@@ -1577,7 +1579,12 @@ class ConversationKernel {
|
|
|
1577
1579
|
// Host-global state can belong to a different foreground conversation.
|
|
1578
1580
|
const restoredMode = agent.getConversationSnapshot(agent.activeConversationId).mode;
|
|
1579
1581
|
agent.setMode(restoredMode || options.mode);
|
|
1580
|
-
|
|
1582
|
+
// Older clients may echo a bare display name from a snapshot. Preserve
|
|
1583
|
+
// an already restored exact deployment in that case; never guess between
|
|
1584
|
+
// providers for an unbound or different ambiguous model name.
|
|
1585
|
+
if (options.model !== agent.model || !agent.activeDeployment()) {
|
|
1586
|
+
agent.setModel(options.model);
|
|
1587
|
+
}
|
|
1581
1588
|
agent.setIntelligence(options.intelligence);
|
|
1582
1589
|
agent.inputMode = options.inputMode;
|
|
1583
1590
|
agent.engine = options.engine;
|
|
@@ -1603,7 +1610,7 @@ class ConversationKernel {
|
|
|
1603
1610
|
newContent: d.newContent,
|
|
1604
1611
|
})),
|
|
1605
1612
|
mode: runtime.runner.mode,
|
|
1606
|
-
model: runtime.runner.
|
|
1613
|
+
model: runtime.runner.modelSelectionValue(),
|
|
1607
1614
|
status: runtime.runner.status,
|
|
1608
1615
|
goal: runtime.runner.goal ? { objective: runtime.runner.goal.objective, paused: runtime.runner.goal.paused } : null,
|
|
1609
1616
|
options: runtime.runner.pendingOptions,
|
|
@@ -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
|