newmark-agent 0.5.3 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversation-utility-host.bundle.cjs +76 -3
- package/dist/core/agent.d.ts +7 -0
- package/dist/core/agent.js +63 -2
- package/dist/core/agentKernelRunner.js +18 -1
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +4 -0
- package/dist/wsl-agent-host.bundle.cjs +76 -3
- package/package.json +1 -1
|
@@ -336933,6 +336933,10 @@ var ToolExecutor = class {
|
|
|
336933
336933
|
async webSearch(query) {
|
|
336934
336934
|
return this.wsearch(query);
|
|
336935
336935
|
}
|
|
336936
|
+
/** OCR entry point for the runtime's final visual fallback. */
|
|
336937
|
+
async finalVisualFallbackOcr(dataUrl, signal) {
|
|
336938
|
+
return await this.localOcr.recognizeDataUrl(dataUrl, signal, "sparse-ui");
|
|
336939
|
+
}
|
|
336936
336940
|
setHostProfile(profile) {
|
|
336937
336941
|
this.hostProfile = { ...profile };
|
|
336938
336942
|
}
|
|
@@ -340846,7 +340850,12 @@ async function runAgentKernel(agent) {
|
|
|
340846
340850
|
try {
|
|
340847
340851
|
const linkedPlanRevisionBeforeRun = agent.getLinkedPlan().revision;
|
|
340848
340852
|
const modelBeforeKernelRun = agent.model;
|
|
340849
|
-
|
|
340853
|
+
const preflightVisualFallback = !agent.activeModelConfig()?.vision ? await agent.finalVisualFallback("vision input not supported by the selected model", processSignal) : null;
|
|
340854
|
+
let lastTurn = preflightVisualFallback ? { text: preflightVisualFallback, stopReason: "stop", errorMessage: "" } : await runWithCompressionResume([], false);
|
|
340855
|
+
if (preflightVisualFallback) {
|
|
340856
|
+
tokens.push({ type: "text", text: preflightVisualFallback });
|
|
340857
|
+
agent.recordWorkStatus("Final visual fallback used: local mini OCR plus conservative text correction.");
|
|
340858
|
+
}
|
|
340850
340859
|
if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some((t3) => t3.text?.includes("[Model fallback]"))) {
|
|
340851
340860
|
tokens.unshift({ type: "text", text: `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.` });
|
|
340852
340861
|
}
|
|
@@ -340876,6 +340885,14 @@ async function runAgentKernel(agent) {
|
|
|
340876
340885
|
await agent.waitForPlannedRouteRetry();
|
|
340877
340886
|
lastTurn = await runWithCompressionResume([], false);
|
|
340878
340887
|
}
|
|
340888
|
+
if (kernelTurnFailed(agent, lastTurn)) {
|
|
340889
|
+
const visualFallback = await agent.finalVisualFallback(lastTurn.errorMessage || lastTurn.text, processSignal);
|
|
340890
|
+
if (visualFallback) {
|
|
340891
|
+
tokens.push({ type: "text", text: visualFallback });
|
|
340892
|
+
agent.recordWorkStatus("Final visual fallback used: local mini OCR plus conservative text correction.");
|
|
340893
|
+
lastTurn = { ...lastTurn, text: visualFallback, errorMessage: "", stopReason: "stop" };
|
|
340894
|
+
}
|
|
340895
|
+
}
|
|
340879
340896
|
if (kernelTurnFailed(agent, lastTurn)) {
|
|
340880
340897
|
throw new ProviderRunError(normalizePublicProviderError(lastTurn.errorMessage || lastTurn.text, [agent.activeModelConfig()?.api_key]));
|
|
340881
340898
|
}
|
|
@@ -350820,6 +350837,60 @@ ${msg.content}
|
|
|
350820
350837
|
};
|
|
350821
350838
|
return results;
|
|
350822
350839
|
}
|
|
350840
|
+
/**
|
|
350841
|
+
* Final visual safety net: OCR each submitted image and ask a text-only
|
|
350842
|
+
* request to conservatively repair the OCR. This is intentionally callable
|
|
350843
|
+
* only after a visual-input refusal and after same-provider vision routing
|
|
350844
|
+
* has been exhausted; the original image is never sent again.
|
|
350845
|
+
*/
|
|
350846
|
+
async finalVisualFallback(errorText, signal) {
|
|
350847
|
+
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 || ""))) return null;
|
|
350848
|
+
const current = this.activeModelConfig();
|
|
350849
|
+
if (!current) return null;
|
|
350850
|
+
const alternateVision = this.config.allModels().some(
|
|
350851
|
+
(model) => model.enabled !== false && model.provider_id === current.provider_id && model.name !== current.name && !!model.vision && !!model.api_key && !!model.provider_url && !["unavailable", "auth_error", "invalid_config"].includes(String(model.evaluation?.status || model.validation?.status || "").toLowerCase())
|
|
350852
|
+
);
|
|
350853
|
+
if (alternateVision) return null;
|
|
350854
|
+
const latest = [...this.history].reverse().find((item) => item?.role === "user");
|
|
350855
|
+
const parts = latest?.content && Array.isArray(latest.content) ? latest.content : [];
|
|
350856
|
+
const images = parts.map((part) => {
|
|
350857
|
+
const image = part.image_url;
|
|
350858
|
+
return image && typeof image === "object" ? String(image.url || "") : "";
|
|
350859
|
+
}).filter((value) => /^data:image\/(?:png|jpeg);base64,/i.test(value)).slice(0, 4);
|
|
350860
|
+
if (!images.length) return null;
|
|
350861
|
+
const ocr = [];
|
|
350862
|
+
for (const [index, image] of images.entries()) {
|
|
350863
|
+
try {
|
|
350864
|
+
const result = await this.tools.finalVisualFallbackOcr(image, signal);
|
|
350865
|
+
if (result.ok && result.text.trim()) ocr.push({ index: index + 1, text: result.text.slice(0, 5e4), confidence: result.confidence });
|
|
350866
|
+
} catch {
|
|
350867
|
+
}
|
|
350868
|
+
}
|
|
350869
|
+
if (!ocr.length) return JSON.stringify({ ok: false, fallback: "mini_ocr_llm", error: "Local OCR returned no readable text; no visual content was fabricated." });
|
|
350870
|
+
const task = typeof latest?.content === "string" ? latest.content : "";
|
|
350871
|
+
const evidence = ocr.map((item) => `Image ${item.index} (OCR confidence ${item.confidence.toFixed(1)}):
|
|
350872
|
+
${item.text}`).join("\n\n");
|
|
350873
|
+
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.
|
|
350874
|
+
User task:
|
|
350875
|
+
${task.slice(0, 12e3)}
|
|
350876
|
+
OCR evidence:
|
|
350877
|
+
${evidence}`;
|
|
350878
|
+
let corrected = "";
|
|
350879
|
+
try {
|
|
350880
|
+
const provider = this.engineModel();
|
|
350881
|
+
if (provider) corrected = String(await provider.chat(this.activeModelName(), [{ role: "user", content: prompt }], "You are a text-only OCR correction assistant. Be conservative and explicit about uncertainty.", 0.05, 3e3, signal) || "").trim();
|
|
350882
|
+
} catch {
|
|
350883
|
+
}
|
|
350884
|
+
return JSON.stringify({
|
|
350885
|
+
ok: !!(corrected || ocr.length),
|
|
350886
|
+
fallback: "mini_ocr_llm",
|
|
350887
|
+
approximate: true,
|
|
350888
|
+
warning: "\u89C6\u89C9\u8F93\u5165\u88AB\u62D2\u7EDD\uFF1B\u4EE5\u4E0B\u5185\u5BB9\u6765\u81EA\u672C\u5730 OCR\uFF0C\u5E76\u7ECF\u6587\u672C\u6A21\u578B\u4FDD\u5B88\u6821\u6B63\uFF0C\u53EF\u80FD\u4E0D\u5B8C\u6574\u3002",
|
|
350889
|
+
raw_ocr: ocr,
|
|
350890
|
+
corrected: corrected || ocr.map((item) => item.text).join("\n\n"),
|
|
350891
|
+
uncertainty: corrected ? "preserved" : "raw_ocr_only"
|
|
350892
|
+
}, null, 2);
|
|
350893
|
+
}
|
|
350823
350894
|
engineModel() {
|
|
350824
350895
|
if (this.forcedProvider) {
|
|
350825
350896
|
const active = this.activeDeployment();
|
|
@@ -351202,8 +351273,10 @@ ${persisted.map((guide, index) => `Guide ${index + 1}: ${guide.text}`).join("\n"
|
|
|
351202
351273
|
}
|
|
351203
351274
|
const selectedModel = this.activeModelConfig();
|
|
351204
351275
|
if (images.length && !selectedModel?.vision) {
|
|
351205
|
-
|
|
351206
|
-
|
|
351276
|
+
const hasSameProviderVision = selectedModel && this.config.allModels().some(
|
|
351277
|
+
(model) => model.enabled !== false && model.provider_id === selectedModel.provider_id && model.name !== selectedModel.name && !!model.vision
|
|
351278
|
+
);
|
|
351279
|
+
if (hasSameProviderVision) this.switchToFallbackModel("vision input not supported by the selected model");
|
|
351207
351280
|
}
|
|
351208
351281
|
const now2 = this.nowLabel();
|
|
351209
351282
|
const visibleUserInput = inputEnvelope?.visibleUserInput === void 0 ? text : String(inputEnvelope.visibleUserInput || "");
|
package/dist/core/agent.d.ts
CHANGED
|
@@ -990,6 +990,13 @@ export declare class Agent {
|
|
|
990
990
|
isModelValidationRunning(): boolean;
|
|
991
991
|
modelValidationStatus(): ModelValidationProgress;
|
|
992
992
|
private runModelValidation;
|
|
993
|
+
/**
|
|
994
|
+
* Final visual safety net: OCR each submitted image and ask a text-only
|
|
995
|
+
* request to conservatively repair the OCR. This is intentionally callable
|
|
996
|
+
* only after a visual-input refusal and after same-provider vision routing
|
|
997
|
+
* has been exhausted; the original image is never sent again.
|
|
998
|
+
*/
|
|
999
|
+
finalVisualFallback(errorText: string, signal?: AbortSignal): Promise<string | null>;
|
|
993
1000
|
engineModel(): LLMProvider | null;
|
|
994
1001
|
/**
|
|
995
1002
|
* dev-0.4.3 模型原生思考强度档位映射表(模型名 → thinking_tier_map)。
|
package/dist/core/agent.js
CHANGED
|
@@ -6913,6 +6913,62 @@ class Agent {
|
|
|
6913
6913
|
};
|
|
6914
6914
|
return results;
|
|
6915
6915
|
}
|
|
6916
|
+
/**
|
|
6917
|
+
* Final visual safety net: OCR each submitted image and ask a text-only
|
|
6918
|
+
* request to conservatively repair the OCR. This is intentionally callable
|
|
6919
|
+
* only after a visual-input refusal and after same-provider vision routing
|
|
6920
|
+
* has been exhausted; the original image is never sent again.
|
|
6921
|
+
*/
|
|
6922
|
+
async finalVisualFallback(errorText, signal) {
|
|
6923
|
+
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 || '')))
|
|
6924
|
+
return null;
|
|
6925
|
+
const current = this.activeModelConfig();
|
|
6926
|
+
if (!current)
|
|
6927
|
+
return null;
|
|
6928
|
+
const alternateVision = this.config.allModels().some(model => model.enabled !== false && model.provider_id === current.provider_id &&
|
|
6929
|
+
model.name !== current.name && !!model.vision && !!model.api_key && !!model.provider_url &&
|
|
6930
|
+
!['unavailable', 'auth_error', 'invalid_config'].includes(String(model.evaluation?.status || model.validation?.status || '').toLowerCase()));
|
|
6931
|
+
if (alternateVision)
|
|
6932
|
+
return null;
|
|
6933
|
+
const latest = [...this.history].reverse().find(item => item?.role === 'user');
|
|
6934
|
+
const parts = latest?.content && Array.isArray(latest.content) ? latest.content : [];
|
|
6935
|
+
const images = parts.map(part => {
|
|
6936
|
+
const image = part.image_url;
|
|
6937
|
+
return image && typeof image === 'object' ? String(image.url || '') : '';
|
|
6938
|
+
}).filter(value => /^data:image\/(?:png|jpeg);base64,/i.test(value)).slice(0, 4);
|
|
6939
|
+
if (!images.length)
|
|
6940
|
+
return null;
|
|
6941
|
+
const ocr = [];
|
|
6942
|
+
for (const [index, image] of images.entries()) {
|
|
6943
|
+
try {
|
|
6944
|
+
const result = await this.tools.finalVisualFallbackOcr(image, signal);
|
|
6945
|
+
if (result.ok && result.text.trim())
|
|
6946
|
+
ocr.push({ index: index + 1, text: result.text.slice(0, 50_000), confidence: result.confidence });
|
|
6947
|
+
}
|
|
6948
|
+
catch { }
|
|
6949
|
+
}
|
|
6950
|
+
if (!ocr.length)
|
|
6951
|
+
return JSON.stringify({ ok: false, fallback: 'mini_ocr_llm', error: 'Local OCR returned no readable text; no visual content was fabricated.' });
|
|
6952
|
+
const task = typeof latest?.content === 'string' ? latest.content : '';
|
|
6953
|
+
const evidence = ocr.map(item => `Image ${item.index} (OCR confidence ${item.confidence.toFixed(1)}):\n${item.text}`).join('\n\n');
|
|
6954
|
+
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}`;
|
|
6955
|
+
let corrected = '';
|
|
6956
|
+
try {
|
|
6957
|
+
const provider = this.engineModel();
|
|
6958
|
+
if (provider)
|
|
6959
|
+
corrected = String(await provider.chat(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();
|
|
6960
|
+
}
|
|
6961
|
+
catch { }
|
|
6962
|
+
return JSON.stringify({
|
|
6963
|
+
ok: !!(corrected || ocr.length),
|
|
6964
|
+
fallback: 'mini_ocr_llm',
|
|
6965
|
+
approximate: true,
|
|
6966
|
+
warning: '视觉输入被拒绝;以下内容来自本地 OCR,并经文本模型保守校正,可能不完整。',
|
|
6967
|
+
raw_ocr: ocr,
|
|
6968
|
+
corrected: corrected || ocr.map(item => item.text).join('\n\n'),
|
|
6969
|
+
uncertainty: corrected ? 'preserved' : 'raw_ocr_only',
|
|
6970
|
+
}, null, 2);
|
|
6971
|
+
}
|
|
6916
6972
|
engineModel() {
|
|
6917
6973
|
if (this.forcedProvider) {
|
|
6918
6974
|
const active = this.activeDeployment();
|
|
@@ -7353,8 +7409,13 @@ class Agent {
|
|
|
7353
7409
|
}
|
|
7354
7410
|
const selectedModel = this.activeModelConfig();
|
|
7355
7411
|
if (images.length && !selectedModel?.vision) {
|
|
7356
|
-
|
|
7357
|
-
|
|
7412
|
+
// Give the normal route planner first chance to select another
|
|
7413
|
+
// same-provider vision deployment. If none is available, the kernel
|
|
7414
|
+
// preflight invokes the final mini-OCR + text-only correction path.
|
|
7415
|
+
const hasSameProviderVision = selectedModel && this.config.allModels().some(model => model.enabled !== false && model.provider_id === selectedModel.provider_id &&
|
|
7416
|
+
model.name !== selectedModel.name && !!model.vision);
|
|
7417
|
+
if (hasSameProviderVision)
|
|
7418
|
+
this.switchToFallbackModel('vision input not supported by the selected model');
|
|
7358
7419
|
}
|
|
7359
7420
|
const now = this.nowLabel();
|
|
7360
7421
|
const visibleUserInput = inputEnvelope?.visibleUserInput === undefined
|
|
@@ -429,7 +429,16 @@ async function runAgentKernel(agent) {
|
|
|
429
429
|
try {
|
|
430
430
|
const linkedPlanRevisionBeforeRun = agent.getLinkedPlan().revision;
|
|
431
431
|
const modelBeforeKernelRun = agent.model;
|
|
432
|
-
|
|
432
|
+
const preflightVisualFallback = !agent.activeModelConfig()?.vision
|
|
433
|
+
? await agent.finalVisualFallback('vision input not supported by the selected model', processSignal)
|
|
434
|
+
: null;
|
|
435
|
+
let lastTurn = preflightVisualFallback
|
|
436
|
+
? { text: preflightVisualFallback, stopReason: 'stop', errorMessage: '' }
|
|
437
|
+
: await runWithCompressionResume([], false);
|
|
438
|
+
if (preflightVisualFallback) {
|
|
439
|
+
tokens.push({ type: 'text', text: preflightVisualFallback });
|
|
440
|
+
agent.recordWorkStatus('Final visual fallback used: local mini OCR plus conservative text correction.');
|
|
441
|
+
}
|
|
433
442
|
if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some(t => t.text?.includes('[Model fallback]'))) {
|
|
434
443
|
tokens.unshift({ type: 'text', text: `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.` });
|
|
435
444
|
}
|
|
@@ -460,6 +469,14 @@ async function runAgentKernel(agent) {
|
|
|
460
469
|
await agent.waitForPlannedRouteRetry();
|
|
461
470
|
lastTurn = await runWithCompressionResume([], false);
|
|
462
471
|
}
|
|
472
|
+
if (kernelTurnFailed(agent, lastTurn)) {
|
|
473
|
+
const visualFallback = await agent.finalVisualFallback(lastTurn.errorMessage || lastTurn.text, processSignal);
|
|
474
|
+
if (visualFallback) {
|
|
475
|
+
tokens.push({ type: 'text', text: visualFallback });
|
|
476
|
+
agent.recordWorkStatus('Final visual fallback used: local mini OCR plus conservative text correction.');
|
|
477
|
+
lastTurn = { ...lastTurn, text: visualFallback, errorMessage: '', stopReason: 'stop' };
|
|
478
|
+
}
|
|
479
|
+
}
|
|
463
480
|
if (kernelTurnFailed(agent, lastTurn)) {
|
|
464
481
|
throw new ProviderRunError(normalizePublicProviderError(lastTurn.errorMessage || lastTurn.text, [agent.activeModelConfig()?.api_key]));
|
|
465
482
|
}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ConfigManager } from '../core/config';
|
|
|
2
2
|
import { NewmarkToolDefinition, NewmarkToolResult } from '../core/compat';
|
|
3
3
|
import { SshManager } from '../core/ssh';
|
|
4
4
|
import { WorkspaceManager } from '../core/workspace';
|
|
5
|
+
import { LocalOcrResult } from '../core/localOcr';
|
|
5
6
|
export interface ToolExecutionContext {
|
|
6
7
|
mode?: string;
|
|
7
8
|
workspacePath?: string;
|
|
@@ -30,6 +31,8 @@ export declare class ToolExecutor {
|
|
|
30
31
|
private hostProfile;
|
|
31
32
|
constructor(root: string, config: ConfigManager, ssh?: SshManager | undefined, workspace?: WorkspaceManager | undefined);
|
|
32
33
|
webSearch(query: string): Promise<string>;
|
|
34
|
+
/** OCR entry point for the runtime's final visual fallback. */
|
|
35
|
+
finalVisualFallbackOcr(dataUrl: string, signal?: AbortSignal): Promise<LocalOcrResult>;
|
|
33
36
|
setHostProfile(profile: ToolHostProfile): void;
|
|
34
37
|
definitions(mode?: string): unknown[];
|
|
35
38
|
canonicalDefinitions(mode?: string): NewmarkToolDefinition[];
|
package/dist/tools/index.js
CHANGED
|
@@ -234,6 +234,10 @@ class ToolExecutor {
|
|
|
234
234
|
async webSearch(query) {
|
|
235
235
|
return this.wsearch(query);
|
|
236
236
|
}
|
|
237
|
+
/** OCR entry point for the runtime's final visual fallback. */
|
|
238
|
+
async finalVisualFallbackOcr(dataUrl, signal) {
|
|
239
|
+
return await this.localOcr.recognizeDataUrl(dataUrl, signal, 'sparse-ui');
|
|
240
|
+
}
|
|
237
241
|
setHostProfile(profile) {
|
|
238
242
|
this.hostProfile = { ...profile };
|
|
239
243
|
}
|
|
@@ -336937,6 +336937,10 @@ var ToolExecutor = class {
|
|
|
336937
336937
|
async webSearch(query) {
|
|
336938
336938
|
return this.wsearch(query);
|
|
336939
336939
|
}
|
|
336940
|
+
/** OCR entry point for the runtime's final visual fallback. */
|
|
336941
|
+
async finalVisualFallbackOcr(dataUrl, signal) {
|
|
336942
|
+
return await this.localOcr.recognizeDataUrl(dataUrl, signal, "sparse-ui");
|
|
336943
|
+
}
|
|
336940
336944
|
setHostProfile(profile) {
|
|
336941
336945
|
this.hostProfile = { ...profile };
|
|
336942
336946
|
}
|
|
@@ -340850,7 +340854,12 @@ async function runAgentKernel(agent) {
|
|
|
340850
340854
|
try {
|
|
340851
340855
|
const linkedPlanRevisionBeforeRun = agent.getLinkedPlan().revision;
|
|
340852
340856
|
const modelBeforeKernelRun = agent.model;
|
|
340853
|
-
|
|
340857
|
+
const preflightVisualFallback = !agent.activeModelConfig()?.vision ? await agent.finalVisualFallback("vision input not supported by the selected model", processSignal) : null;
|
|
340858
|
+
let lastTurn = preflightVisualFallback ? { text: preflightVisualFallback, stopReason: "stop", errorMessage: "" } : await runWithCompressionResume([], false);
|
|
340859
|
+
if (preflightVisualFallback) {
|
|
340860
|
+
tokens.push({ type: "text", text: preflightVisualFallback });
|
|
340861
|
+
agent.recordWorkStatus("Final visual fallback used: local mini OCR plus conservative text correction.");
|
|
340862
|
+
}
|
|
340854
340863
|
if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some((t3) => t3.text?.includes("[Model fallback]"))) {
|
|
340855
340864
|
tokens.unshift({ type: "text", text: `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.` });
|
|
340856
340865
|
}
|
|
@@ -340880,6 +340889,14 @@ async function runAgentKernel(agent) {
|
|
|
340880
340889
|
await agent.waitForPlannedRouteRetry();
|
|
340881
340890
|
lastTurn = await runWithCompressionResume([], false);
|
|
340882
340891
|
}
|
|
340892
|
+
if (kernelTurnFailed(agent, lastTurn)) {
|
|
340893
|
+
const visualFallback = await agent.finalVisualFallback(lastTurn.errorMessage || lastTurn.text, processSignal);
|
|
340894
|
+
if (visualFallback) {
|
|
340895
|
+
tokens.push({ type: "text", text: visualFallback });
|
|
340896
|
+
agent.recordWorkStatus("Final visual fallback used: local mini OCR plus conservative text correction.");
|
|
340897
|
+
lastTurn = { ...lastTurn, text: visualFallback, errorMessage: "", stopReason: "stop" };
|
|
340898
|
+
}
|
|
340899
|
+
}
|
|
340883
340900
|
if (kernelTurnFailed(agent, lastTurn)) {
|
|
340884
340901
|
throw new ProviderRunError(normalizePublicProviderError(lastTurn.errorMessage || lastTurn.text, [agent.activeModelConfig()?.api_key]));
|
|
340885
340902
|
}
|
|
@@ -350824,6 +350841,60 @@ ${msg.content}
|
|
|
350824
350841
|
};
|
|
350825
350842
|
return results;
|
|
350826
350843
|
}
|
|
350844
|
+
/**
|
|
350845
|
+
* Final visual safety net: OCR each submitted image and ask a text-only
|
|
350846
|
+
* request to conservatively repair the OCR. This is intentionally callable
|
|
350847
|
+
* only after a visual-input refusal and after same-provider vision routing
|
|
350848
|
+
* has been exhausted; the original image is never sent again.
|
|
350849
|
+
*/
|
|
350850
|
+
async finalVisualFallback(errorText, signal) {
|
|
350851
|
+
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 || ""))) return null;
|
|
350852
|
+
const current = this.activeModelConfig();
|
|
350853
|
+
if (!current) return null;
|
|
350854
|
+
const alternateVision = this.config.allModels().some(
|
|
350855
|
+
(model) => model.enabled !== false && model.provider_id === current.provider_id && model.name !== current.name && !!model.vision && !!model.api_key && !!model.provider_url && !["unavailable", "auth_error", "invalid_config"].includes(String(model.evaluation?.status || model.validation?.status || "").toLowerCase())
|
|
350856
|
+
);
|
|
350857
|
+
if (alternateVision) return null;
|
|
350858
|
+
const latest = [...this.history].reverse().find((item) => item?.role === "user");
|
|
350859
|
+
const parts = latest?.content && Array.isArray(latest.content) ? latest.content : [];
|
|
350860
|
+
const images = parts.map((part) => {
|
|
350861
|
+
const image = part.image_url;
|
|
350862
|
+
return image && typeof image === "object" ? String(image.url || "") : "";
|
|
350863
|
+
}).filter((value) => /^data:image\/(?:png|jpeg);base64,/i.test(value)).slice(0, 4);
|
|
350864
|
+
if (!images.length) return null;
|
|
350865
|
+
const ocr = [];
|
|
350866
|
+
for (const [index, image] of images.entries()) {
|
|
350867
|
+
try {
|
|
350868
|
+
const result = await this.tools.finalVisualFallbackOcr(image, signal);
|
|
350869
|
+
if (result.ok && result.text.trim()) ocr.push({ index: index + 1, text: result.text.slice(0, 5e4), confidence: result.confidence });
|
|
350870
|
+
} catch {
|
|
350871
|
+
}
|
|
350872
|
+
}
|
|
350873
|
+
if (!ocr.length) return JSON.stringify({ ok: false, fallback: "mini_ocr_llm", error: "Local OCR returned no readable text; no visual content was fabricated." });
|
|
350874
|
+
const task = typeof latest?.content === "string" ? latest.content : "";
|
|
350875
|
+
const evidence = ocr.map((item) => `Image ${item.index} (OCR confidence ${item.confidence.toFixed(1)}):
|
|
350876
|
+
${item.text}`).join("\n\n");
|
|
350877
|
+
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.
|
|
350878
|
+
User task:
|
|
350879
|
+
${task.slice(0, 12e3)}
|
|
350880
|
+
OCR evidence:
|
|
350881
|
+
${evidence}`;
|
|
350882
|
+
let corrected = "";
|
|
350883
|
+
try {
|
|
350884
|
+
const provider = this.engineModel();
|
|
350885
|
+
if (provider) corrected = String(await provider.chat(this.activeModelName(), [{ role: "user", content: prompt }], "You are a text-only OCR correction assistant. Be conservative and explicit about uncertainty.", 0.05, 3e3, signal) || "").trim();
|
|
350886
|
+
} catch {
|
|
350887
|
+
}
|
|
350888
|
+
return JSON.stringify({
|
|
350889
|
+
ok: !!(corrected || ocr.length),
|
|
350890
|
+
fallback: "mini_ocr_llm",
|
|
350891
|
+
approximate: true,
|
|
350892
|
+
warning: "\u89C6\u89C9\u8F93\u5165\u88AB\u62D2\u7EDD\uFF1B\u4EE5\u4E0B\u5185\u5BB9\u6765\u81EA\u672C\u5730 OCR\uFF0C\u5E76\u7ECF\u6587\u672C\u6A21\u578B\u4FDD\u5B88\u6821\u6B63\uFF0C\u53EF\u80FD\u4E0D\u5B8C\u6574\u3002",
|
|
350893
|
+
raw_ocr: ocr,
|
|
350894
|
+
corrected: corrected || ocr.map((item) => item.text).join("\n\n"),
|
|
350895
|
+
uncertainty: corrected ? "preserved" : "raw_ocr_only"
|
|
350896
|
+
}, null, 2);
|
|
350897
|
+
}
|
|
350827
350898
|
engineModel() {
|
|
350828
350899
|
if (this.forcedProvider) {
|
|
350829
350900
|
const active = this.activeDeployment();
|
|
@@ -351206,8 +351277,10 @@ ${persisted.map((guide, index) => `Guide ${index + 1}: ${guide.text}`).join("\n"
|
|
|
351206
351277
|
}
|
|
351207
351278
|
const selectedModel = this.activeModelConfig();
|
|
351208
351279
|
if (images.length && !selectedModel?.vision) {
|
|
351209
|
-
|
|
351210
|
-
|
|
351280
|
+
const hasSameProviderVision = selectedModel && this.config.allModels().some(
|
|
351281
|
+
(model) => model.enabled !== false && model.provider_id === selectedModel.provider_id && model.name !== selectedModel.name && !!model.vision
|
|
351282
|
+
);
|
|
351283
|
+
if (hasSameProviderVision) this.switchToFallbackModel("vision input not supported by the selected model");
|
|
351211
351284
|
}
|
|
351212
351285
|
const now2 = this.nowLabel();
|
|
351213
351286
|
const visibleUserInput = inputEnvelope?.visibleUserInput === void 0 ? text : String(inputEnvelope.visibleUserInput || "");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newmark-agent",
|
|
3
3
|
"productName": "Newmark Agent",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.4",
|
|
5
5
|
"description": "Newmark Agent — Portable AI coding agent with rich GUI and CLI (TypeScript)",
|
|
6
6
|
"homepage": "https://github.com/positer/Newmark-Agent",
|
|
7
7
|
"repository": {
|