@namzu/sdk 32.0.0 → 33.1.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/CHANGELOG.md +157 -0
- package/dist/authorization/gate.d.ts +1 -1
- package/dist/authorization/gate.js +1 -1
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +13 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +3 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/connector/index.d.ts +1 -1
- package/dist/connector/index.d.ts.map +1 -1
- package/dist/connector/index.js +1 -1
- package/dist/connector/index.js.map +1 -1
- package/dist/connector/mcp/adapter.d.ts.map +1 -1
- package/dist/connector/mcp/adapter.js +71 -16
- package/dist/connector/mcp/adapter.js.map +1 -1
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +3 -2
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/connector/mcp/http-redirect.d.ts +17 -0
- package/dist/connector/mcp/http-redirect.d.ts.map +1 -0
- package/dist/connector/mcp/http-redirect.js +31 -0
- package/dist/connector/mcp/http-redirect.js.map +1 -0
- package/dist/connector/mcp/http-sse.d.ts.map +1 -1
- package/dist/connector/mcp/http-sse.js +5 -0
- package/dist/connector/mcp/http-sse.js.map +1 -1
- package/dist/connector/mcp/image-admission.d.ts +16 -0
- package/dist/connector/mcp/image-admission.d.ts.map +1 -0
- package/dist/connector/mcp/image-admission.js +288 -0
- package/dist/connector/mcp/image-admission.js.map +1 -0
- package/dist/connector/mcp/stdio.d.ts +13 -0
- package/dist/connector/mcp/stdio.d.ts.map +1 -1
- package/dist/connector/mcp/stdio.js +66 -23
- package/dist/connector/mcp/stdio.js.map +1 -1
- package/dist/connector/mcp/streamable-http.d.ts.map +1 -1
- package/dist/connector/mcp/streamable-http.js +3 -0
- package/dist/connector/mcp/streamable-http.js.map +1 -1
- package/dist/constants/tools/index.js +1 -1
- package/dist/constants/tools/index.js.map +1 -1
- package/dist/execution/base.d.ts +13 -0
- package/dist/execution/base.d.ts.map +1 -1
- package/dist/execution/base.js +103 -8
- package/dist/execution/base.js.map +1 -1
- package/dist/execution/errors.d.ts +12 -0
- package/dist/execution/errors.d.ts.map +1 -0
- package/dist/execution/errors.js +21 -0
- package/dist/execution/errors.js.map +1 -0
- package/dist/execution/factory.d.ts.map +1 -1
- package/dist/execution/factory.js +1 -0
- package/dist/execution/factory.js.map +1 -1
- package/dist/execution/hybrid.d.ts +5 -0
- package/dist/execution/hybrid.d.ts.map +1 -1
- package/dist/execution/hybrid.js +36 -16
- package/dist/execution/hybrid.js.map +1 -1
- package/dist/execution/index.d.ts +1 -0
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +1 -0
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/local.d.ts +13 -0
- package/dist/execution/local.d.ts.map +1 -1
- package/dist/execution/local.js +145 -19
- package/dist/execution/local.js.map +1 -1
- package/dist/execution/output.d.ts +27 -0
- package/dist/execution/output.d.ts.map +1 -0
- package/dist/execution/output.js +105 -0
- package/dist/execution/output.js.map +1 -0
- package/dist/execution/remote.d.ts +26 -2
- package/dist/execution/remote.d.ts.map +1 -1
- package/dist/execution/remote.js +86 -15
- package/dist/execution/remote.js.map +1 -1
- package/dist/plugin/lifecycle.d.ts.map +1 -1
- package/dist/plugin/lifecycle.js +9 -3
- package/dist/plugin/lifecycle.js.map +1 -1
- package/dist/process/kill-tree.d.ts +11 -13
- package/dist/process/kill-tree.d.ts.map +1 -1
- package/dist/process/kill-tree.js +11 -13
- package/dist/process/kill-tree.js.map +1 -1
- package/dist/provider/capabilities.d.ts +2 -0
- package/dist/provider/capabilities.d.ts.map +1 -1
- package/dist/provider/capabilities.js +6 -0
- package/dist/provider/capabilities.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +31 -0
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/idle-timeout.d.ts.map +1 -1
- package/dist/provider/idle-timeout.js +5 -0
- package/dist/provider/idle-timeout.js.map +1 -1
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +5 -0
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +2 -2
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +2 -2
- package/dist/public-runtime.js.map +1 -1
- package/dist/registry/tool/execute.d.ts +7 -1
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +201 -54
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/run/command-gate.d.ts.map +1 -1
- package/dist/run/command-gate.js +11 -0
- package/dist/run/command-gate.js.map +1 -1
- package/dist/run/workspace-fingerprint.d.ts +4 -4
- package/dist/run/workspace-fingerprint.d.ts.map +1 -1
- package/dist/run/workspace-fingerprint.js +6 -4
- package/dist/run/workspace-fingerprint.js.map +1 -1
- package/dist/runtime/bidi/index.d.ts +1 -1
- package/dist/runtime/bidi/index.d.ts.map +1 -1
- package/dist/runtime/bidi/index.js +1 -1
- package/dist/runtime/bidi/index.js.map +1 -1
- package/dist/runtime/bidi/session.d.ts +19 -0
- package/dist/runtime/bidi/session.d.ts.map +1 -1
- package/dist/runtime/bidi/session.js +150 -26
- package/dist/runtime/bidi/session.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +43 -1
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +626 -161
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +3 -1
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +48 -4
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +10 -0
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +67 -0
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +5 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +84 -22
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
- package/dist/runtime/query/request-rich-content.js +33 -13
- package/dist/runtime/query/request-rich-content.js.map +1 -1
- package/dist/runtime/query/result.d.ts.map +1 -1
- package/dist/runtime/query/result.js +25 -22
- package/dist/runtime/query/result.js.map +1 -1
- package/dist/runtime/query/resume-pending.d.ts +5 -1
- package/dist/runtime/query/resume-pending.d.ts.map +1 -1
- package/dist/runtime/query/resume-pending.js +66 -1
- package/dist/runtime/query/resume-pending.js.map +1 -1
- package/dist/runtime/query/tool-output-budget.d.ts.map +1 -1
- package/dist/runtime/query/tool-output-budget.js +45 -16
- package/dist/runtime/query/tool-output-budget.js.map +1 -1
- package/dist/runtime/query/tooling.d.ts +6 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +4 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/sandbox/provider/local.d.ts.map +1 -1
- package/dist/sandbox/provider/local.js +193 -41
- package/dist/sandbox/provider/local.js.map +1 -1
- package/dist/scheduler/delegating.d.ts +2 -1
- package/dist/scheduler/delegating.d.ts.map +1 -1
- package/dist/scheduler/delegating.js +6 -5
- package/dist/scheduler/delegating.js.map +1 -1
- package/dist/scheduler/local.d.ts +2 -1
- package/dist/scheduler/local.d.ts.map +1 -1
- package/dist/scheduler/local.js +2 -2
- package/dist/scheduler/local.js.map +1 -1
- package/dist/session/workspace/driver.d.ts +12 -4
- package/dist/session/workspace/driver.d.ts.map +1 -1
- package/dist/session/workspace/git-worktree.d.ts +17 -3
- package/dist/session/workspace/git-worktree.d.ts.map +1 -1
- package/dist/session/workspace/git-worktree.js +153 -34
- package/dist/session/workspace/git-worktree.js.map +1 -1
- package/dist/skills/registry.d.ts +15 -0
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js +33 -0
- package/dist/skills/registry.js.map +1 -1
- package/dist/tools/builtins/computer-use.d.ts.map +1 -1
- package/dist/tools/builtins/computer-use.js +76 -2
- package/dist/tools/builtins/computer-use.js.map +1 -1
- package/dist/tools/builtins/run-code.d.ts.map +1 -1
- package/dist/tools/builtins/run-code.js +9 -11
- package/dist/tools/builtins/run-code.js.map +1 -1
- package/dist/tools/builtins/skill.d.ts +2 -1
- package/dist/tools/builtins/skill.d.ts.map +1 -1
- package/dist/tools/builtins/skill.js +250 -6
- package/dist/tools/builtins/skill.js.map +1 -1
- package/dist/tools/coordinator/agent.d.ts.map +1 -1
- package/dist/tools/coordinator/agent.js +100 -30
- package/dist/tools/coordinator/agent.js.map +1 -1
- package/dist/types/agent/manager.d.ts +1 -1
- package/dist/types/agent/manager.d.ts.map +1 -1
- package/dist/types/agent/scheduler.d.ts +10 -1
- package/dist/types/agent/scheduler.d.ts.map +1 -1
- package/dist/types/authorization/index.d.ts +12 -12
- package/dist/types/bidi/index.d.ts +19 -2
- package/dist/types/bidi/index.d.ts.map +1 -1
- package/dist/types/computer-use/index.d.ts +18 -0
- package/dist/types/computer-use/index.d.ts.map +1 -1
- package/dist/types/connector/execution-context.d.ts +3 -1
- package/dist/types/connector/execution-context.d.ts.map +1 -1
- package/dist/types/errors/catalog.d.ts.map +1 -1
- package/dist/types/errors/catalog.js +13 -6
- package/dist/types/errors/catalog.js.map +1 -1
- package/dist/types/errors/index.d.ts.map +1 -1
- package/dist/types/errors/index.js +21 -8
- package/dist/types/errors/index.js.map +1 -1
- package/dist/types/execution/index.d.ts +43 -1
- package/dist/types/execution/index.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +11 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/message/index.d.ts +1 -1
- package/dist/types/message/index.d.ts.map +1 -1
- package/dist/types/message/index.js +2 -1
- package/dist/types/message/index.js.map +1 -1
- package/dist/types/plugin/index.d.ts +14 -1
- package/dist/types/plugin/index.d.ts.map +1 -1
- package/dist/types/plugin/index.js +1 -0
- package/dist/types/plugin/index.js.map +1 -1
- package/dist/types/provider/config.d.ts +9 -0
- package/dist/types/provider/config.d.ts.map +1 -1
- package/dist/types/provider/interface.d.ts +14 -0
- package/dist/types/provider/interface.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +17 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/sandbox/index.d.ts +15 -10
- package/dist/types/sandbox/index.d.ts.map +1 -1
- package/dist/types/sandbox/index.js.map +1 -1
- package/dist/types/tool/index.d.ts +77 -6
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/types/tool/presentation.d.ts +6 -2
- package/dist/types/tool/presentation.d.ts.map +1 -1
- package/dist/types/workspace/ref.d.ts +5 -0
- package/dist/types/workspace/ref.d.ts.map +1 -1
- package/dist/utils/abort.d.ts +8 -0
- package/dist/utils/abort.d.ts.map +1 -1
- package/dist/utils/abort.js +16 -0
- package/dist/utils/abort.js.map +1 -1
- package/package.json +4 -4
- package/src/authorization/gate.ts +1 -1
- package/src/bridge/a2a/mapper.ts +23 -4
- package/src/bridge/sse/mapper.ts +3 -0
- package/src/connector/index.ts +2 -0
- package/src/connector/mcp/adapter.ts +82 -21
- package/src/connector/mcp/client.ts +2 -1
- package/src/connector/mcp/http-redirect.ts +32 -0
- package/src/connector/mcp/http-sse.ts +5 -0
- package/src/connector/mcp/image-admission.ts +272 -0
- package/src/connector/mcp/stdio.ts +63 -21
- package/src/connector/mcp/streamable-http.ts +3 -0
- package/src/constants/tools/index.ts +1 -1
- package/src/execution/base.ts +124 -7
- package/src/execution/errors.ts +24 -0
- package/src/execution/factory.ts +1 -0
- package/src/execution/hybrid.ts +47 -16
- package/src/execution/index.ts +1 -0
- package/src/execution/local.ts +164 -19
- package/src/execution/output.ts +104 -0
- package/src/execution/remote.ts +122 -17
- package/src/plugin/lifecycle.ts +9 -3
- package/src/process/kill-tree.ts +11 -13
- package/src/provider/capabilities.ts +10 -0
- package/src/provider/fallback.ts +39 -0
- package/src/provider/idle-timeout.ts +8 -0
- package/src/provider/retry.ts +8 -0
- package/src/public-runtime.ts +7 -1
- package/src/registry/tool/execute.ts +230 -64
- package/src/run/command-gate.ts +12 -0
- package/src/run/workspace-fingerprint.ts +5 -4
- package/src/runtime/bidi/index.ts +1 -1
- package/src/runtime/bidi/session.ts +177 -28
- package/src/runtime/query/executor.ts +769 -163
- package/src/runtime/query/index.ts +58 -7
- package/src/runtime/query/iteration/index.ts +72 -0
- package/src/runtime/query/iteration/phases/context.ts +5 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +87 -23
- package/src/runtime/query/request-rich-content.ts +39 -14
- package/src/runtime/query/result.ts +25 -23
- package/src/runtime/query/resume-pending.ts +83 -1
- package/src/runtime/query/tool-output-budget.ts +52 -17
- package/src/runtime/query/tooling.ts +10 -0
- package/src/sandbox/provider/local.ts +213 -45
- package/src/scheduler/delegating.ts +6 -5
- package/src/scheduler/local.ts +3 -2
- package/src/session/workspace/driver.ts +12 -4
- package/src/session/workspace/git-worktree.ts +173 -34
- package/src/skills/registry.ts +47 -0
- package/src/tools/builtins/computer-use.ts +86 -2
- package/src/tools/builtins/run-code.ts +9 -11
- package/src/tools/builtins/skill.ts +325 -7
- package/src/tools/coordinator/agent.ts +108 -33
- package/src/types/agent/manager.ts +1 -1
- package/src/types/agent/scheduler.ts +10 -1
- package/src/types/bidi/index.ts +19 -2
- package/src/types/computer-use/index.ts +19 -0
- package/src/types/connector/execution-context.ts +3 -0
- package/src/types/errors/catalog.ts +14 -6
- package/src/types/errors/index.ts +22 -8
- package/src/types/execution/index.ts +46 -1
- package/src/types/hitl/index.ts +11 -0
- package/src/types/message/index.ts +3 -2
- package/src/types/plugin/index.ts +15 -0
- package/src/types/provider/config.ts +9 -0
- package/src/types/provider/interface.ts +18 -0
- package/src/types/run/events.ts +17 -3
- package/src/types/sandbox/index.ts +15 -10
- package/src/types/tool/index.ts +85 -6
- package/src/types/tool/presentation.ts +10 -3
- package/src/types/workspace/ref.ts +5 -0
- package/src/utils/abort.ts +18 -0
package/dist/utils/abort.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
1
2
|
import { toErrorMessage } from './error.js';
|
|
2
3
|
/**
|
|
3
4
|
* The words a caller attached to a stop, or nothing when there were none.
|
|
@@ -44,6 +45,21 @@ export function abortReasonText(reason) {
|
|
|
44
45
|
const text = toErrorMessage(reason).trim();
|
|
45
46
|
return text.length > 0 ? text : undefined;
|
|
46
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Subscribe to caller cancellation without raising the Node 20.0 runtime floor.
|
|
50
|
+
*
|
|
51
|
+
* `addAbortListener` cannot be hidden by another listener calling
|
|
52
|
+
* `stopImmediatePropagation`. Both branches return an explicit disposer so a
|
|
53
|
+
* completed operation does not remain attached to a long-lived caller signal.
|
|
54
|
+
*/
|
|
55
|
+
export function subscribeToAbort(signal, listener) {
|
|
56
|
+
if (typeof EventEmitter.addAbortListener === 'function') {
|
|
57
|
+
const subscription = EventEmitter.addAbortListener(signal, listener);
|
|
58
|
+
return () => subscription[Symbol.dispose]();
|
|
59
|
+
}
|
|
60
|
+
signal.addEventListener('abort', listener, { once: true });
|
|
61
|
+
return () => signal.removeEventListener('abort', listener);
|
|
62
|
+
}
|
|
47
63
|
export function createChildAbortController(parent) {
|
|
48
64
|
const child = new AbortController();
|
|
49
65
|
if (parent.signal.aborted) {
|
package/dist/utils/abort.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abort.js","sourceRoot":"","sources":["../../src/utils/abort.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,eAAe,CAAC,MAAe;IAC9C,IAAI,CAAC,CAAC,MAAM,YAAY,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAChD,uEAAuE;IACvE,wEAAwE;IACxE,oEAAoE;IACpE,mEAAmE;IACnE,EAAE;IACF,yEAAyE;IACzE,qEAAqE;IACrE,sEAAsE;IACtE,qCAAqC;IACrC,IACC,MAAM,CAAC,IAAI,KAAK,YAAY;QAC5B,MAAM,CAAC,IAAI,KAAK,cAAc;QAC9B,MAAM,CAAC,IAAI,KAAK,cAAc,EAC7B,CAAC;QACF,OAAO,SAAS,CAAA;IACjB,CAAC;IACD,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IAC1C,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;AAC1C,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,MAAuB;IACjE,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE,CAAA;IAEnC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAC3B,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACjC,OAAO,KAAK,CAAA;IACb,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,CAAA;IAEpC,MAAM,aAAa,GAAG,GAAS,EAAE;QAChC,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,CAAA;QAC7B,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAChC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAChC,CAAC;IACF,CAAC,CAAA;IAED,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IAEtE,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAC5B,OAAO,EACP,GAAG,EAAE;QACJ,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;IAC1D,CAAC,EACD,EAAE,IAAI,EAAE,IAAI,EAAE,CACd,CAAA;IAED,OAAO,KAAK,CAAA;AACb,CAAC"}
|
|
1
|
+
{"version":3,"file":"abort.js","sourceRoot":"","sources":["../../src/utils/abort.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,eAAe,CAAC,MAAe;IAC9C,IAAI,CAAC,CAAC,MAAM,YAAY,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAChD,uEAAuE;IACvE,wEAAwE;IACxE,oEAAoE;IACpE,mEAAmE;IACnE,EAAE;IACF,yEAAyE;IACzE,qEAAqE;IACrE,sEAAsE;IACtE,qCAAqC;IACrC,IACC,MAAM,CAAC,IAAI,KAAK,YAAY;QAC5B,MAAM,CAAC,IAAI,KAAK,cAAc;QAC9B,MAAM,CAAC,IAAI,KAAK,cAAc,EAC7B,CAAC;QACF,OAAO,SAAS,CAAA;IACjB,CAAC;IACD,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IAC1C,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;AAC1C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAmB,EAAE,QAAoB;IACzE,IAAI,OAAO,YAAY,CAAC,gBAAgB,KAAK,UAAU,EAAE,CAAC;QACzD,MAAM,YAAY,GAAG,YAAY,CAAC,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QACpE,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;IAC5C,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IAC1D,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;AAC3D,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,MAAuB;IACjE,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE,CAAA;IAEnC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAC3B,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACjC,OAAO,KAAK,CAAA;IACb,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,CAAA;IAEpC,MAAM,aAAa,GAAG,GAAS,EAAE;QAChC,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,CAAA;QAC7B,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAChC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAChC,CAAC;IACF,CAAC,CAAA;IAED,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IAEtE,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAC5B,OAAO,EACP,GAAG,EAAE;QACJ,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;IAC1D,CAAC,EACD,EAAE,IAAI,EAAE,IAAI,EAAE,CACd,CAAA;IAED,OAAO,KAAK,CAAA;AACb,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@namzu/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "33.1.0",
|
|
4
4
|
"description": "Open-source AI agent SDK with a built-in runtime. Nothing between you and your agents.",
|
|
5
5
|
"license": "FSL-1.1-MIT",
|
|
6
6
|
"engines": {
|
|
@@ -77,11 +77,11 @@
|
|
|
77
77
|
"lint": "biome check src/",
|
|
78
78
|
"lint:fix": "biome check --write src/",
|
|
79
79
|
"format": "biome format --write src/",
|
|
80
|
-
"test": "
|
|
81
|
-
"test:coverage": "
|
|
80
|
+
"test": "node ../../scripts/run-sdk-tests.mjs unit --passWithNoTests",
|
|
81
|
+
"test:coverage": "node ../../scripts/run-sdk-tests.mjs unit --passWithNoTests --coverage",
|
|
82
82
|
"typecheck": "tsc --noEmit",
|
|
83
83
|
"knip": "knip",
|
|
84
84
|
"knip:production": "knip --production",
|
|
85
|
-
"test:proc": "tsc --build &&
|
|
85
|
+
"test:proc": "tsc --build && node ../../scripts/run-sdk-tests.mjs proc"
|
|
86
86
|
}
|
|
87
87
|
}
|
|
@@ -21,7 +21,7 @@ export interface ToolCallContext {
|
|
|
21
21
|
* What the rule actually said, in words a model can act on.
|
|
22
22
|
*
|
|
23
23
|
* This used to be the rule TYPE and nothing else, so a denial reached the
|
|
24
|
-
* model as "Blocked by the
|
|
24
|
+
* model as "Blocked by the authorization gate: Matched rule: deny_by_name" —
|
|
25
25
|
* naming the kind of rule and nothing about it. Not which tool, not which
|
|
26
26
|
* pattern, not whether a different input would fare better.
|
|
27
27
|
*
|
package/src/bridge/a2a/mapper.ts
CHANGED
|
@@ -206,10 +206,29 @@ const MAPPING: {
|
|
|
206
206
|
}),
|
|
207
207
|
|
|
208
208
|
run_paused: (e, ctx) =>
|
|
209
|
-
statusEvent(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
statusEvent(
|
|
210
|
+
e.runId,
|
|
211
|
+
'input-required',
|
|
212
|
+
false,
|
|
213
|
+
ctx,
|
|
214
|
+
{
|
|
215
|
+
role: 'agent',
|
|
216
|
+
parts: [{ kind: 'text', text: `Run paused: ${e.reason}` }],
|
|
217
|
+
},
|
|
218
|
+
// A remote host has the same recovery decision as a local one. The
|
|
219
|
+
// checkpoint is the address of that recovery; the classification tells
|
|
220
|
+
// it whether and when a retry is justified.
|
|
221
|
+
{
|
|
222
|
+
checkpointId: e.checkpointId,
|
|
223
|
+
...(e.failure
|
|
224
|
+
? {
|
|
225
|
+
code: e.failure.code,
|
|
226
|
+
retryable: e.failure.retryable,
|
|
227
|
+
...(e.failure.details ? { details: e.failure.details } : {}),
|
|
228
|
+
}
|
|
229
|
+
: {}),
|
|
230
|
+
},
|
|
231
|
+
),
|
|
213
232
|
|
|
214
233
|
iteration_completed: null,
|
|
215
234
|
// Context management is kernel-internal bookkeeping; A2A peers model a
|
package/src/bridge/sse/mapper.ts
CHANGED
|
@@ -276,6 +276,9 @@ const MAPPING: {
|
|
|
276
276
|
run_id: runId,
|
|
277
277
|
checkpoint_id: e.checkpointId,
|
|
278
278
|
reason: e.reason,
|
|
279
|
+
...(e.failure ? { failure: e.failure } : {}),
|
|
280
|
+
...(e.providerError ? { provider_error: e.providerError } : {}),
|
|
281
|
+
...(e.explanation ? { explanation: e.explanation } : {}),
|
|
279
282
|
}),
|
|
280
283
|
},
|
|
281
284
|
|
package/src/connector/index.ts
CHANGED
|
@@ -24,9 +24,11 @@ export { WebhookConnector } from './builtins/webhook.js'
|
|
|
24
24
|
// thing that defines one.
|
|
25
25
|
export {
|
|
26
26
|
BaseExecutionContext,
|
|
27
|
+
CommandCancellationUnsupportedError,
|
|
27
28
|
ExecutionContextFactory,
|
|
28
29
|
HybridExecutionContext,
|
|
29
30
|
LocalExecutionContext,
|
|
31
|
+
RemoteExecutionBusyError,
|
|
30
32
|
RemoteExecutionContext,
|
|
31
33
|
} from '../execution/index.js'
|
|
32
34
|
export type {
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { zodToJsonSchema } from 'zod-to-json-schema'
|
|
3
|
-
import { wrapUntrusted } from '../../tools/untrusted-envelope.js'
|
|
3
|
+
import { type UntrustedEnvelope, wrapUntrusted } from '../../tools/untrusted-envelope.js'
|
|
4
4
|
import type {
|
|
5
5
|
MCPJsonSchema,
|
|
6
6
|
MCPToolDefinition,
|
|
7
7
|
MCPToolResult,
|
|
8
8
|
} from '../../types/connector/index.js'
|
|
9
|
-
import type { ToolResultBlock } from '../../types/message/index.js'
|
|
9
|
+
import type { ToolResultBlock, ToolResultContent } from '../../types/message/index.js'
|
|
10
10
|
import type { ToolContext, ToolDefinition, ToolResult } from '../../types/tool/index.js'
|
|
11
11
|
import type { MCPClient } from './client.js'
|
|
12
|
+
import { MCPHttpRedirectError } from './http-redirect.js'
|
|
13
|
+
import { admitMcpImageBatch } from './image-admission.js'
|
|
12
14
|
import { inlineSchemaRefs } from './schema-refs.js'
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -376,7 +378,9 @@ function positionalTypeName(member: unknown): string {
|
|
|
376
378
|
* property collision the later member wins, matching the reading order.
|
|
377
379
|
*/
|
|
378
380
|
function mergeAllOf(members: readonly unknown[], parent: Record<string, unknown>): JsonRecord {
|
|
379
|
-
const properties: JsonRecord = {
|
|
381
|
+
const properties: JsonRecord = {
|
|
382
|
+
...((parent.properties as JsonRecord | undefined) ?? {}),
|
|
383
|
+
}
|
|
380
384
|
const required = new Set((parent.required as string[] | undefined) ?? [])
|
|
381
385
|
let additionalProperties = parent.additionalProperties
|
|
382
386
|
|
|
@@ -439,7 +443,9 @@ export function mcpToolToToolDefinition(
|
|
|
439
443
|
// Carried as the server wrote it. It is shown to the model, never
|
|
440
444
|
// validated here, so rebuilding it would only lose fidelity.
|
|
441
445
|
...(tool.outputSchema
|
|
442
|
-
? {
|
|
446
|
+
? {
|
|
447
|
+
outputSchema: inlineSchemaRefs(tool.outputSchema as unknown as Record<string, unknown>),
|
|
448
|
+
}
|
|
443
449
|
: {}),
|
|
444
450
|
category: 'network',
|
|
445
451
|
permissions: ['network_access'],
|
|
@@ -453,10 +459,28 @@ export function mcpToolToToolDefinition(
|
|
|
453
459
|
provenance: { server: serverName, readOnlyHintTrusted },
|
|
454
460
|
|
|
455
461
|
async execute(input: unknown, context: ToolContext): Promise<ToolResult> {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
462
|
+
try {
|
|
463
|
+
const result = await client.callTool(tool.name, input as Record<string, unknown>, {
|
|
464
|
+
signal: context.abortSignal,
|
|
465
|
+
})
|
|
466
|
+
return frameServerResult(mcpToolResultToToolResult(result), serverName, tool.name)
|
|
467
|
+
} catch (error) {
|
|
468
|
+
if (error instanceof MCPHttpRedirectError && error.remoteOutcomeUnknown) {
|
|
469
|
+
return {
|
|
470
|
+
success: false,
|
|
471
|
+
output: '',
|
|
472
|
+
error: error.message,
|
|
473
|
+
data: {
|
|
474
|
+
code: 'mcp_tool_outcome_unknown',
|
|
475
|
+
server: serverName,
|
|
476
|
+
tool: tool.name,
|
|
477
|
+
outcome: 'unknown',
|
|
478
|
+
retrySafety: 'unsafe',
|
|
479
|
+
},
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
throw error
|
|
483
|
+
}
|
|
460
484
|
},
|
|
461
485
|
}
|
|
462
486
|
}
|
|
@@ -510,18 +534,31 @@ export function frameServerResult(
|
|
|
510
534
|
serverName: string,
|
|
511
535
|
toolName: string,
|
|
512
536
|
): ToolResult {
|
|
513
|
-
|
|
537
|
+
const envelope: UntrustedEnvelope = {
|
|
538
|
+
kind: 'connector-tool-result',
|
|
539
|
+
attributes: { server: serverName, tool: toolName },
|
|
540
|
+
provenance: 'This is output the named server returned, not this agent.',
|
|
541
|
+
}
|
|
542
|
+
const frameText = (text: string): string =>
|
|
543
|
+
text.length === 0 ? text : wrapUntrusted(envelope, text)
|
|
544
|
+
const frameContent = (content: ToolResultContent): ToolResultContent =>
|
|
545
|
+
typeof content === 'string'
|
|
546
|
+
? frameText(content)
|
|
547
|
+
: content.map((block) =>
|
|
548
|
+
block.type === 'text' ? { ...block, text: frameText(block.text) } : block,
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
const output = frameText(result.output)
|
|
552
|
+
const error = result.error === undefined ? undefined : frameText(result.error)
|
|
553
|
+
const content = result.content === undefined ? undefined : frameContent(result.content)
|
|
554
|
+
if (output === result.output && error === result.error && content === result.content)
|
|
555
|
+
return result
|
|
514
556
|
|
|
515
557
|
return {
|
|
516
558
|
...result,
|
|
517
|
-
output
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
attributes: { server: serverName, tool: toolName },
|
|
521
|
-
provenance: 'This is output the named server returned, not this agent.',
|
|
522
|
-
},
|
|
523
|
-
result.output,
|
|
524
|
-
),
|
|
559
|
+
output,
|
|
560
|
+
...(error !== undefined ? { error } : {}),
|
|
561
|
+
...(content !== undefined ? { content } : {}),
|
|
525
562
|
}
|
|
526
563
|
}
|
|
527
564
|
|
|
@@ -530,6 +567,11 @@ export function mcpToolResultToToolResult(result: MCPToolResult): ToolResult {
|
|
|
530
567
|
.filter((block): block is { type: 'text'; text: string } => block.type === 'text')
|
|
531
568
|
.map((block) => block.text)
|
|
532
569
|
.join('\n')
|
|
570
|
+
const imageBlocks = result.content.filter(
|
|
571
|
+
(block): block is Extract<(typeof result.content)[number], { type: 'image' }> =>
|
|
572
|
+
block.type === 'image',
|
|
573
|
+
)
|
|
574
|
+
const imagesAdmitted = admitMcpImageBatch(imageBlocks)
|
|
533
575
|
|
|
534
576
|
// Non-text blocks used to be filtered out and never seen again: a
|
|
535
577
|
// bridged MCP server returning a chart, a screenshot or a PDF had that
|
|
@@ -540,8 +582,19 @@ export function mcpToolResultToToolResult(result: MCPToolResult): ToolResult {
|
|
|
540
582
|
for (const block of result.content) {
|
|
541
583
|
if (block.type === 'text') {
|
|
542
584
|
blocks.push({ type: 'text', text: block.text })
|
|
543
|
-
} else if (block.type === 'image'
|
|
544
|
-
blocks.push({
|
|
585
|
+
} else if (block.type === 'image') {
|
|
586
|
+
blocks.push({
|
|
587
|
+
type: 'image',
|
|
588
|
+
data: block.data,
|
|
589
|
+
mediaType: block.mimeType,
|
|
590
|
+
...(!imagesAdmitted
|
|
591
|
+
? {
|
|
592
|
+
modelOmission: {
|
|
593
|
+
reason: 'invalid-image' as const,
|
|
594
|
+
},
|
|
595
|
+
}
|
|
596
|
+
: {}),
|
|
597
|
+
})
|
|
545
598
|
} else if (block.type === 'resource' && block.resource?.text) {
|
|
546
599
|
// A resource with inline text is readable content; one that is
|
|
547
600
|
// only a URI is a pointer the model cannot dereference, so it is
|
|
@@ -556,10 +609,15 @@ export function mcpToolResultToToolResult(result: MCPToolResult): ToolResult {
|
|
|
556
609
|
// the model reading the answer and reading nothing at all — the call
|
|
557
610
|
// succeeded, so no error path would have caught it either. Text wins
|
|
558
611
|
// when both are present: the server wrote it for the model.
|
|
559
|
-
const
|
|
612
|
+
const visibleText =
|
|
560
613
|
textContent.length > 0 || result.structuredContent === undefined
|
|
561
614
|
? textContent
|
|
562
615
|
: stringifyStructured(result.structuredContent)
|
|
616
|
+
const invalidImageNotice =
|
|
617
|
+
imageBlocks.length > 0 && !imagesAdmitted
|
|
618
|
+
? '[MCP image batch withheld from model input: one or more blocks are not complete supported raster containers matching their declared media types.]'
|
|
619
|
+
: ''
|
|
620
|
+
const output = [visibleText, invalidImageNotice].filter((part) => part.length > 0).join('\n')
|
|
563
621
|
|
|
564
622
|
return {
|
|
565
623
|
success: !result.isError,
|
|
@@ -570,7 +628,10 @@ export function mcpToolResultToToolResult(result: MCPToolResult): ToolResult {
|
|
|
570
628
|
data:
|
|
571
629
|
result.structuredContent === undefined
|
|
572
630
|
? result.content
|
|
573
|
-
: {
|
|
631
|
+
: {
|
|
632
|
+
content: result.content,
|
|
633
|
+
structuredContent: result.structuredContent,
|
|
634
|
+
},
|
|
574
635
|
error: result.isError ? output : undefined,
|
|
575
636
|
}
|
|
576
637
|
}
|
|
@@ -170,13 +170,14 @@ export class MCPClient {
|
|
|
170
170
|
async disconnect(): Promise<void> {
|
|
171
171
|
const reason = new Error('MCPClient disconnecting')
|
|
172
172
|
this.abortCancellations(reason)
|
|
173
|
-
|
|
173
|
+
const alreadyDisconnected = this.status === 'disconnected'
|
|
174
174
|
|
|
175
175
|
this.rejectAllPending('MCPClient disconnecting')
|
|
176
176
|
|
|
177
177
|
await this.transport.close()
|
|
178
178
|
this.status = 'disconnected'
|
|
179
179
|
this.connectedAt = undefined
|
|
180
|
+
if (alreadyDisconnected) return
|
|
180
181
|
this.log.info('MCP client disconnected')
|
|
181
182
|
this.emitLifecycle({ type: 'mcp_client_disconnected', clientId: this.id })
|
|
182
183
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A request reached the configured MCP server, but no response from the
|
|
3
|
+
* requested operation was received because that server redirected it.
|
|
4
|
+
*
|
|
5
|
+
* This stays an internal transport-to-adapter signal. The public tool result
|
|
6
|
+
* carries the stable, serializable outcome fields instead of exposing an
|
|
7
|
+
* Error subclass as API.
|
|
8
|
+
*/
|
|
9
|
+
export class MCPHttpRedirectError extends Error {
|
|
10
|
+
readonly code = 'mcp_http_redirect_refused'
|
|
11
|
+
readonly remoteOutcomeUnknown: boolean
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
readonly status: number,
|
|
15
|
+
readonly requestMethod?: string,
|
|
16
|
+
) {
|
|
17
|
+
const base = `MCP HTTP ${status} redirect was not followed because authenticated requests must remain at their configured endpoint. Configure the final MCP endpoint directly.`
|
|
18
|
+
const outcome =
|
|
19
|
+
requestMethod === 'tools/call'
|
|
20
|
+
? ' The configured MCP server received the tool call, so the remote outcome is unknown; do not automatically retry.'
|
|
21
|
+
: ''
|
|
22
|
+
super(base + outcome)
|
|
23
|
+
this.name = 'MCPHttpRedirectError'
|
|
24
|
+
this.remoteOutcomeUnknown = requestMethod === 'tools/call'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function refuseMcpHttpRedirect(response: Response, requestMethod?: string): void {
|
|
29
|
+
if (response.status >= 300 && response.status < 400) {
|
|
30
|
+
throw new MCPHttpRedirectError(response.status, requestMethod)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
import { SCOPE_ATTRIBUTE } from '../../utils/log/types.js'
|
|
8
8
|
import { type Logger, resolveLogger } from '../../utils/logger.js'
|
|
9
9
|
import { ConnectorHttpOperation, validateConnectorTimeoutMs } from '../http-operation.js'
|
|
10
|
+
import { refuseMcpHttpRedirect } from './http-redirect.js'
|
|
10
11
|
|
|
11
12
|
const DEFAULT_TIMEOUT_MS = 30_000
|
|
12
13
|
|
|
@@ -111,10 +112,12 @@ export class HttpSseTransport implements MCPTransport {
|
|
|
111
112
|
...this.config.headers,
|
|
112
113
|
},
|
|
113
114
|
body: JSON.stringify(message),
|
|
115
|
+
redirect: 'manual',
|
|
114
116
|
signal: operation.signal,
|
|
115
117
|
}),
|
|
116
118
|
)
|
|
117
119
|
|
|
120
|
+
refuseMcpHttpRedirect(response, message.method)
|
|
118
121
|
if (!response.ok) {
|
|
119
122
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
120
123
|
}
|
|
@@ -202,9 +205,11 @@ export class HttpSseTransport implements MCPTransport {
|
|
|
202
205
|
Accept: 'text/event-stream',
|
|
203
206
|
...this.config.headers,
|
|
204
207
|
},
|
|
208
|
+
redirect: 'manual',
|
|
205
209
|
signal,
|
|
206
210
|
})
|
|
207
211
|
|
|
212
|
+
refuseMcpHttpRedirect(response)
|
|
208
213
|
if (!response.ok || !response.body) {
|
|
209
214
|
throw new Error(`SSE connection failed: HTTP ${response.status}`)
|
|
210
215
|
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import type { MCPContentBlock } from '../../types/connector/index.js'
|
|
2
|
+
|
|
3
|
+
const MAX_ENCODED_BYTES = 24 * 1024 * 1024
|
|
4
|
+
const MAX_DIMENSION = 16_384
|
|
5
|
+
const MAX_PIXELS = 40_000_000
|
|
6
|
+
|
|
7
|
+
type MCPImageBlock = Extract<MCPContentBlock, { type: 'image' }>
|
|
8
|
+
type ImageMediaType = 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif'
|
|
9
|
+
|
|
10
|
+
const IMAGE_MEDIA_TYPES = new Set<ImageMediaType>([
|
|
11
|
+
'image/png',
|
|
12
|
+
'image/jpeg',
|
|
13
|
+
'image/webp',
|
|
14
|
+
'image/gif',
|
|
15
|
+
])
|
|
16
|
+
|
|
17
|
+
function isImageMediaType(value: string): value is ImageMediaType {
|
|
18
|
+
return IMAGE_MEDIA_TYPES.has(value as ImageMediaType)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function decodeCanonicalBase64(value: string): Uint8Array | null {
|
|
22
|
+
if (value.length === 0 || value.length > MAX_ENCODED_BYTES) return null
|
|
23
|
+
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
const decoded = Buffer.from(value, 'base64')
|
|
27
|
+
return decoded.toString('base64') === value ? decoded : null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function dimensionsAreSafe(width: number, height: number): boolean {
|
|
31
|
+
return (
|
|
32
|
+
Number.isSafeInteger(width) &&
|
|
33
|
+
Number.isSafeInteger(height) &&
|
|
34
|
+
width > 0 &&
|
|
35
|
+
height > 0 &&
|
|
36
|
+
width <= MAX_DIMENSION &&
|
|
37
|
+
height <= MAX_DIMENSION &&
|
|
38
|
+
width * height <= MAX_PIXELS
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const crcTable = (() => {
|
|
43
|
+
const table = new Uint32Array(256)
|
|
44
|
+
for (let n = 0; n < 256; n += 1) {
|
|
45
|
+
let value = n
|
|
46
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
47
|
+
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1
|
|
48
|
+
}
|
|
49
|
+
table[n] = value >>> 0
|
|
50
|
+
}
|
|
51
|
+
return table
|
|
52
|
+
})()
|
|
53
|
+
|
|
54
|
+
function crc32(bytes: Uint8Array, start: number, end: number): number {
|
|
55
|
+
let crc = 0xffffffff
|
|
56
|
+
for (let offset = start; offset < end; offset += 1) {
|
|
57
|
+
crc = (crcTable[(crc ^ (bytes[offset] ?? 0)) & 0xff] ?? 0) ^ (crc >>> 8)
|
|
58
|
+
}
|
|
59
|
+
return (crc ^ 0xffffffff) >>> 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isPng(bytes: Uint8Array): boolean {
|
|
63
|
+
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
64
|
+
if (bytes.length < 45 || !buffer.subarray(0, 8).equals(Buffer.from('89504e470d0a1a0a', 'hex'))) {
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
let offset = 8
|
|
68
|
+
let sawHeader = false
|
|
69
|
+
let sawData = false
|
|
70
|
+
while (offset + 12 <= bytes.length) {
|
|
71
|
+
const length = buffer.readUInt32BE(offset)
|
|
72
|
+
const typeStart = offset + 4
|
|
73
|
+
const dataStart = typeStart + 4
|
|
74
|
+
const dataEnd = dataStart + length
|
|
75
|
+
const chunkEnd = dataEnd + 4
|
|
76
|
+
if (dataEnd < dataStart || chunkEnd > bytes.length) return false
|
|
77
|
+
const type = buffer.toString('ascii', typeStart, dataStart)
|
|
78
|
+
if (buffer.readUInt32BE(dataEnd) !== crc32(bytes, typeStart, dataEnd)) return false
|
|
79
|
+
if (!sawHeader) {
|
|
80
|
+
if (type !== 'IHDR' || length !== 13) return false
|
|
81
|
+
const width = buffer.readUInt32BE(dataStart)
|
|
82
|
+
const height = buffer.readUInt32BE(dataStart + 4)
|
|
83
|
+
if (!dimensionsAreSafe(width, height)) return false
|
|
84
|
+
sawHeader = true
|
|
85
|
+
} else if (type === 'IHDR') return false
|
|
86
|
+
if (type === 'IDAT') sawData = true
|
|
87
|
+
if (type === 'IEND') return length === 0 && sawHeader && sawData && chunkEnd === bytes.length
|
|
88
|
+
offset = chunkEnd
|
|
89
|
+
}
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const JPEG_SOF_MARKERS = new Set([
|
|
94
|
+
0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf,
|
|
95
|
+
])
|
|
96
|
+
|
|
97
|
+
function isJpeg(bytes: Uint8Array): boolean {
|
|
98
|
+
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
99
|
+
if (bytes.length < 11 || bytes[0] !== 0xff || bytes[1] !== 0xd8) return false
|
|
100
|
+
let offset = 2
|
|
101
|
+
let sawFrame = false
|
|
102
|
+
let sawScan = false
|
|
103
|
+
let inScan = false
|
|
104
|
+
while (offset < bytes.length) {
|
|
105
|
+
if (inScan && bytes[offset] !== 0xff) {
|
|
106
|
+
offset += 1
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
if (bytes[offset] !== 0xff) return false
|
|
110
|
+
while (offset < bytes.length && bytes[offset] === 0xff) offset += 1
|
|
111
|
+
if (offset >= bytes.length) return false
|
|
112
|
+
const marker = bytes[offset] ?? 0
|
|
113
|
+
offset += 1
|
|
114
|
+
if (inScan && (marker === 0x00 || (marker >= 0xd0 && marker <= 0xd7))) continue
|
|
115
|
+
inScan = false
|
|
116
|
+
if (marker === 0xd9) return sawFrame && sawScan && offset === bytes.length
|
|
117
|
+
if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) continue
|
|
118
|
+
if (offset + 2 > bytes.length) return false
|
|
119
|
+
const length = buffer.readUInt16BE(offset)
|
|
120
|
+
if (length < 2 || offset + length > bytes.length) return false
|
|
121
|
+
if (JPEG_SOF_MARKERS.has(marker)) {
|
|
122
|
+
if (length < 8) return false
|
|
123
|
+
const height = buffer.readUInt16BE(offset + 3)
|
|
124
|
+
const width = buffer.readUInt16BE(offset + 5)
|
|
125
|
+
if (!dimensionsAreSafe(width, height)) return false
|
|
126
|
+
sawFrame = true
|
|
127
|
+
}
|
|
128
|
+
offset += length
|
|
129
|
+
if (marker === 0xda) {
|
|
130
|
+
sawScan = true
|
|
131
|
+
inScan = true
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function skipGifSubBlocks(bytes: Uint8Array, start: number): number | null {
|
|
138
|
+
let offset = start
|
|
139
|
+
while (offset < bytes.length) {
|
|
140
|
+
const length = bytes[offset] ?? 0
|
|
141
|
+
offset += 1
|
|
142
|
+
if (length === 0) return offset
|
|
143
|
+
if (offset + length > bytes.length) return null
|
|
144
|
+
offset += length
|
|
145
|
+
}
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isGif(bytes: Uint8Array): boolean {
|
|
150
|
+
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
151
|
+
if (bytes.length < 14) return false
|
|
152
|
+
const signature = buffer.toString('ascii', 0, 6)
|
|
153
|
+
if (signature !== 'GIF87a' && signature !== 'GIF89a') return false
|
|
154
|
+
if (!dimensionsAreSafe(buffer.readUInt16LE(6), buffer.readUInt16LE(8))) return false
|
|
155
|
+
let offset = 13
|
|
156
|
+
const packed = bytes[10] ?? 0
|
|
157
|
+
if ((packed & 0x80) !== 0) offset += 3 * 2 ** ((packed & 0x07) + 1)
|
|
158
|
+
if (offset > bytes.length) return false
|
|
159
|
+
let sawImage = false
|
|
160
|
+
while (offset < bytes.length) {
|
|
161
|
+
const marker = bytes[offset] ?? 0
|
|
162
|
+
offset += 1
|
|
163
|
+
if (marker === 0x3b) return sawImage && offset === bytes.length
|
|
164
|
+
if (marker === 0x21) {
|
|
165
|
+
if (offset >= bytes.length) return false
|
|
166
|
+
offset += 1
|
|
167
|
+
const next = skipGifSubBlocks(bytes, offset)
|
|
168
|
+
if (next === null) return false
|
|
169
|
+
offset = next
|
|
170
|
+
continue
|
|
171
|
+
}
|
|
172
|
+
if (marker !== 0x2c || offset + 9 > bytes.length) return false
|
|
173
|
+
const width = buffer.readUInt16LE(offset + 4)
|
|
174
|
+
const height = buffer.readUInt16LE(offset + 6)
|
|
175
|
+
if (!dimensionsAreSafe(width, height)) return false
|
|
176
|
+
const imagePacked = bytes[offset + 8] ?? 0
|
|
177
|
+
offset += 9
|
|
178
|
+
if ((imagePacked & 0x80) !== 0) offset += 3 * 2 ** ((imagePacked & 0x07) + 1)
|
|
179
|
+
if (offset >= bytes.length) return false
|
|
180
|
+
offset += 1
|
|
181
|
+
const next = skipGifSubBlocks(bytes, offset)
|
|
182
|
+
if (next === null) return false
|
|
183
|
+
offset = next
|
|
184
|
+
sawImage = true
|
|
185
|
+
}
|
|
186
|
+
return false
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function readUInt24LE(bytes: Uint8Array, offset: number): number {
|
|
190
|
+
return (bytes[offset] ?? 0) | ((bytes[offset + 1] ?? 0) << 8) | ((bytes[offset + 2] ?? 0) << 16)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function webpDimensions(type: string, data: Uint8Array): readonly [number, number] | null {
|
|
194
|
+
const buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength)
|
|
195
|
+
if (type === 'VP8X') {
|
|
196
|
+
if (data.length < 10) return null
|
|
197
|
+
return [readUInt24LE(data, 4) + 1, readUInt24LE(data, 7) + 1]
|
|
198
|
+
}
|
|
199
|
+
if (type === 'VP8L') {
|
|
200
|
+
if (data.length < 5 || data[0] !== 0x2f) return null
|
|
201
|
+
const bits = buffer.readUInt32LE(1)
|
|
202
|
+
return [(bits & 0x3fff) + 1, ((bits >>> 14) & 0x3fff) + 1]
|
|
203
|
+
}
|
|
204
|
+
if (type === 'VP8 ') {
|
|
205
|
+
if (data.length < 10 || data[3] !== 0x9d || data[4] !== 0x01 || data[5] !== 0x2a) {
|
|
206
|
+
return null
|
|
207
|
+
}
|
|
208
|
+
return [buffer.readUInt16LE(6) & 0x3fff, buffer.readUInt16LE(8) & 0x3fff]
|
|
209
|
+
}
|
|
210
|
+
return null
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function isWebp(bytes: Uint8Array): boolean {
|
|
214
|
+
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
215
|
+
if (
|
|
216
|
+
bytes.length < 20 ||
|
|
217
|
+
buffer.toString('ascii', 0, 4) !== 'RIFF' ||
|
|
218
|
+
buffer.readUInt32LE(4) + 8 !== bytes.length ||
|
|
219
|
+
buffer.toString('ascii', 8, 12) !== 'WEBP'
|
|
220
|
+
) {
|
|
221
|
+
return false
|
|
222
|
+
}
|
|
223
|
+
let offset = 12
|
|
224
|
+
let dimensions: readonly [number, number] | null = null
|
|
225
|
+
let sawImagePayload = false
|
|
226
|
+
while (offset + 8 <= bytes.length) {
|
|
227
|
+
const type = buffer.toString('ascii', offset, offset + 4)
|
|
228
|
+
const length = buffer.readUInt32LE(offset + 4)
|
|
229
|
+
const dataStart = offset + 8
|
|
230
|
+
const dataEnd = dataStart + length
|
|
231
|
+
if (dataEnd < dataStart || dataEnd > bytes.length) return false
|
|
232
|
+
const current = webpDimensions(type, bytes.subarray(dataStart, dataEnd))
|
|
233
|
+
if (current && dimensions === null) dimensions = current
|
|
234
|
+
if (type === 'VP8 ' || type === 'VP8L') sawImagePayload = current !== null
|
|
235
|
+
offset = dataEnd + (length & 1)
|
|
236
|
+
}
|
|
237
|
+
return (
|
|
238
|
+
offset === bytes.length &&
|
|
239
|
+
dimensions !== null &&
|
|
240
|
+
sawImagePayload &&
|
|
241
|
+
dimensionsAreSafe(dimensions[0], dimensions[1])
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function imageIsAdmitted(block: MCPImageBlock): boolean {
|
|
246
|
+
if (!isImageMediaType(block.mimeType)) return false
|
|
247
|
+
const bytes = decodeCanonicalBase64(block.data)
|
|
248
|
+
if (!bytes) return false
|
|
249
|
+
switch (block.mimeType) {
|
|
250
|
+
case 'image/png':
|
|
251
|
+
return isPng(bytes)
|
|
252
|
+
case 'image/jpeg':
|
|
253
|
+
return isJpeg(bytes)
|
|
254
|
+
case 'image/gif':
|
|
255
|
+
return isGif(bytes)
|
|
256
|
+
case 'image/webp':
|
|
257
|
+
return isWebp(bytes)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Admit an MCP image batch atomically before any member becomes model input.
|
|
263
|
+
*
|
|
264
|
+
* A remote server controls the declared MIME and bytes. Canonical base64 is
|
|
265
|
+
* only an encoding property; the decoded value must also be a complete,
|
|
266
|
+
* bounded raster container whose format agrees with that declaration. One bad
|
|
267
|
+
* member withholds the complete batch so a model is never shown a partial
|
|
268
|
+
* result while the host retains every raw block for inspection.
|
|
269
|
+
*/
|
|
270
|
+
export function admitMcpImageBatch(images: readonly MCPImageBlock[]): boolean {
|
|
271
|
+
return images.length === 0 || images.every(imageIsAdmitted)
|
|
272
|
+
}
|