fix: image resizer wasm loading, reenable image resizing (#26805)
This commit is contained in:
parent
c50d2b3656
commit
981e00971a
@ -14,7 +14,7 @@ export const Image = Schema.Struct({
|
|||||||
description: "Maximum image height before resizing or rejecting the attachment (default: 2000)",
|
description: "Maximum image height before resizing or rejecting the attachment (default: 2000)",
|
||||||
}),
|
}),
|
||||||
max_base64_bytes: Schema.optional(PositiveInt).annotate({
|
max_base64_bytes: Schema.optional(PositiveInt).annotate({
|
||||||
description: "Maximum base64 payload bytes for an image attachment (default: 4718592)",
|
description: "Maximum base64 payload bytes for an image attachment (default: 5242880)",
|
||||||
}),
|
}),
|
||||||
}).annotate({ identifier: "ImageAttachmentConfig" })
|
}).annotate({ identifier: "ImageAttachmentConfig" })
|
||||||
export type Image = Schema.Schema.Type<typeof Image>
|
export type Image = Schema.Schema.Type<typeof Image>
|
||||||
|
|||||||
@ -1,21 +1,24 @@
|
|||||||
import { Config } from "@/config/config"
|
import { Config } from "@/config/config"
|
||||||
import type { MessageV2 } from "@/session/message-v2"
|
import type { MessageV2 } from "@/session/message-v2"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
|
import photonWasm from "@silvia-odwyer/photon-node/photon_rs_bg.wasm" with { type: "file" }
|
||||||
import { Context, Effect, Layer, Schema } from "effect"
|
import { Context, Effect, Layer, Schema } from "effect"
|
||||||
|
import path from "node:path"
|
||||||
|
import { fileURLToPath } from "node:url"
|
||||||
|
|
||||||
const MAX_BASE64_BYTES = 4.5 * 1024 * 1024
|
const MAX_BASE64_BYTES = 5 * 1024 * 1024
|
||||||
const MAX_WIDTH = 2000
|
const MAX_WIDTH = 2000
|
||||||
const MAX_HEIGHT = 2000
|
const MAX_HEIGHT = 2000
|
||||||
const AUTO_RESIZE = true
|
const AUTO_RESIZE = true
|
||||||
const JPEG_QUALITIES = [80, 85, 70, 55, 40]
|
const JPEG_QUALITIES = [80, 85, 70, 55, 40]
|
||||||
const log = Log.create({ service: "image" })
|
const log = Log.create({ service: "image" })
|
||||||
|
|
||||||
export class PhotonUnavailableError extends Schema.TaggedErrorClass<PhotonUnavailableError>()(
|
export class ResizerUnavailableError extends Schema.TaggedErrorClass<ResizerUnavailableError>()(
|
||||||
"ImagePhotonUnavailableError",
|
"ImageResizerUnavailableError",
|
||||||
{},
|
{},
|
||||||
) {
|
) {
|
||||||
override get message() {
|
override get message() {
|
||||||
return "Photon image processor is unavailable"
|
return "Image resizer is unavailable"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +49,7 @@ export class SizeError extends Schema.TaggedErrorClass<SizeError>()("ImageSizeEr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Error = PhotonUnavailableError | InvalidDataUrlError | DecodeError | SizeError
|
export type Error = ResizerUnavailableError | InvalidDataUrlError | DecodeError | SizeError
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly normalize: (input: MessageV2.FilePart) => Effect.Effect<MessageV2.FilePart, Error>
|
readonly normalize: (input: MessageV2.FilePart) => Effect.Effect<MessageV2.FilePart, Error>
|
||||||
@ -59,18 +62,15 @@ export const layer = Layer.effect(
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const config = yield* Config.Service
|
const config = yield* Config.Service
|
||||||
const loadPhoton = yield* Effect.cached(
|
const loadPhoton = yield* Effect.cached(
|
||||||
Effect.promise(async () => {
|
Effect.sync(() => {
|
||||||
try {
|
// Patched photon-node reads this during module init so Bun compiled binaries use the embedded wasm path.
|
||||||
const photonWasm = (await import("@silvia-odwyer/photon-node/photon_rs_bg.wasm", { with: { type: "file" } }))
|
;(globalThis as typeof globalThis & { __OPENCODE_PHOTON_WASM_PATH?: string }).__OPENCODE_PHOTON_WASM_PATH =
|
||||||
.default
|
path.isAbsolute(photonWasm) ? photonWasm : fileURLToPath(new URL(photonWasm, import.meta.url))
|
||||||
// Patched photon-node reads this during module init so Bun compiled binaries use the embedded wasm path.
|
}).pipe(
|
||||||
;(globalThis as typeof globalThis & { __OPENCODE_PHOTON_WASM_PATH?: string }).__OPENCODE_PHOTON_WASM_PATH =
|
Effect.andThen(() => Effect.tryPromise(() => import("@silvia-odwyer/photon-node"))),
|
||||||
photonWasm
|
Effect.tapError((error) => Effect.sync(() => log.warn("failed to load photon", { error }))),
|
||||||
return await import("@silvia-odwyer/photon-node")
|
Effect.mapError(() => new ResizerUnavailableError()),
|
||||||
} catch {
|
),
|
||||||
return null
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const normalize = Effect.fn("Image.normalize")(function* (input: MessageV2.FilePart) {
|
const normalize = Effect.fn("Image.normalize")(function* (input: MessageV2.FilePart) {
|
||||||
@ -85,30 +85,26 @@ export const layer = Layer.effect(
|
|||||||
return yield* new InvalidDataUrlError({ url: input.url })
|
return yield* new InvalidDataUrlError({ url: input.url })
|
||||||
|
|
||||||
const base64 = input.url.slice(input.url.indexOf(";base64,") + ";base64,".length)
|
const base64 = input.url.slice(input.url.indexOf(";base64,") + ";base64,".length)
|
||||||
const photon = yield* loadPhoton
|
const bytes = Buffer.byteLength(base64, "utf8")
|
||||||
if (!photon) return yield* new PhotonUnavailableError()
|
|
||||||
|
|
||||||
const decoded = yield* Effect.sync(() => {
|
const photon = yield* loadPhoton
|
||||||
try {
|
|
||||||
return photon.PhotonImage.new_from_byteslice(Buffer.from(base64, "base64"))
|
const decoded = yield* Effect.try({
|
||||||
} catch {
|
try: () => photon.PhotonImage.new_from_byteslice(Buffer.from(base64, "base64")),
|
||||||
return undefined
|
catch: (error) => {
|
||||||
}
|
log.warn("failed to decode image", { error })
|
||||||
|
return new DecodeError()
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if (!decoded) return yield* new DecodeError()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const originalWidth = decoded.get_width()
|
const originalWidth = decoded.get_width()
|
||||||
const originalHeight = decoded.get_height()
|
const originalHeight = decoded.get_height()
|
||||||
if (
|
if (originalWidth <= info.maxWidth && originalHeight <= info.maxHeight && bytes <= info.maxBase64Bytes)
|
||||||
originalWidth <= info.maxWidth &&
|
|
||||||
originalHeight <= info.maxHeight &&
|
|
||||||
Buffer.byteLength(base64, "utf8") <= info.maxBase64Bytes
|
|
||||||
)
|
|
||||||
return input
|
return input
|
||||||
if (!info.autoResize)
|
if (!info.autoResize)
|
||||||
return yield* new SizeError({
|
return yield* new SizeError({
|
||||||
bytes: Buffer.byteLength(base64, "utf8"),
|
bytes,
|
||||||
max: info.maxBase64Bytes,
|
max: info.maxBase64Bytes,
|
||||||
width: originalWidth,
|
width: originalWidth,
|
||||||
height: originalHeight,
|
height: originalHeight,
|
||||||
@ -159,7 +155,7 @@ export const layer = Layer.effect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return yield* new SizeError({
|
return yield* new SizeError({
|
||||||
bytes: Buffer.byteLength(base64, "utf8"),
|
bytes,
|
||||||
max: info.maxBase64Bytes,
|
max: info.maxBase64Bytes,
|
||||||
width: originalWidth,
|
width: originalWidth,
|
||||||
height: originalHeight,
|
height: originalHeight,
|
||||||
|
|||||||
@ -405,14 +405,18 @@ export const layer: Layer.Layer<
|
|||||||
typeof attachment.mime === "string" &&
|
typeof attachment.mime === "string" &&
|
||||||
typeof attachment.url === "string",
|
typeof attachment.url === "string",
|
||||||
)
|
)
|
||||||
// temporarily disabled
|
|
||||||
// const normalized = yield* Effect.forEach(toolAttachments, (attachment) =>
|
|
||||||
// attachment.mime.startsWith("image/")
|
|
||||||
// ? image.normalize(attachment).pipe(Effect.exit)
|
|
||||||
// : Effect.succeed(Exit.succeed<MessageV2.FilePart>(attachment)),
|
|
||||||
// )
|
|
||||||
const normalized = yield* Effect.forEach(toolAttachments, (attachment) =>
|
const normalized = yield* Effect.forEach(toolAttachments, (attachment) =>
|
||||||
Effect.succeed(Exit.succeed<MessageV2.FilePart>(attachment)),
|
attachment.mime.startsWith("image/")
|
||||||
|
? image
|
||||||
|
.normalize(attachment)
|
||||||
|
.pipe(
|
||||||
|
Effect.catchIf(
|
||||||
|
(error) => error instanceof Image.ResizerUnavailableError,
|
||||||
|
() => Effect.succeed(attachment),
|
||||||
|
),
|
||||||
|
Effect.exit,
|
||||||
|
)
|
||||||
|
: Effect.succeed(Exit.succeed<MessageV2.FilePart>(attachment)),
|
||||||
)
|
)
|
||||||
const omitted = normalized.filter(Exit.isFailure).length
|
const omitted = normalized.filter(Exit.isFailure).length
|
||||||
const attachments = normalized.filter(Exit.isSuccess).map((item) => item.value)
|
const attachments = normalized.filter(Exit.isSuccess).map((item) => item.value)
|
||||||
|
|||||||
@ -42,6 +42,7 @@ import { Shell } from "@/shell/shell"
|
|||||||
import { ShellID } from "@/tool/shell/id"
|
import { ShellID } from "@/tool/shell/id"
|
||||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||||
import { Truncate } from "@/tool/truncate"
|
import { Truncate } from "@/tool/truncate"
|
||||||
|
import { Image } from "@/image/image"
|
||||||
import { decodeDataUrl } from "@/util/data-url"
|
import { decodeDataUrl } from "@/util/data-url"
|
||||||
import { Process } from "@/util/process"
|
import { Process } from "@/util/process"
|
||||||
import { Cause, Effect, Exit, Latch, Layer, Option, Scope, Context, Schema, Types } from "effect"
|
import { Cause, Effect, Exit, Latch, Layer, Option, Scope, Context, Schema, Types } from "effect"
|
||||||
@ -165,10 +166,10 @@ function referenceTextPart(input: {
|
|||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly cancel: (sessionID: SessionID) => Effect.Effect<void>
|
readonly cancel: (sessionID: SessionID) => Effect.Effect<void>
|
||||||
readonly prompt: (input: PromptInput) => Effect.Effect<MessageV2.WithParts>
|
readonly prompt: (input: PromptInput) => Effect.Effect<MessageV2.WithParts, Image.Error>
|
||||||
readonly loop: (input: LoopInput) => Effect.Effect<MessageV2.WithParts>
|
readonly loop: (input: LoopInput) => Effect.Effect<MessageV2.WithParts>
|
||||||
readonly shell: (input: ShellInput) => Effect.Effect<MessageV2.WithParts, Session.BusyError>
|
readonly shell: (input: ShellInput) => Effect.Effect<MessageV2.WithParts, Session.BusyError>
|
||||||
readonly command: (input: CommandInput) => Effect.Effect<MessageV2.WithParts>
|
readonly command: (input: CommandInput) => Effect.Effect<MessageV2.WithParts, Image.Error>
|
||||||
readonly resolvePromptParts: (template: string) => Effect.Effect<PromptInput["parts"]>
|
readonly resolvePromptParts: (template: string) => Effect.Effect<PromptInput["parts"]>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,6 +194,7 @@ export const layer = Layer.effect(
|
|||||||
const lsp = yield* LSP.Service
|
const lsp = yield* LSP.Service
|
||||||
const registry = yield* ToolRegistry.Service
|
const registry = yield* ToolRegistry.Service
|
||||||
const truncate = yield* Truncate.Service
|
const truncate = yield* Truncate.Service
|
||||||
|
const image = yield* Image.Service
|
||||||
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
||||||
const scope = yield* Scope.Scope
|
const scope = yield* Scope.Scope
|
||||||
const instruction = yield* Instruction.Service
|
const instruction = yield* Instruction.Service
|
||||||
@ -211,7 +213,7 @@ export const layer = Layer.effect(
|
|||||||
return {
|
return {
|
||||||
cancel: (sessionID: SessionID) => cancel(sessionID),
|
cancel: (sessionID: SessionID) => cancel(sessionID),
|
||||||
resolvePromptParts: (template: string) => resolvePromptParts(template),
|
resolvePromptParts: (template: string) => resolvePromptParts(template),
|
||||||
prompt: (input: PromptInput) => prompt(input),
|
prompt: (input: PromptInput) => prompt(input).pipe(Effect.catch(Effect.die)),
|
||||||
} satisfies TaskPromptOps
|
} satisfies TaskPromptOps
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1478,7 +1480,16 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
|||||||
{ message: info, parts: resolvedParts },
|
{ message: info, parts: resolvedParts },
|
||||||
)
|
)
|
||||||
|
|
||||||
const parts = resolvedParts
|
const parts = yield* Effect.forEach(resolvedParts, (part) =>
|
||||||
|
part.type === "file" && part.mime.startsWith("image/")
|
||||||
|
? image.normalize(part).pipe(
|
||||||
|
Effect.catchIf(
|
||||||
|
(error) => error instanceof Image.ResizerUnavailableError,
|
||||||
|
() => Effect.succeed(part),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Effect.succeed(part),
|
||||||
|
)
|
||||||
|
|
||||||
const parsed = decodeMessageInfo(info, { errors: "all", propertyOrder: "original" })
|
const parsed = decodeMessageInfo(info, { errors: "all", propertyOrder: "original" })
|
||||||
if (Exit.isFailure(parsed)) {
|
if (Exit.isFailure(parsed)) {
|
||||||
@ -1599,26 +1610,26 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
|||||||
return { info, parts }
|
return { info, parts }
|
||||||
}, Effect.scoped)
|
}, Effect.scoped)
|
||||||
|
|
||||||
const prompt: (input: PromptInput) => Effect.Effect<MessageV2.WithParts> = Effect.fn("SessionPrompt.prompt")(
|
const prompt: (input: PromptInput) => Effect.Effect<MessageV2.WithParts, Image.Error> = Effect.fn(
|
||||||
function* (input: PromptInput) {
|
"SessionPrompt.prompt",
|
||||||
const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie)
|
)(function* (input: PromptInput) {
|
||||||
yield* revert.cleanup(session)
|
const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie)
|
||||||
const message = yield* createUserMessage(input)
|
yield* revert.cleanup(session)
|
||||||
yield* sessions.touch(input.sessionID)
|
const message = yield* createUserMessage(input)
|
||||||
|
yield* sessions.touch(input.sessionID)
|
||||||
|
|
||||||
const permissions: Permission.Ruleset = []
|
const permissions: Permission.Ruleset = []
|
||||||
for (const [t, enabled] of Object.entries(input.tools ?? {})) {
|
for (const [t, enabled] of Object.entries(input.tools ?? {})) {
|
||||||
permissions.push({ permission: t, action: enabled ? "allow" : "deny", pattern: "*" })
|
permissions.push({ permission: t, action: enabled ? "allow" : "deny", pattern: "*" })
|
||||||
}
|
}
|
||||||
if (permissions.length > 0) {
|
if (permissions.length > 0) {
|
||||||
session.permission = permissions
|
session.permission = permissions
|
||||||
yield* sessions.setPermission({ sessionID: session.id, permission: permissions })
|
yield* sessions.setPermission({ sessionID: session.id, permission: permissions })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.noReply === true) return message
|
if (input.noReply === true) return message
|
||||||
return yield* loop({ sessionID: input.sessionID })
|
return yield* loop({ sessionID: input.sessionID })
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const lastAssistant = Effect.fnUntraced(function* (sessionID: SessionID) {
|
const lastAssistant = Effect.fnUntraced(function* (sessionID: SessionID) {
|
||||||
const match = yield* sessions.findMessage(sessionID, (m) => m.info.role !== "user").pipe(Effect.orDie)
|
const match = yield* sessions.findMessage(sessionID, (m) => m.info.role !== "user").pipe(Effect.orDie)
|
||||||
@ -2019,6 +2030,7 @@ export const defaultLayer = Layer.suspend(() =>
|
|||||||
Layer.provide(Session.defaultLayer),
|
Layer.provide(Session.defaultLayer),
|
||||||
Layer.provide(SessionRevert.defaultLayer),
|
Layer.provide(SessionRevert.defaultLayer),
|
||||||
Layer.provide(SessionSummary.defaultLayer),
|
Layer.provide(SessionSummary.defaultLayer),
|
||||||
|
Layer.provide(Image.defaultLayer),
|
||||||
Layer.provide(
|
Layer.provide(
|
||||||
Layer.mergeAll(
|
Layer.mergeAll(
|
||||||
Agent.defaultLayer,
|
Agent.defaultLayer,
|
||||||
|
|||||||
BIN
packages/opencode/test/image/fixtures/picture-5mb-base64.png
Normal file
BIN
packages/opencode/test/image/fixtures/picture-5mb-base64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 MiB |
@ -2,6 +2,7 @@ import { describe, expect } from "bun:test"
|
|||||||
import { Cause, Effect, Exit, Layer } from "effect"
|
import { Cause, Effect, Exit, Layer } from "effect"
|
||||||
import { Image } from "@/image/image"
|
import { Image } from "@/image/image"
|
||||||
import { MessageID, PartID, SessionID } from "@/session/schema"
|
import { MessageID, PartID, SessionID } from "@/session/schema"
|
||||||
|
import path from "node:path"
|
||||||
import { TestConfig } from "../fixture/config"
|
import { TestConfig } from "../fixture/config"
|
||||||
import { testEffect } from "../lib/effect"
|
import { testEffect } from "../lib/effect"
|
||||||
|
|
||||||
@ -57,6 +58,46 @@ describe("Image", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("resizes images that fit the byte limit but exceed dimension limits", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const photon = yield* Effect.promise(() => import("@silvia-odwyer/photon-node"))
|
||||||
|
const source = new photon.PhotonImage(new Uint8Array(Array.from({ length: 9_000 * 4 }, () => 255)), 9_000, 1)
|
||||||
|
const image = yield* Image.Service
|
||||||
|
const result = yield* image.normalize(part("image/png", Buffer.from(source.get_bytes()).toString("base64")))
|
||||||
|
const resized = photon.PhotonImage.new_from_byteslice(
|
||||||
|
Buffer.from(result.url.slice(result.url.indexOf(";base64,") + ";base64,".length), "base64"),
|
||||||
|
)
|
||||||
|
|
||||||
|
source.free()
|
||||||
|
expect(resized.get_width()).toBeLessThanOrEqual(2_000)
|
||||||
|
expect(resized.get_height()).toBeLessThanOrEqual(2_000)
|
||||||
|
resized.free()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("resizes the 5MB base64 picture fixture", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const photon = yield* Effect.promise(() => import("@silvia-odwyer/photon-node"))
|
||||||
|
const data = Buffer.from(
|
||||||
|
yield* Effect.promise(() =>
|
||||||
|
Bun.file(path.join(import.meta.dir, "fixtures", "picture-5mb-base64.png")).arrayBuffer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
const input = part("image/png", data.toString("base64"))
|
||||||
|
const image = yield* Image.Service
|
||||||
|
const result = yield* image.normalize(input)
|
||||||
|
const base64 = result.url.slice(result.url.indexOf(";base64,") + ";base64,".length)
|
||||||
|
const resized = photon.PhotonImage.new_from_byteslice(Buffer.from(base64, "base64"))
|
||||||
|
|
||||||
|
expect(input.url.slice(input.url.indexOf(";base64,") + ";base64,".length).length).toBe(5 * 1024 * 1024)
|
||||||
|
expect(result.url).not.toBe(input.url)
|
||||||
|
expect(base64.length).toBeLessThan(5 * 1024 * 1024)
|
||||||
|
expect(resized.get_width()).toBeLessThanOrEqual(2_000)
|
||||||
|
expect(resized.get_height()).toBeLessThanOrEqual(2_000)
|
||||||
|
resized.free()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
tiny.effect("fails with a typed size error when no resized candidate fits", () =>
|
tiny.effect("fails with a typed size error when no resized candidate fits", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const photon = yield* Effect.promise(() => import("@silvia-odwyer/photon-node"))
|
const photon = yield* Effect.promise(() => import("@silvia-odwyer/photon-node"))
|
||||||
|
|||||||
@ -393,6 +393,35 @@ You can also configure [local models](/docs/models#local). [Learn more](/docs/mo
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Image attachments
|
||||||
|
|
||||||
|
OpenCode normalizes image attachments before sending them to the model. By default, images are resized when they exceed `2000x2000` pixels or `5242880` base64 bytes.
|
||||||
|
|
||||||
|
Configure image attachment limits with the `attachment.image` option:
|
||||||
|
|
||||||
|
```json title="opencode.json"
|
||||||
|
{
|
||||||
|
"$schema": "https://opencode.ai/config.json",
|
||||||
|
"attachment": {
|
||||||
|
"image": {
|
||||||
|
"auto_resize": true,
|
||||||
|
"max_width": 2000,
|
||||||
|
"max_height": 2000,
|
||||||
|
"max_base64_bytes": 5242880
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `auto_resize` - Resize images that exceed the configured limits before provider requests. Set to `false` to reject oversized images instead.
|
||||||
|
- `max_width` - Maximum image width in pixels before resizing or rejection.
|
||||||
|
- `max_height` - Maximum image height in pixels before resizing or rejection.
|
||||||
|
- `max_base64_bytes` - Maximum encoded image payload size. This is the base64 payload size, not the original file size.
|
||||||
|
|
||||||
|
If an image still cannot fit after resizing, OpenCode omits oversized tool-result images or fails oversized user-provided images with an image size error.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
#### Provider-Specific Options
|
#### Provider-Specific Options
|
||||||
|
|
||||||
Some providers support additional configuration options beyond the generic `timeout` and `apiKey` settings.
|
Some providers support additional configuration options beyond the generic `timeout` and `apiKey` settings.
|
||||||
|
|||||||
@ -1,11 +1,282 @@
|
|||||||
diff --git a/photon_rs.js b/photon_rs.js
|
diff --git a/photon_rs.js b/photon_rs.js
|
||||||
index 8f4144d..b83e9a9 100644
|
index 8f4144d..29a964a 100644
|
||||||
--- a/photon_rs.js
|
--- a/photon_rs.js
|
||||||
+++ b/photon_rs.js
|
+++ b/photon_rs.js
|
||||||
@@ -4509,7 +4509,8 @@ module.exports.__wbindgen_init_externref_table = function() {
|
@@ -1,6 +1,7 @@
|
||||||
;
|
|
||||||
|
let imports = {};
|
||||||
|
-imports['__wbindgen_placeholder__'] = module.exports;
|
||||||
|
+const __wbindgen_placeholder__ = {};
|
||||||
|
+imports['__wbindgen_placeholder__'] = __wbindgen_placeholder__;
|
||||||
|
let wasm;
|
||||||
|
const { TextEncoder, TextDecoder } = require(`util`);
|
||||||
|
|
||||||
|
@@ -4272,12 +4273,12 @@ class Rgba {
|
||||||
|
}
|
||||||
|
module.exports.Rgba = Rgba;
|
||||||
|
|
||||||
|
-module.exports.__wbg_new_abda76e883ba8a5f = function() {
|
||||||
|
+__wbindgen_placeholder__.__wbg_new_abda76e883ba8a5f = function() {
|
||||||
|
const ret = new Error();
|
||||||
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) {
|
||||||
|
const ret = arg1.stack;
|
||||||
|
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
const len1 = WASM_VECTOR_LEN;
|
||||||
|
@@ -4285,7 +4286,7 @@ module.exports.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) {
|
||||||
|
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_error_f851667af71bcfc6 = function(arg0, arg1) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_error_f851667af71bcfc6 = function(arg0, arg1) {
|
||||||
|
let deferred0_0;
|
||||||
|
let deferred0_1;
|
||||||
|
try {
|
||||||
|
@@ -4297,7 +4298,7 @@ module.exports.__wbg_error_f851667af71bcfc6 = function(arg0, arg1) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_instanceof_Window_c4b70662a0d2c5ec = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_instanceof_Window_c4b70662a0d2c5ec = function(arg0) {
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = arg0 instanceof Window;
|
||||||
|
@@ -4308,42 +4309,42 @@ module.exports.__wbg_instanceof_Window_c4b70662a0d2c5ec = function(arg0) {
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_document_e5c1786dea6542e4 = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_document_e5c1786dea6542e4 = function(arg0) {
|
||||||
|
const ret = arg0.document;
|
||||||
|
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_body_e70ae6abd01ae584 = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_body_e70ae6abd01ae584 = function(arg0) {
|
||||||
|
const ret = arg0.body;
|
||||||
|
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_createElement_5d4c76f218b78145 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_createElement_5d4c76f218b78145 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||||
|
const ret = arg0.createElement(getStringFromWasm0(arg1, arg2));
|
||||||
|
return ret;
|
||||||
|
}, arguments) };
|
||||||
|
|
||||||
|
-module.exports.__wbg_width_4c6f0048d64cf86b = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_width_4c6f0048d64cf86b = function(arg0) {
|
||||||
|
const ret = arg0.width;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_height_21f0d3fd8f753394 = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_height_21f0d3fd8f753394 = function(arg0) {
|
||||||
|
const ret = arg0.height;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_width_79e0847ed5883b03 = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_width_79e0847ed5883b03 = function(arg0) {
|
||||||
|
const ret = arg0.width;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_height_e4e4e4779f8feac0 = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_height_e4e4e4779f8feac0 = function(arg0) {
|
||||||
|
const ret = arg0.height;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_data_fda507064d127f5b = function(arg0, arg1) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_data_fda507064d127f5b = function(arg0, arg1) {
|
||||||
|
const ret = arg1.data;
|
||||||
|
const ptr1 = passArray8ToWasm0(ret, wasm.__wbindgen_malloc);
|
||||||
|
const len1 = WASM_VECTOR_LEN;
|
||||||
|
@@ -4351,12 +4352,12 @@ module.exports.__wbg_data_fda507064d127f5b = function(arg0, arg1) {
|
||||||
|
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_newwithu8clampedarrayandsh_1fddccb3a94a5e05 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_newwithu8clampedarrayandsh_1fddccb3a94a5e05 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||||
|
const ret = new ImageData(getClampedArrayU8FromWasm0(arg0, arg1), arg2 >>> 0, arg3 >>> 0);
|
||||||
|
return ret;
|
||||||
|
}, arguments) };
|
||||||
|
|
||||||
|
-module.exports.__wbg_instanceof_CanvasRenderingContext2d_3abbe7ec7af32cae = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_instanceof_CanvasRenderingContext2d_3abbe7ec7af32cae = function(arg0) {
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = arg0 instanceof CanvasRenderingContext2D;
|
||||||
|
@@ -4367,24 +4368,24 @@ module.exports.__wbg_instanceof_CanvasRenderingContext2d_3abbe7ec7af32cae = fun
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_drawImage_fede06db74e39a60 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_drawImage_fede06db74e39a60 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||||
|
arg0.drawImage(arg1, arg2, arg3);
|
||||||
|
}, arguments) };
|
||||||
|
|
||||||
|
-module.exports.__wbg_drawImage_f395c8e43c79a909 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_drawImage_f395c8e43c79a909 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {
|
||||||
|
arg0.drawImage(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
|
||||||
|
}, arguments) };
|
||||||
|
|
||||||
|
-module.exports.__wbg_getImageData_5e1c242046e6b59e = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_getImageData_5e1c242046e6b59e = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||||
|
const ret = arg0.getImageData(arg1, arg2, arg3, arg4);
|
||||||
|
return ret;
|
||||||
|
}, arguments) };
|
||||||
|
|
||||||
|
-module.exports.__wbg_putImageData_a8b3e177ee06d521 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_putImageData_a8b3e177ee06d521 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||||
|
arg0.putImageData(arg1, arg2, arg3);
|
||||||
|
}, arguments) };
|
||||||
|
|
||||||
|
-module.exports.__wbg_instanceof_HtmlCanvasElement_25d964a0dde6717e = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_instanceof_HtmlCanvasElement_25d964a0dde6717e = function(arg0) {
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = arg0 instanceof HTMLCanvasElement;
|
||||||
|
@@ -4395,93 +4396,93 @@ module.exports.__wbg_instanceof_HtmlCanvasElement_25d964a0dde6717e = function(a
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_width_dc225e55343b745e = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_width_dc225e55343b745e = function(arg0) {
|
||||||
|
const ret = arg0.width;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_setwidth_488780db69b08846 = function(arg0, arg1) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_setwidth_488780db69b08846 = function(arg0, arg1) {
|
||||||
|
arg0.width = arg1 >>> 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_height_3a8bec2f3fe71b26 = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_height_3a8bec2f3fe71b26 = function(arg0) {
|
||||||
|
const ret = arg0.height;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_setheight_1761808c18403921 = function(arg0, arg1) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_setheight_1761808c18403921 = function(arg0, arg1) {
|
||||||
|
arg0.height = arg1 >>> 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_getContext_fc99dbd3a9a7e318 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_getContext_fc99dbd3a9a7e318 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||||
|
const ret = arg0.getContext(getStringFromWasm0(arg1, arg2));
|
||||||
|
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||||
|
}, arguments) };
|
||||||
|
|
||||||
|
-module.exports.__wbg_settextContent_f82a86a8df347e1c = function(arg0, arg1, arg2) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_settextContent_f82a86a8df347e1c = function(arg0, arg1, arg2) {
|
||||||
|
arg0.textContent = arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2);
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_appendChild_fa3b00dade9fc4cf = function() { return handleError(function (arg0, arg1) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_appendChild_fa3b00dade9fc4cf = function() { return handleError(function (arg0, arg1) {
|
||||||
|
const ret = arg0.appendChild(arg1);
|
||||||
|
return ret;
|
||||||
|
}, arguments) };
|
||||||
|
|
||||||
|
-module.exports.__wbg_newnoargs_e643855c6572a4a8 = function(arg0, arg1) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_newnoargs_e643855c6572a4a8 = function(arg0, arg1) {
|
||||||
|
const ret = new Function(getStringFromWasm0(arg0, arg1));
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_call_f96b398515635514 = function() { return handleError(function (arg0, arg1) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_call_f96b398515635514 = function() { return handleError(function (arg0, arg1) {
|
||||||
|
const ret = arg0.call(arg1);
|
||||||
|
return ret;
|
||||||
|
}, arguments) };
|
||||||
|
|
||||||
|
-module.exports.__wbg_self_b9aad7f1c618bfaf = function() { return handleError(function () {
|
||||||
|
+__wbindgen_placeholder__.__wbg_self_b9aad7f1c618bfaf = function() { return handleError(function () {
|
||||||
|
const ret = self.self;
|
||||||
|
return ret;
|
||||||
|
}, arguments) };
|
||||||
|
|
||||||
|
-module.exports.__wbg_window_55e469842c98b086 = function() { return handleError(function () {
|
||||||
|
+__wbindgen_placeholder__.__wbg_window_55e469842c98b086 = function() { return handleError(function () {
|
||||||
|
const ret = window.window;
|
||||||
|
return ret;
|
||||||
|
}, arguments) };
|
||||||
|
|
||||||
|
-module.exports.__wbg_globalThis_d0957e302752547e = function() { return handleError(function () {
|
||||||
|
+__wbindgen_placeholder__.__wbg_globalThis_d0957e302752547e = function() { return handleError(function () {
|
||||||
|
const ret = globalThis.globalThis;
|
||||||
|
return ret;
|
||||||
|
}, arguments) };
|
||||||
|
|
||||||
|
-module.exports.__wbg_global_ae2f87312b8987fb = function() { return handleError(function () {
|
||||||
|
+__wbindgen_placeholder__.__wbg_global_ae2f87312b8987fb = function() { return handleError(function () {
|
||||||
|
const ret = global.global;
|
||||||
|
return ret;
|
||||||
|
}, arguments) };
|
||||||
|
|
||||||
|
-module.exports.__wbindgen_is_undefined = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbindgen_is_undefined = function(arg0) {
|
||||||
|
const ret = arg0 === undefined;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_buffer_fcbfb6d88b2732e9 = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_buffer_fcbfb6d88b2732e9 = function(arg0) {
|
||||||
|
const ret = arg0.buffer;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_new_bc5d9aad3f9ac80e = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_new_bc5d9aad3f9ac80e = function(arg0) {
|
||||||
|
const ret = new Uint8Array(arg0);
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_set_4b3aa8445ac1e91c = function(arg0, arg1, arg2) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_set_4b3aa8445ac1e91c = function(arg0, arg1, arg2) {
|
||||||
|
arg0.set(arg1, arg2 >>> 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbg_length_d9c4ded7e708c6a1 = function(arg0) {
|
||||||
|
+__wbindgen_placeholder__.__wbg_length_d9c4ded7e708c6a1 = function(arg0) {
|
||||||
|
const ret = arg0.length;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbindgen_debug_string = function(arg0, arg1) {
|
||||||
|
+__wbindgen_placeholder__.__wbindgen_debug_string = function(arg0, arg1) {
|
||||||
|
const ret = debugString(arg1);
|
||||||
|
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
const len1 = WASM_VECTOR_LEN;
|
||||||
|
@@ -4489,16 +4490,16 @@ module.exports.__wbindgen_debug_string = function(arg0, arg1) {
|
||||||
|
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbindgen_throw = function(arg0, arg1) {
|
||||||
|
+__wbindgen_placeholder__.__wbindgen_throw = function(arg0, arg1) {
|
||||||
|
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbindgen_memory = function() {
|
||||||
|
+__wbindgen_placeholder__.__wbindgen_memory = function() {
|
||||||
|
const ret = wasm.memory;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
-module.exports.__wbindgen_init_externref_table = function() {
|
||||||
|
+__wbindgen_placeholder__.__wbindgen_init_externref_table = function() {
|
||||||
|
const table = wasm.__wbindgen_export_2;
|
||||||
|
const offset = table.grow(4);
|
||||||
|
table.set(0, undefined);
|
||||||
|
@@ -4509,7 +4510,8 @@ module.exports.__wbindgen_init_externref_table = function() {
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
-const path = require('path').join(__dirname, 'photon_rs_bg.wasm');
|
-const path = require('path').join(__dirname, 'photon_rs_bg.wasm');
|
||||||
+// Allow opencode's Bun compiled binary to point photon-node at its embedded wasm asset.
|
+// Allow opencode's Bun compiled binary to point photon-node at its embedded wasm asset.
|
||||||
+const path = globalThis.__OPENCODE_PHOTON_WASM_PATH || require('path').join(__dirname, 'photon_rs_bg.wasm');
|
+const path = globalThis.__OPENCODE_PHOTON_WASM_PATH || require('path').join(__dirname, 'photon_rs_bg.wasm');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user