@ai-agent-forge/plugin-image-inspect 0.0.0-stage → 0.88.16

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/MANUAL.md ADDED
@@ -0,0 +1,13 @@
1
+ # image_inspect
2
+
3
+ Use `image_inspect` only when the conversation exposes an `image_ref=...` (in an image placeholder or a compaction summary section `## Available Images`)
4
+ placeholder and you need to know something from that original image.
5
+
6
+ Input:
7
+
8
+ ```json
9
+ { "image_ref": "image-1a2b3c4d", "question": "What does the error dialog say?" }
10
+ ```
11
+
12
+ The session's selected vision model answers the question. Treat its answer as
13
+ an observation from another model, not as your own view of the image.
package/README.md CHANGED
@@ -1,3 +1,14 @@
1
- # Temporary Holding Version
1
+ # @agent-forge/plugin-image-inspect
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `image_inspect` supplies visual understanding to a main model that is not
4
+ vision-compatible. The tool accepts an original-image reference and one
5
+ question, then asks the session-selected `visual_image` model in an isolated
6
+ completion. It never accepts a provider/model override and never takes over the
7
+ main task. The result states the original image dimensions and a no-crop basis;
8
+ the vision model is asked for normalized coordinates relative to that original
9
+ so they map back regardless of provider-side resizing. Compaction summaries
10
+ carry `image_ref` tokens forward, so the reference remains usable after
11
+ compaction.
12
+
13
+ The host hides the tool when the main request is vision-compatible or no vision
14
+ target is selected.
@@ -0,0 +1,25 @@
1
+ {
2
+ "packageFormatVersion": 1,
3
+ "id": "agent-forge.plugin.image-inspect",
4
+ "pluginVersion": "0.88.16",
5
+ "apiVersion": "1",
6
+ "minHostVersion": "0.85.14",
7
+ "capabilityRefs": [
8
+ {
9
+ "id": "vision.inspect",
10
+ "version": 1
11
+ }
12
+ ],
13
+ "platform": {
14
+ "os": [
15
+ "windows",
16
+ "macos",
17
+ "linux"
18
+ ],
19
+ "runtime": [
20
+ {
21
+ "name": "node"
22
+ }
23
+ ]
24
+ }
25
+ }
@@ -0,0 +1,8 @@
1
+ import type { CapabilityPluginSyncFactory } from "@agent-forge/plugin-sdk";
2
+ /**
3
+ * `image_inspect` for a non-vision main model. The tool resolves the original
4
+ * image from the session branch by content reference and asks the selected
5
+ * vision model exactly one question in an isolated completion.
6
+ */
7
+ export declare function createImageInspectCapabilityFactoryV1(): CapabilityPluginSyncFactory;
8
+ //# sourceMappingURL=capability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEX,2BAA2B,EAG3B,MAAM,yBAAyB,CAAC;AA8GjC;;;;GAIG;AACH,wBAAgB,qCAAqC,IAAI,2BAA2B,CAkEnF","sourcesContent":["import type {\n\tCapabilityAPI,\n\tCapabilityPluginSyncFactory,\n\tModelInvocationResultV1,\n\tSessionEntryView,\n} from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { imageReferenceV1 } from \"./image-ref.ts\";\nimport { type ImageSizeV1, imageSizeV1 } from \"./image-size.ts\";\n\nconst IMAGE_INSPECT_PARAMETERS = Type.Object(\n\t{\n\t\timage_ref: Type.String({ minLength: 1, maxLength: 64, pattern: \"^image-[0-9a-f]{8}$\" }),\n\t\tquestion: Type.String({ minLength: 1, maxLength: 2000 }),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_REFERENCE_PATTERN = /^image-[0-9a-f]{8}$/u;\n\ninterface ToolResultV1 {\n\treadonly content: Array<{ type: \"text\"; text: string }>;\n\treadonly details: unknown;\n}\n\ninterface FoundImageV1 {\n\treadonly data: string;\n\treadonly mimeType: string;\n}\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = first !== null && typeof first === \"object\" && !Array.isArray(first) ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow new Error(\"image_inspect input must be an object\");\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction combineSignals(signals: readonly (AbortSignal | undefined)[]): AbortSignal | undefined {\n\tconst present = signals.filter((signal): signal is AbortSignal => signal !== undefined);\n\tif (present.length === 0) return undefined;\n\tif (present.length === 1) return present[0];\n\treturn AbortSignal.any(present);\n}\n\nfunction contentBlocks(content: unknown): unknown[] {\n\tif (Array.isArray(content)) return content;\n\tif (typeof content === \"string\") return [{ type: \"text\", text: content }];\n\treturn [];\n}\n\nfunction findImage(entries: readonly SessionEntryView[], reference: string): FoundImageV1 | undefined {\n\tfor (let entryIndex = entries.length - 1; entryIndex >= 0; entryIndex--) {\n\t\tconst entry = entries[entryIndex];\n\t\tif (entry?.type !== \"message\") continue;\n\t\tconst blocks = contentBlocks(entry.message?.content);\n\t\tfor (let blockIndex = blocks.length - 1; blockIndex >= 0; blockIndex--) {\n\t\t\tconst block = blocks[blockIndex];\n\t\t\tif (typeof block !== \"object\" || block === null) continue;\n\t\t\tconst record = block as Record<string, unknown>;\n\t\t\tif (record.type !== \"image\") continue;\n\t\t\tconst data = record.data;\n\t\t\tif (typeof data !== \"string\" || data === \"\") continue;\n\t\t\tif (imageReferenceV1(data) !== reference) continue;\n\t\t\tconst mimeType = typeof record.mimeType === \"string\" && record.mimeType !== \"\" ? record.mimeType : \"image/png\";\n\t\t\treturn { data, mimeType };\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction failed(code: string, message: string, details?: Record<string, unknown>): ToolResultV1 {\n\treturn {\n\t\tcontent: [{ type: \"text\", text: `image_inspect failed (${code}): ${message}` }],\n\t\tdetails: { version: 1, status: \"failed\", code, message, ...(details ?? {}) },\n\t};\n}\n\nfunction imageBasisNote(imageSize: ImageSizeV1 | undefined): string {\n\treturn imageSize === undefined\n\t\t? \"Image basis: original image, no crop; dimensions unavailable.\"\n\t\t: `Image basis: original ${imageSize.width}x${imageSize.height}, no crop. Coordinates are normalized to this image unless the answer states otherwise.`;\n}\n\nfunction resultFromInvocation(\n\tresult: ModelInvocationResultV1,\n\timageRef: string,\n\ttarget: { provider: string; id: string },\n\timageSize?: ImageSizeV1,\n): ToolResultV1 {\n\tif (result.status === \"cancelled\") {\n\t\treturn failed(\"cancelled\", \"image inspection was cancelled\", { imageRef, target });\n\t}\n\tif (result.status === \"failed\") {\n\t\treturn failed(result.error.code, result.error.message, { imageRef, target });\n\t}\n\treturn {\n\t\tcontent: [{ type: \"text\", text: `${result.text}\\n\\n${imageBasisNote(imageSize)}` }],\n\t\tdetails: {\n\t\t\tversion: 1,\n\t\t\tstatus: \"ok\",\n\t\t\timageRef,\n\t\t\ttarget,\n\t\t\tmodel: result.model,\n\t\t\t...(imageSize === undefined ? {} : { image: imageSize }),\n\t\t\t...(result.usage === undefined ? {} : { usage: result.usage }),\n\t\t\tnote: \"observation from the selected vision model; not the main model's own view\",\n\t\t},\n\t};\n}\n\n/**\n * `image_inspect` for a non-vision main model. The tool resolves the original\n * image from the session branch by content reference and asks the selected\n * vision model exactly one question in an isolated completion.\n */\nexport function createImageInspectCapabilityFactoryV1(): CapabilityPluginSyncFactory {\n\treturn (api: CapabilityAPI) => {\n\t\tapi.registerTool({\n\t\t\tname: \"image_inspect\",\n\t\t\tlabel: \"Image inspect\",\n\t\t\tdescription:\n\t\t\t\t\"Inspect an original image that the current model cannot see. Pass the image_ref from the image placeholder (or from an Available Images summary entry) and one question; a selected vision model answers. Do not use it when you can already see the image.\",\n\t\t\tparameters: IMAGE_INSPECT_PARAMETERS,\n\t\t\treadonlySafe: true,\n\t\t\tcapability: { key: \"visual_image\", hideWhenNative: true, requiresSelection: true },\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown): Promise<ToolResultV1> => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failed(\"invalid_input\", error instanceof Error ? error.message : \"invalid input\");\n\t\t\t\t}\n\t\t\t\tconst imageRef = typeof input.image_ref === \"string\" ? input.image_ref : \"\";\n\t\t\t\tif (!IMAGE_REFERENCE_PATTERN.test(imageRef)) {\n\t\t\t\t\treturn failed(\"invalid_image_ref\", \"image_ref must be an image_ref value from the conversation\");\n\t\t\t\t}\n\t\t\t\tconst question = typeof input.question === \"string\" ? input.question.trim() : \"\";\n\t\t\t\tif (question === \"\") return failed(\"invalid_question\", \"question must be a non-empty string\");\n\n\t\t\t\tconst target = api.selection?.get(\"visual_image\");\n\t\t\t\tif (target === undefined) {\n\t\t\t\t\treturn failed(\"selection_missing\", \"no vision target is selected for this session\");\n\t\t\t\t}\n\t\t\t\tif (!target.available) {\n\t\t\t\t\treturn failed(\"selection_unavailable\", target.error ?? \"selected vision target is unavailable\", {\n\t\t\t\t\t\timageRef,\n\t\t\t\t\t\ttarget: { provider: target.provider, id: target.id },\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst image = findImage(api.session?.getBranchEntries() ?? [], imageRef);\n\t\t\t\tconst imageSize = image === undefined ? undefined : imageSizeV1(image.data, image.mimeType);\n\t\t\t\tif (image === undefined) {\n\t\t\t\t\treturn failed(\"image_unavailable\", `no image with reference ${imageRef} is available in this branch`, {\n\t\t\t\t\t\timageRef,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (api.modelInvocation === undefined) {\n\t\t\t\t\treturn failed(\"model_invocation_unavailable\", \"the host does not expose model invocation\", {\n\t\t\t\t\t\timageRef,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst signal = combineSignals([signalFrom(third) ?? signalFrom(second), api.sessionAbortSignal?.()]);\n\t\t\t\tif (signal?.aborted === true) return failed(\"cancelled\", \"image inspection was cancelled\", { imageRef });\n\t\t\t\tconst result = await api.modelInvocation.complete(\n\t\t\t\t\t{\n\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\ttarget: { provider: target.provider, id: target.id },\n\t\t\t\t\t\tcapability: \"visual_image\",\n\t\t\t\t\t\tsystemPrompt: `You inspect one image for another model. The image is the original, no crop${\n\t\t\t\t\t\t\timageSize === undefined ? \"\" : `, ${imageSize.width}x${imageSize.height} pixels`\n\t\t\t\t\t\t}. Answer only the question. Separate direct observation, inference, and uncertainty. Report any coordinates as normalized fractions in [0,1] relative to the full original image (origin top-left); use absolute pixels only when the question asks for them. Do not take over the main task or invent details that are not visible.`,\n\t\t\t\t\t\tprompt: question,\n\t\t\t\t\t\timages: [{ type: \"image\", data: image.data, mimeType: image.mimeType }],\n\t\t\t\t\t\tmaxTokens: 1200,\n\t\t\t\t\t},\n\t\t\t\t\tsignal === undefined ? {} : { signal },\n\t\t\t\t);\n\t\t\t\treturn resultFromInvocation(result, imageRef, { provider: target.provider, id: target.id }, imageSize);\n\t\t\t},\n\t\t});\n\t};\n}\n"]}
@@ -0,0 +1,158 @@
1
+ import { Type } from "typebox";
2
+ import { imageReferenceV1 } from "./image-ref.js";
3
+ import { imageSizeV1 } from "./image-size.js";
4
+ const IMAGE_INSPECT_PARAMETERS = Type.Object({
5
+ image_ref: Type.String({ minLength: 1, maxLength: 64, pattern: "^image-[0-9a-f]{8}$" }),
6
+ question: Type.String({ minLength: 1, maxLength: 2000 }),
7
+ }, { additionalProperties: false });
8
+ const IMAGE_REFERENCE_PATTERN = /^image-[0-9a-f]{8}$/u;
9
+ function objectInput(first, second) {
10
+ const value = first !== null && typeof first === "object" && !Array.isArray(first) ? first : second;
11
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
12
+ throw new Error("image_inspect input must be an object");
13
+ }
14
+ return value;
15
+ }
16
+ function signalFrom(value) {
17
+ return value instanceof AbortSignal ? value : undefined;
18
+ }
19
+ function combineSignals(signals) {
20
+ const present = signals.filter((signal) => signal !== undefined);
21
+ if (present.length === 0)
22
+ return undefined;
23
+ if (present.length === 1)
24
+ return present[0];
25
+ return AbortSignal.any(present);
26
+ }
27
+ function contentBlocks(content) {
28
+ if (Array.isArray(content))
29
+ return content;
30
+ if (typeof content === "string")
31
+ return [{ type: "text", text: content }];
32
+ return [];
33
+ }
34
+ function findImage(entries, reference) {
35
+ for (let entryIndex = entries.length - 1; entryIndex >= 0; entryIndex--) {
36
+ const entry = entries[entryIndex];
37
+ if (entry?.type !== "message")
38
+ continue;
39
+ const blocks = contentBlocks(entry.message?.content);
40
+ for (let blockIndex = blocks.length - 1; blockIndex >= 0; blockIndex--) {
41
+ const block = blocks[blockIndex];
42
+ if (typeof block !== "object" || block === null)
43
+ continue;
44
+ const record = block;
45
+ if (record.type !== "image")
46
+ continue;
47
+ const data = record.data;
48
+ if (typeof data !== "string" || data === "")
49
+ continue;
50
+ if (imageReferenceV1(data) !== reference)
51
+ continue;
52
+ const mimeType = typeof record.mimeType === "string" && record.mimeType !== "" ? record.mimeType : "image/png";
53
+ return { data, mimeType };
54
+ }
55
+ }
56
+ return undefined;
57
+ }
58
+ function failed(code, message, details) {
59
+ return {
60
+ content: [{ type: "text", text: `image_inspect failed (${code}): ${message}` }],
61
+ details: { version: 1, status: "failed", code, message, ...(details ?? {}) },
62
+ };
63
+ }
64
+ function imageBasisNote(imageSize) {
65
+ return imageSize === undefined
66
+ ? "Image basis: original image, no crop; dimensions unavailable."
67
+ : `Image basis: original ${imageSize.width}x${imageSize.height}, no crop. Coordinates are normalized to this image unless the answer states otherwise.`;
68
+ }
69
+ function resultFromInvocation(result, imageRef, target, imageSize) {
70
+ if (result.status === "cancelled") {
71
+ return failed("cancelled", "image inspection was cancelled", { imageRef, target });
72
+ }
73
+ if (result.status === "failed") {
74
+ return failed(result.error.code, result.error.message, { imageRef, target });
75
+ }
76
+ return {
77
+ content: [{ type: "text", text: `${result.text}\n\n${imageBasisNote(imageSize)}` }],
78
+ details: {
79
+ version: 1,
80
+ status: "ok",
81
+ imageRef,
82
+ target,
83
+ model: result.model,
84
+ ...(imageSize === undefined ? {} : { image: imageSize }),
85
+ ...(result.usage === undefined ? {} : { usage: result.usage }),
86
+ note: "observation from the selected vision model; not the main model's own view",
87
+ },
88
+ };
89
+ }
90
+ /**
91
+ * `image_inspect` for a non-vision main model. The tool resolves the original
92
+ * image from the session branch by content reference and asks the selected
93
+ * vision model exactly one question in an isolated completion.
94
+ */
95
+ export function createImageInspectCapabilityFactoryV1() {
96
+ return (api) => {
97
+ api.registerTool({
98
+ name: "image_inspect",
99
+ label: "Image inspect",
100
+ description: "Inspect an original image that the current model cannot see. Pass the image_ref from the image placeholder (or from an Available Images summary entry) and one question; a selected vision model answers. Do not use it when you can already see the image.",
101
+ parameters: IMAGE_INSPECT_PARAMETERS,
102
+ readonlySafe: true,
103
+ capability: { key: "visual_image", hideWhenNative: true, requiresSelection: true },
104
+ execute: async (first, second, third) => {
105
+ let input;
106
+ try {
107
+ input = objectInput(first, second);
108
+ }
109
+ catch (error) {
110
+ return failed("invalid_input", error instanceof Error ? error.message : "invalid input");
111
+ }
112
+ const imageRef = typeof input.image_ref === "string" ? input.image_ref : "";
113
+ if (!IMAGE_REFERENCE_PATTERN.test(imageRef)) {
114
+ return failed("invalid_image_ref", "image_ref must be an image_ref value from the conversation");
115
+ }
116
+ const question = typeof input.question === "string" ? input.question.trim() : "";
117
+ if (question === "")
118
+ return failed("invalid_question", "question must be a non-empty string");
119
+ const target = api.selection?.get("visual_image");
120
+ if (target === undefined) {
121
+ return failed("selection_missing", "no vision target is selected for this session");
122
+ }
123
+ if (!target.available) {
124
+ return failed("selection_unavailable", target.error ?? "selected vision target is unavailable", {
125
+ imageRef,
126
+ target: { provider: target.provider, id: target.id },
127
+ });
128
+ }
129
+ const image = findImage(api.session?.getBranchEntries() ?? [], imageRef);
130
+ const imageSize = image === undefined ? undefined : imageSizeV1(image.data, image.mimeType);
131
+ if (image === undefined) {
132
+ return failed("image_unavailable", `no image with reference ${imageRef} is available in this branch`, {
133
+ imageRef,
134
+ });
135
+ }
136
+ if (api.modelInvocation === undefined) {
137
+ return failed("model_invocation_unavailable", "the host does not expose model invocation", {
138
+ imageRef,
139
+ });
140
+ }
141
+ const signal = combineSignals([signalFrom(third) ?? signalFrom(second), api.sessionAbortSignal?.()]);
142
+ if (signal?.aborted === true)
143
+ return failed("cancelled", "image inspection was cancelled", { imageRef });
144
+ const result = await api.modelInvocation.complete({
145
+ version: 1,
146
+ target: { provider: target.provider, id: target.id },
147
+ capability: "visual_image",
148
+ systemPrompt: `You inspect one image for another model. The image is the original, no crop${imageSize === undefined ? "" : `, ${imageSize.width}x${imageSize.height} pixels`}. Answer only the question. Separate direct observation, inference, and uncertainty. Report any coordinates as normalized fractions in [0,1] relative to the full original image (origin top-left); use absolute pixels only when the question asks for them. Do not take over the main task or invent details that are not visible.`,
149
+ prompt: question,
150
+ images: [{ type: "image", data: image.data, mimeType: image.mimeType }],
151
+ maxTokens: 1200,
152
+ }, signal === undefined ? {} : { signal });
153
+ return resultFromInvocation(result, imageRef, { provider: target.provider, id: target.id }, imageSize);
154
+ },
155
+ });
156
+ };
157
+ }
158
+ //# sourceMappingURL=capability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tCapabilityAPI,\n\tCapabilityPluginSyncFactory,\n\tModelInvocationResultV1,\n\tSessionEntryView,\n} from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { imageReferenceV1 } from \"./image-ref.ts\";\nimport { type ImageSizeV1, imageSizeV1 } from \"./image-size.ts\";\n\nconst IMAGE_INSPECT_PARAMETERS = Type.Object(\n\t{\n\t\timage_ref: Type.String({ minLength: 1, maxLength: 64, pattern: \"^image-[0-9a-f]{8}$\" }),\n\t\tquestion: Type.String({ minLength: 1, maxLength: 2000 }),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst IMAGE_REFERENCE_PATTERN = /^image-[0-9a-f]{8}$/u;\n\ninterface ToolResultV1 {\n\treadonly content: Array<{ type: \"text\"; text: string }>;\n\treadonly details: unknown;\n}\n\ninterface FoundImageV1 {\n\treadonly data: string;\n\treadonly mimeType: string;\n}\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = first !== null && typeof first === \"object\" && !Array.isArray(first) ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow new Error(\"image_inspect input must be an object\");\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction combineSignals(signals: readonly (AbortSignal | undefined)[]): AbortSignal | undefined {\n\tconst present = signals.filter((signal): signal is AbortSignal => signal !== undefined);\n\tif (present.length === 0) return undefined;\n\tif (present.length === 1) return present[0];\n\treturn AbortSignal.any(present);\n}\n\nfunction contentBlocks(content: unknown): unknown[] {\n\tif (Array.isArray(content)) return content;\n\tif (typeof content === \"string\") return [{ type: \"text\", text: content }];\n\treturn [];\n}\n\nfunction findImage(entries: readonly SessionEntryView[], reference: string): FoundImageV1 | undefined {\n\tfor (let entryIndex = entries.length - 1; entryIndex >= 0; entryIndex--) {\n\t\tconst entry = entries[entryIndex];\n\t\tif (entry?.type !== \"message\") continue;\n\t\tconst blocks = contentBlocks(entry.message?.content);\n\t\tfor (let blockIndex = blocks.length - 1; blockIndex >= 0; blockIndex--) {\n\t\t\tconst block = blocks[blockIndex];\n\t\t\tif (typeof block !== \"object\" || block === null) continue;\n\t\t\tconst record = block as Record<string, unknown>;\n\t\t\tif (record.type !== \"image\") continue;\n\t\t\tconst data = record.data;\n\t\t\tif (typeof data !== \"string\" || data === \"\") continue;\n\t\t\tif (imageReferenceV1(data) !== reference) continue;\n\t\t\tconst mimeType = typeof record.mimeType === \"string\" && record.mimeType !== \"\" ? record.mimeType : \"image/png\";\n\t\t\treturn { data, mimeType };\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction failed(code: string, message: string, details?: Record<string, unknown>): ToolResultV1 {\n\treturn {\n\t\tcontent: [{ type: \"text\", text: `image_inspect failed (${code}): ${message}` }],\n\t\tdetails: { version: 1, status: \"failed\", code, message, ...(details ?? {}) },\n\t};\n}\n\nfunction imageBasisNote(imageSize: ImageSizeV1 | undefined): string {\n\treturn imageSize === undefined\n\t\t? \"Image basis: original image, no crop; dimensions unavailable.\"\n\t\t: `Image basis: original ${imageSize.width}x${imageSize.height}, no crop. Coordinates are normalized to this image unless the answer states otherwise.`;\n}\n\nfunction resultFromInvocation(\n\tresult: ModelInvocationResultV1,\n\timageRef: string,\n\ttarget: { provider: string; id: string },\n\timageSize?: ImageSizeV1,\n): ToolResultV1 {\n\tif (result.status === \"cancelled\") {\n\t\treturn failed(\"cancelled\", \"image inspection was cancelled\", { imageRef, target });\n\t}\n\tif (result.status === \"failed\") {\n\t\treturn failed(result.error.code, result.error.message, { imageRef, target });\n\t}\n\treturn {\n\t\tcontent: [{ type: \"text\", text: `${result.text}\\n\\n${imageBasisNote(imageSize)}` }],\n\t\tdetails: {\n\t\t\tversion: 1,\n\t\t\tstatus: \"ok\",\n\t\t\timageRef,\n\t\t\ttarget,\n\t\t\tmodel: result.model,\n\t\t\t...(imageSize === undefined ? {} : { image: imageSize }),\n\t\t\t...(result.usage === undefined ? {} : { usage: result.usage }),\n\t\t\tnote: \"observation from the selected vision model; not the main model's own view\",\n\t\t},\n\t};\n}\n\n/**\n * `image_inspect` for a non-vision main model. The tool resolves the original\n * image from the session branch by content reference and asks the selected\n * vision model exactly one question in an isolated completion.\n */\nexport function createImageInspectCapabilityFactoryV1(): CapabilityPluginSyncFactory {\n\treturn (api: CapabilityAPI) => {\n\t\tapi.registerTool({\n\t\t\tname: \"image_inspect\",\n\t\t\tlabel: \"Image inspect\",\n\t\t\tdescription:\n\t\t\t\t\"Inspect an original image that the current model cannot see. Pass the image_ref from the image placeholder (or from an Available Images summary entry) and one question; a selected vision model answers. Do not use it when you can already see the image.\",\n\t\t\tparameters: IMAGE_INSPECT_PARAMETERS,\n\t\t\treadonlySafe: true,\n\t\t\tcapability: { key: \"visual_image\", hideWhenNative: true, requiresSelection: true },\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown): Promise<ToolResultV1> => {\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn failed(\"invalid_input\", error instanceof Error ? error.message : \"invalid input\");\n\t\t\t\t}\n\t\t\t\tconst imageRef = typeof input.image_ref === \"string\" ? input.image_ref : \"\";\n\t\t\t\tif (!IMAGE_REFERENCE_PATTERN.test(imageRef)) {\n\t\t\t\t\treturn failed(\"invalid_image_ref\", \"image_ref must be an image_ref value from the conversation\");\n\t\t\t\t}\n\t\t\t\tconst question = typeof input.question === \"string\" ? input.question.trim() : \"\";\n\t\t\t\tif (question === \"\") return failed(\"invalid_question\", \"question must be a non-empty string\");\n\n\t\t\t\tconst target = api.selection?.get(\"visual_image\");\n\t\t\t\tif (target === undefined) {\n\t\t\t\t\treturn failed(\"selection_missing\", \"no vision target is selected for this session\");\n\t\t\t\t}\n\t\t\t\tif (!target.available) {\n\t\t\t\t\treturn failed(\"selection_unavailable\", target.error ?? \"selected vision target is unavailable\", {\n\t\t\t\t\t\timageRef,\n\t\t\t\t\t\ttarget: { provider: target.provider, id: target.id },\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst image = findImage(api.session?.getBranchEntries() ?? [], imageRef);\n\t\t\t\tconst imageSize = image === undefined ? undefined : imageSizeV1(image.data, image.mimeType);\n\t\t\t\tif (image === undefined) {\n\t\t\t\t\treturn failed(\"image_unavailable\", `no image with reference ${imageRef} is available in this branch`, {\n\t\t\t\t\t\timageRef,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (api.modelInvocation === undefined) {\n\t\t\t\t\treturn failed(\"model_invocation_unavailable\", \"the host does not expose model invocation\", {\n\t\t\t\t\t\timageRef,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst signal = combineSignals([signalFrom(third) ?? signalFrom(second), api.sessionAbortSignal?.()]);\n\t\t\t\tif (signal?.aborted === true) return failed(\"cancelled\", \"image inspection was cancelled\", { imageRef });\n\t\t\t\tconst result = await api.modelInvocation.complete(\n\t\t\t\t\t{\n\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\ttarget: { provider: target.provider, id: target.id },\n\t\t\t\t\t\tcapability: \"visual_image\",\n\t\t\t\t\t\tsystemPrompt: `You inspect one image for another model. The image is the original, no crop${\n\t\t\t\t\t\t\timageSize === undefined ? \"\" : `, ${imageSize.width}x${imageSize.height} pixels`\n\t\t\t\t\t\t}. Answer only the question. Separate direct observation, inference, and uncertainty. Report any coordinates as normalized fractions in [0,1] relative to the full original image (origin top-left); use absolute pixels only when the question asks for them. Do not take over the main task or invent details that are not visible.`,\n\t\t\t\t\t\tprompt: question,\n\t\t\t\t\t\timages: [{ type: \"image\", data: image.data, mimeType: image.mimeType }],\n\t\t\t\t\t\tmaxTokens: 1200,\n\t\t\t\t\t},\n\t\t\t\t\tsignal === undefined ? {} : { signal },\n\t\t\t\t);\n\t\t\t\treturn resultFromInvocation(result, imageRef, { provider: target.provider, id: target.id }, imageSize);\n\t\t\t},\n\t\t});\n\t};\n}\n"]}
@@ -0,0 +1,3 @@
1
+ declare const _default: (api: import("@agent-forge/plugin-sdk").CapabilityAPI) => void | import("@agent-forge/plugin-sdk").CapabilityPluginSyncInstance;
2
+ export default _default;
3
+ //# sourceMappingURL=entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":";AAGA,wBAA6G","sourcesContent":["import type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { createImageInspectCapabilityFactoryV1 } from \"./capability.ts\";\n\nexport default ((api) => createImageInspectCapabilityFactoryV1()(api)) satisfies CapabilityPluginSyncFactory;\n"]}
package/dist/entry.js ADDED
@@ -0,0 +1,3 @@
1
+ import { createImageInspectCapabilityFactoryV1 } from "./capability.js";
2
+ export default ((api) => createImageInspectCapabilityFactoryV1()(api));
3
+ //# sourceMappingURL=entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qCAAqC,EAAE,MAAM,iBAAiB,CAAC;AAExE,eAAe,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,qCAAqC,EAAE,CAAC,GAAG,CAAC,CAAuC,CAAC","sourcesContent":["import type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { createImageInspectCapabilityFactoryV1 } from \"./capability.ts\";\n\nexport default ((api) => createImageInspectCapabilityFactoryV1()(api)) satisfies CapabilityPluginSyncFactory;\n"]}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Stable reference for an original image payload. Must stay identical to
3
+ * `imageReferenceV1` in packages/ai/src/api/transform-messages.ts: the model
4
+ * sees this value in the non-vision placeholder and passes it to image_inspect.
5
+ */
6
+ export declare function imageReferenceV1(data: string): string;
7
+ //# sourceMappingURL=image-ref.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-ref.d.ts","sourceRoot":"","sources":["../src/image-ref.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOrD","sourcesContent":["/**\n * Stable reference for an original image payload. Must stay identical to\n * `imageReferenceV1` in packages/ai/src/api/transform-messages.ts: the model\n * sees this value in the non-vision placeholder and passes it to image_inspect.\n */\nexport function imageReferenceV1(data: string): string {\n\tlet hash = 0x811c9dc5;\n\tfor (let index = 0; index < data.length; index++) {\n\t\thash ^= data.charCodeAt(index);\n\t\thash = Math.imul(hash, 0x01000193);\n\t}\n\treturn `image-${(hash >>> 0).toString(16).padStart(8, \"0\")}`;\n}\n"]}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Stable reference for an original image payload. Must stay identical to
3
+ * `imageReferenceV1` in packages/ai/src/api/transform-messages.ts: the model
4
+ * sees this value in the non-vision placeholder and passes it to image_inspect.
5
+ */
6
+ export function imageReferenceV1(data) {
7
+ let hash = 0x811c9dc5;
8
+ for (let index = 0; index < data.length; index++) {
9
+ hash ^= data.charCodeAt(index);
10
+ hash = Math.imul(hash, 0x01000193);
11
+ }
12
+ return `image-${(hash >>> 0).toString(16).padStart(8, "0")}`;
13
+ }
14
+ //# sourceMappingURL=image-ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-ref.js","sourceRoot":"","sources":["../src/image-ref.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAU;IACtD,IAAI,IAAI,GAAG,UAAU,CAAC;IACtB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QAClD,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,SAAS,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AAAA,CAC7D","sourcesContent":["/**\n * Stable reference for an original image payload. Must stay identical to\n * `imageReferenceV1` in packages/ai/src/api/transform-messages.ts: the model\n * sees this value in the non-vision placeholder and passes it to image_inspect.\n */\nexport function imageReferenceV1(data: string): string {\n\tlet hash = 0x811c9dc5;\n\tfor (let index = 0; index < data.length; index++) {\n\t\thash ^= data.charCodeAt(index);\n\t\thash = Math.imul(hash, 0x01000193);\n\t}\n\treturn `image-${(hash >>> 0).toString(16).padStart(8, \"0\")}`;\n}\n"]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Minimal image header reader for the original-image basis note.
3
+ *
4
+ * Vision providers may resize the image before the model sees it, so
5
+ * coordinates from the model must be anchored to a server-visible fact: the
6
+ * original image's dimensions. Only the header is read; no decoding.
7
+ */
8
+ export interface ImageSizeV1 {
9
+ readonly width: number;
10
+ readonly height: number;
11
+ }
12
+ export declare function imageSizeV1(base64: string, mimeType?: string): ImageSizeV1 | undefined;
13
+ //# sourceMappingURL=image-size.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-size.d.ts","sourceRoot":"","sources":["../src/image-size.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AA6DD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAgBtF","sourcesContent":["/**\n * Minimal image header reader for the original-image basis note.\n *\n * Vision providers may resize the image before the model sees it, so\n * coordinates from the model must be anchored to a server-visible fact: the\n * original image's dimensions. Only the header is read; no decoding.\n */\n\nexport interface ImageSizeV1 {\n\treadonly width: number;\n\treadonly height: number;\n}\n\nfunction pngSize(bytes: Buffer): ImageSizeV1 | undefined {\n\tif (bytes.length < 24) return undefined;\n\tconst signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];\n\tfor (let index = 0; index < signature.length; index++) {\n\t\tif (bytes[index] !== signature[index]) return undefined;\n\t}\n\tif (bytes.toString(\"ascii\", 12, 16) !== \"IHDR\") return undefined;\n\treturn { width: bytes.readUInt32BE(16), height: bytes.readUInt32BE(20) };\n}\n\nfunction gifSize(bytes: Buffer): ImageSizeV1 | undefined {\n\tif (bytes.length < 10) return undefined;\n\tconst header = bytes.toString(\"ascii\", 0, 6);\n\tif (header !== \"GIF87a\" && header !== \"GIF89a\") return undefined;\n\treturn { width: bytes.readUInt16LE(6), height: bytes.readUInt16LE(8) };\n}\n\nfunction jpegSize(bytes: Buffer): ImageSizeV1 | undefined {\n\tif (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8) return undefined;\n\tlet offset = 2;\n\twhile (offset + 4 <= bytes.length) {\n\t\tif (bytes[offset] !== 0xff) return undefined;\n\t\tlet marker = bytes[offset + 1] ?? 0;\n\t\twhile (marker === 0xff) {\n\t\t\toffset++;\n\t\t\tmarker = bytes[offset + 1] ?? 0;\n\t\t}\n\t\toffset += 2;\n\t\tif (marker === 0xd8 || marker === 0xd9 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) continue;\n\t\tif (offset + 2 > bytes.length) return undefined;\n\t\tconst length = bytes.readUInt16BE(offset);\n\t\tif (length < 2) return undefined;\n\t\tconst isStartOfFrame = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;\n\t\tif (isStartOfFrame) {\n\t\t\tif (offset + 7 > bytes.length) return undefined;\n\t\t\treturn { height: bytes.readUInt16BE(offset + 3), width: bytes.readUInt16BE(offset + 5) };\n\t\t}\n\t\toffset += length;\n\t}\n\treturn undefined;\n}\n\nfunction webpSize(bytes: Buffer): ImageSizeV1 | undefined {\n\tif (bytes.length < 30) return undefined;\n\tif (bytes.toString(\"ascii\", 0, 4) !== \"RIFF\" || bytes.toString(\"ascii\", 8, 12) !== \"WEBP\") return undefined;\n\tconst chunk = bytes.toString(\"ascii\", 12, 16);\n\tif (chunk === \"VP8X\") {\n\t\treturn { width: 1 + bytes.readUIntLE(24, 3), height: 1 + bytes.readUIntLE(27, 3) };\n\t}\n\tif (chunk === \"VP8 \") {\n\t\treturn { width: bytes.readUInt16LE(26) & 0x3fff, height: bytes.readUInt16LE(28) & 0x3fff };\n\t}\n\tif (chunk === \"VP8L\") {\n\t\tconst bits = bytes.readUInt32LE(21);\n\t\treturn { width: 1 + (bits & 0x3fff), height: 1 + ((bits >> 14) & 0x3fff) };\n\t}\n\treturn undefined;\n}\n\nexport function imageSizeV1(base64: string, mimeType?: string): ImageSizeV1 | undefined {\n\tlet bytes: Buffer;\n\ttry {\n\t\tbytes = Buffer.from(base64, \"base64\");\n\t} catch {\n\t\treturn undefined;\n\t}\n\tconst size =\n\t\tmimeType === \"image/gif\"\n\t\t\t? gifSize(bytes)\n\t\t\t: mimeType === \"image/jpeg\"\n\t\t\t\t? jpegSize(bytes)\n\t\t\t\t: mimeType === \"image/webp\"\n\t\t\t\t\t? webpSize(bytes)\n\t\t\t\t\t: (pngSize(bytes) ?? gifSize(bytes) ?? jpegSize(bytes) ?? webpSize(bytes));\n\treturn size !== undefined && size.width > 0 && size.height > 0 ? size : undefined;\n}\n"]}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Minimal image header reader for the original-image basis note.
3
+ *
4
+ * Vision providers may resize the image before the model sees it, so
5
+ * coordinates from the model must be anchored to a server-visible fact: the
6
+ * original image's dimensions. Only the header is read; no decoding.
7
+ */
8
+ function pngSize(bytes) {
9
+ if (bytes.length < 24)
10
+ return undefined;
11
+ const signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
12
+ for (let index = 0; index < signature.length; index++) {
13
+ if (bytes[index] !== signature[index])
14
+ return undefined;
15
+ }
16
+ if (bytes.toString("ascii", 12, 16) !== "IHDR")
17
+ return undefined;
18
+ return { width: bytes.readUInt32BE(16), height: bytes.readUInt32BE(20) };
19
+ }
20
+ function gifSize(bytes) {
21
+ if (bytes.length < 10)
22
+ return undefined;
23
+ const header = bytes.toString("ascii", 0, 6);
24
+ if (header !== "GIF87a" && header !== "GIF89a")
25
+ return undefined;
26
+ return { width: bytes.readUInt16LE(6), height: bytes.readUInt16LE(8) };
27
+ }
28
+ function jpegSize(bytes) {
29
+ if (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8)
30
+ return undefined;
31
+ let offset = 2;
32
+ while (offset + 4 <= bytes.length) {
33
+ if (bytes[offset] !== 0xff)
34
+ return undefined;
35
+ let marker = bytes[offset + 1] ?? 0;
36
+ while (marker === 0xff) {
37
+ offset++;
38
+ marker = bytes[offset + 1] ?? 0;
39
+ }
40
+ offset += 2;
41
+ if (marker === 0xd8 || marker === 0xd9 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7))
42
+ continue;
43
+ if (offset + 2 > bytes.length)
44
+ return undefined;
45
+ const length = bytes.readUInt16BE(offset);
46
+ if (length < 2)
47
+ return undefined;
48
+ const isStartOfFrame = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;
49
+ if (isStartOfFrame) {
50
+ if (offset + 7 > bytes.length)
51
+ return undefined;
52
+ return { height: bytes.readUInt16BE(offset + 3), width: bytes.readUInt16BE(offset + 5) };
53
+ }
54
+ offset += length;
55
+ }
56
+ return undefined;
57
+ }
58
+ function webpSize(bytes) {
59
+ if (bytes.length < 30)
60
+ return undefined;
61
+ if (bytes.toString("ascii", 0, 4) !== "RIFF" || bytes.toString("ascii", 8, 12) !== "WEBP")
62
+ return undefined;
63
+ const chunk = bytes.toString("ascii", 12, 16);
64
+ if (chunk === "VP8X") {
65
+ return { width: 1 + bytes.readUIntLE(24, 3), height: 1 + bytes.readUIntLE(27, 3) };
66
+ }
67
+ if (chunk === "VP8 ") {
68
+ return { width: bytes.readUInt16LE(26) & 0x3fff, height: bytes.readUInt16LE(28) & 0x3fff };
69
+ }
70
+ if (chunk === "VP8L") {
71
+ const bits = bytes.readUInt32LE(21);
72
+ return { width: 1 + (bits & 0x3fff), height: 1 + ((bits >> 14) & 0x3fff) };
73
+ }
74
+ return undefined;
75
+ }
76
+ export function imageSizeV1(base64, mimeType) {
77
+ let bytes;
78
+ try {
79
+ bytes = Buffer.from(base64, "base64");
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ const size = mimeType === "image/gif"
85
+ ? gifSize(bytes)
86
+ : mimeType === "image/jpeg"
87
+ ? jpegSize(bytes)
88
+ : mimeType === "image/webp"
89
+ ? webpSize(bytes)
90
+ : (pngSize(bytes) ?? gifSize(bytes) ?? jpegSize(bytes) ?? webpSize(bytes));
91
+ return size !== undefined && size.width > 0 && size.height > 0 ? size : undefined;
92
+ }
93
+ //# sourceMappingURL=image-size.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-size.js","sourceRoot":"","sources":["../src/image-size.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Minimal image header reader for the original-image basis note.\n *\n * Vision providers may resize the image before the model sees it, so\n * coordinates from the model must be anchored to a server-visible fact: the\n * original image's dimensions. Only the header is read; no decoding.\n */\n\nexport interface ImageSizeV1 {\n\treadonly width: number;\n\treadonly height: number;\n}\n\nfunction pngSize(bytes: Buffer): ImageSizeV1 | undefined {\n\tif (bytes.length < 24) return undefined;\n\tconst signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];\n\tfor (let index = 0; index < signature.length; index++) {\n\t\tif (bytes[index] !== signature[index]) return undefined;\n\t}\n\tif (bytes.toString(\"ascii\", 12, 16) !== \"IHDR\") return undefined;\n\treturn { width: bytes.readUInt32BE(16), height: bytes.readUInt32BE(20) };\n}\n\nfunction gifSize(bytes: Buffer): ImageSizeV1 | undefined {\n\tif (bytes.length < 10) return undefined;\n\tconst header = bytes.toString(\"ascii\", 0, 6);\n\tif (header !== \"GIF87a\" && header !== \"GIF89a\") return undefined;\n\treturn { width: bytes.readUInt16LE(6), height: bytes.readUInt16LE(8) };\n}\n\nfunction jpegSize(bytes: Buffer): ImageSizeV1 | undefined {\n\tif (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8) return undefined;\n\tlet offset = 2;\n\twhile (offset + 4 <= bytes.length) {\n\t\tif (bytes[offset] !== 0xff) return undefined;\n\t\tlet marker = bytes[offset + 1] ?? 0;\n\t\twhile (marker === 0xff) {\n\t\t\toffset++;\n\t\t\tmarker = bytes[offset + 1] ?? 0;\n\t\t}\n\t\toffset += 2;\n\t\tif (marker === 0xd8 || marker === 0xd9 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) continue;\n\t\tif (offset + 2 > bytes.length) return undefined;\n\t\tconst length = bytes.readUInt16BE(offset);\n\t\tif (length < 2) return undefined;\n\t\tconst isStartOfFrame = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;\n\t\tif (isStartOfFrame) {\n\t\t\tif (offset + 7 > bytes.length) return undefined;\n\t\t\treturn { height: bytes.readUInt16BE(offset + 3), width: bytes.readUInt16BE(offset + 5) };\n\t\t}\n\t\toffset += length;\n\t}\n\treturn undefined;\n}\n\nfunction webpSize(bytes: Buffer): ImageSizeV1 | undefined {\n\tif (bytes.length < 30) return undefined;\n\tif (bytes.toString(\"ascii\", 0, 4) !== \"RIFF\" || bytes.toString(\"ascii\", 8, 12) !== \"WEBP\") return undefined;\n\tconst chunk = bytes.toString(\"ascii\", 12, 16);\n\tif (chunk === \"VP8X\") {\n\t\treturn { width: 1 + bytes.readUIntLE(24, 3), height: 1 + bytes.readUIntLE(27, 3) };\n\t}\n\tif (chunk === \"VP8 \") {\n\t\treturn { width: bytes.readUInt16LE(26) & 0x3fff, height: bytes.readUInt16LE(28) & 0x3fff };\n\t}\n\tif (chunk === \"VP8L\") {\n\t\tconst bits = bytes.readUInt32LE(21);\n\t\treturn { width: 1 + (bits & 0x3fff), height: 1 + ((bits >> 14) & 0x3fff) };\n\t}\n\treturn undefined;\n}\n\nexport function imageSizeV1(base64: string, mimeType?: string): ImageSizeV1 | undefined {\n\tlet bytes: Buffer;\n\ttry {\n\t\tbytes = Buffer.from(base64, \"base64\");\n\t} catch {\n\t\treturn undefined;\n\t}\n\tconst size =\n\t\tmimeType === \"image/gif\"\n\t\t\t? gifSize(bytes)\n\t\t\t: mimeType === \"image/jpeg\"\n\t\t\t\t? jpegSize(bytes)\n\t\t\t\t: mimeType === \"image/webp\"\n\t\t\t\t\t? webpSize(bytes)\n\t\t\t\t\t: (pngSize(bytes) ?? gifSize(bytes) ?? jpegSize(bytes) ?? webpSize(bytes));\n\treturn size !== undefined && size.width > 0 && size.height > 0 ? size : undefined;\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export { createImageInspectCapabilityFactoryV1 } from "./capability.ts";
2
+ export { imageReferenceV1 } from "./image-ref.ts";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qCAAqC,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["export { createImageInspectCapabilityFactoryV1 } from \"./capability.ts\";\nexport { imageReferenceV1 } from \"./image-ref.ts\";\n"]}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { createImageInspectCapabilityFactoryV1 } from "./capability.js";
2
+ export { imageReferenceV1 } from "./image-ref.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qCAAqC,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["export { createImageInspectCapabilityFactoryV1 } from \"./capability.ts\";\nexport { imageReferenceV1 } from \"./image-ref.ts\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
- "name": "@ai-agent-forge/plugin-image-inspect",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@ai-agent-forge/plugin-image-inspect",
3
+ "version": "0.88.16",
4
+ "description": "Supplemental image inspection capability for non-vision models",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "MANUAL.md",
17
+ "agent-forge.json",
18
+ "plugin.json",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "clean": "shx rm -rf dist",
23
+ "build": "tsgo -p tsconfig.build.json",
24
+ "test": "vitest --run",
25
+ "prepublishOnly": "npm run clean && npm run build",
26
+ "test:coverage": "vitest --run --coverage"
27
+ },
28
+ "keywords": [
29
+ "agent-forge",
30
+ "plugin",
31
+ "vision",
32
+ "image"
33
+ ],
34
+ "author": "Agent Forge contributors",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/agent-forge/agent-forge.git",
39
+ "directory": "plugins/image-inspect"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/agent-forge/agent-forge/issues"
43
+ },
44
+ "homepage": "https://github.com/agent-forge/agent-forge#readme",
45
+ "engines": {
46
+ "node": ">=22.19.0"
47
+ },
48
+ "dependencies": {
49
+ "typebox": "1.3.27",
50
+ "@ai-agent-forge/plugin-sdk": "^0.88.16"
51
+ },
52
+ "devDependencies": {
53
+ "shx": "0.3.4",
54
+ "vitest": "4.1.11"
55
+ }
56
+ }
package/plugin.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "id": "agent-forge.plugin.image-inspect",
3
+ "version": "0.88.16",
4
+ "apiVersion": "1-draft",
5
+ "entry": "./dist/entry.js",
6
+ "hostVersion": ">=0.84.4",
7
+ "description": "补充识图:主模型无原生视觉时,用会话已选视觉模型查看原图并回答指定问题",
8
+ "activation": "always",
9
+ "requiredCapabilities": [
10
+ "tools"
11
+ ],
12
+ "provides": [
13
+ {
14
+ "id": "vision.inspect",
15
+ "version": 1,
16
+ "kind": "contribution"
17
+ }
18
+ ]
19
+ }