Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
06ec553
Merge pull request #3678 from lidge-jun/codex/promote-main-243-01a07240
lidge-jun Sep 5, 2026
116c2ac
Merge commit '44ea9576e27c6be8be7f13a86e32bb349368c54d' into codex/re…
invalid-email-address Sep 6, 2026
07b48da
Merge pull request #3785 from lidge-jun/codex/release-244-main-07c0
lidge-jun Sep 6, 2026
bcdf559
chore(release): promote validated 2.45.0 to main [skip ci]
invalid-email-address Sep 6, 2026
b0900e5
chore(release): promote 2.45.0 to main (#3813)
lidge-jun Sep 6, 2026
3970601
chore(release): prepare 2.46.0 stable promotion
invalid-email-address Sep 7, 2026
bba6322
Merge pull request #3851 from lidge-jun/codex/release-246-main
lidge-jun Sep 7, 2026
3d53e5f
release: prepare 2.47.0 from audited regression candidate
invalid-email-address Sep 7, 2026
eda8754
Merge commit '48ab3e1e66cfa6e0c873de2fafa4540ac61d6c7d' into codex/re…
invalid-email-address Sep 7, 2026
f9e3515
Merge commit '57252193b' into codex/release-247-main
invalid-email-address Sep 7, 2026
6f71931
release: promote 2.47.0 to main (#3929)
lidge-jun Sep 7, 2026
9a60256
Merge commit 'd0737cff3' into codex/release-247-main-final
invalid-email-address Sep 7, 2026
9e9b1d3
Merge commit 'f48c322c0' into codex/release-247-main-final
invalid-email-address Sep 7, 2026
947bae9
Merge commit '0d7652ad1' into codex/release-247-main-final
invalid-email-address Sep 7, 2026
f7f890f
release: apply final roster correction to main (#3933)
lidge-jun Sep 7, 2026
544ebee
release: promote 2.48.0 to main
invalid-email-address Sep 8, 2026
d24ff57
release: set main channel version 2.48.0
invalid-email-address Sep 8, 2026
9a27e86
Merge pull request #4011 from lidge-jun/codex/release-248-main
lidge-jun Sep 8, 2026
62849df
release: promote verified 2.49.0 product tree to main
lidge-jun Sep 9, 2026
2f3f736
Merge pull request #4117 from lidge-jun/codex/release-249-main-01a08498
lidge-jun Sep 9, 2026
3a3de88
release: promote verified 2.50.0 product tree to main
lidge-jun Sep 10, 2026
2d4d7a2
Merge pull request #4195 from lidge-jun/codex/release-250-main-01a08a81
lidge-jun Sep 10, 2026
cf456e8
release: promote verified 2.51.0 product tree to main
lidge-jun Sep 11, 2026
c155cc7
Merge pull request #4271 from lidge-jun/codex/release-251-main
lidge-jun Sep 11, 2026
95c4875
release: promote verified 2.52.0 product tree to main
lidge-jun Sep 12, 2026
4d37c35
Merge pull request #4407 from lidge-jun/codex/release-2520-main
lidge-jun Sep 12, 2026
641b05a
release: promote verified 2.53.0 product tree to main
lidge-jun Sep 13, 2026
aa05b3e
Merge pull request #4507 from lidge-jun/codex/release-2530-main
lidge-jun Sep 13, 2026
8e532c5
release: promote verified 2.54.0 product tree to main
lidge-jun Sep 13, 2026
9f7397e
Merge pull request #4540 from lidge-jun/codex/release-2540-main
lidge-jun Sep 13, 2026
8a1705a
fix(devin): bound remote image prompt references
luvs01 Sep 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions src/adapters/devin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,17 @@ function textFromParts(content: string | OcxContentPart[] | undefined): string {
return content.map((part) => (part.type === "text" ? part.text : "")).filter(Boolean).join("\n");
}

const MAX_DEVIN_REMOTE_IMAGE_URL_CHARS = 8_192;

function boundedDevinRemoteImageReference(imageUrl: string): string | undefined {
if (imageUrl.length > MAX_DEVIN_REMOTE_IMAGE_URL_CHARS) return undefined;
try {
return new URL(imageUrl).protocol === "https:" ? imageUrl : undefined;
} catch {
return undefined;
}
}

/**
* Convert inbound content parts to the multimodal shape the wire encoder accepts.
*
Expand All @@ -217,9 +228,10 @@ function textFromParts(content: string | OcxContentPart[] | undefined): string {
* text-only string and a message whose only content was an image was dropped
* entirely, which is why a pasted screenshot killed the turn and the only
* workaround was running OCR before sending. A data: URL carries everything
* field #10 needs; a remote https URL cannot be inlined without a fetch, so it
* stays as an explicit text reference rather than pretending the model can see
* a picture it cannot. Video has no Devin field and is skipped.
* field #10 needs; a bounded remote https URL cannot be inlined without a fetch,
* so it stays as an explicit text reference rather than pretending the model can
* see a picture it cannot. Unsupported and oversized references become a fixed
* omission marker, never attacker-sized prompt text. Video has no Devin field.
*/
function mapOcxContentToWire(content: string | OcxContentPart[] | undefined): string | ContentPart[] {
if (typeof content === "string" || !Array.isArray(content)) return content ?? "";
Expand All @@ -230,7 +242,13 @@ function mapOcxContentToWire(content: string | OcxContentPart[] | undefined): st
} else if (part.type === "image") {
const m = part.imageUrl.match(/^data:([^;]+);base64,(.+)$/);
if (m) out.push({ type: "image", mimeType: m[1]!, base64Data: m[2]! });
else out.push({ type: "text", text: `[image url: ${part.imageUrl}]` });
else {
const remoteReference = boundedDevinRemoteImageReference(part.imageUrl);
out.push({
type: "text",
text: remoteReference ? `[image url: ${remoteReference}]` : "[image omitted: unsupported or oversized URL]",
});
}
}
}
return out;
Expand Down
4 changes: 4 additions & 0 deletions structure/adapters/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,7 @@ medium/high/max UID before accepting a suffix already present in the model id.
The merged `devin` provider uses this resolver for every account, whichever login
path minted the credential. Omitted effort preserves an explicit
variant; unrelated model families retain their existing suffix precedence.

## Devin image boundary

The registered Devin implementation in `src/adapters/devin.ts` maps data URLs to its native image field. Its textual fallback accepts only bounded HTTPS references and emits a fixed-size omission marker for unsupported or oversized values.
4 changes: 4 additions & 0 deletions structure/data-planes/inbound-compat.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,7 @@ Translated Chat request construction uses the [inline-image budget](../transport
The [explicit model-capability contract](../config.md#explicit-per-model-capability-declarations) preserves operator declarations through provider storage and catalog capture; it does not infer upstream capability or change this surface's routing behavior.

Provider-scoped approval reviewer settings are projected by the [catalog owner](../catalog.md#provider-scoped-approval-reviewer); this surface retains its existing routing, transport and account-selection behavior.

## Devin image references

When compatible inbound content reaches `src/adapters/devin.ts`, inline data URLs use Devin's native image field. Only bounded HTTPS remote references become prompt text; all other remote values become a fixed-size omission marker.
4 changes: 4 additions & 0 deletions structure/providers/chat-compat.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,7 @@ byte-limit boundaries.
Canonical Spark Lite metadata follows the final serialized model and surviving nonempty Lite tool catalog; see [Responses transport](../transports/responses.md).

Translated Chat request construction uses the [inline-image budget](../transports/streaming-health.md#translated-chat-inline-image-budget); the shared normalizer counts retained bytes even when a wire-specific drop callback keeps the image attached.

## Devin image compatibility

For Responses content routed through `src/adapters/devin.ts`, inline data URLs use Devin's image field. Bounded HTTPS references remain visible as text, while unsupported or oversized references are represented by a fixed-size omission marker.
4 changes: 4 additions & 0 deletions structure/providers/cursor.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,7 @@ Combo child requests normalize effort and thinking controls against the selected
`src/adapters/cursor.ts` surfaces the first bare context overflow before attempting conversation remint on later eligible requests. `cursorClientThreadOwner` recognizes both client thread aliases; `src/adapters/cursor/thread-continuity.ts` limits recovery to three remints per retained identity-scoped owner, with a one-hour idle TTL and 2,048-entry bound. Conversation-only requests have no stable owner and do not automatically remint. Quota/rate errors, tool-result resumes, partial output, local side effects, isolated helper/shadow requests and compaction remain fail-closed. Isolated requests neither consume the parent allowance nor invalidate its checkpoint. Eligible overflow checks refresh existing retention timestamps and LRU position even after the cap is exhausted, without allocating absent scopes. Retention expiry, eviction or process restart resets the in-memory allowance; this is not a persistent lifetime cap or semantic-progress policy.

Translated Chat request construction uses the [inline-image budget](../transports/streaming-health.md#translated-chat-inline-image-budget); the shared normalizer counts retained bytes even when a wire-specific drop callback keeps the image attached.

## Cross-adapter image boundary

Cursor transport behavior remains independent from the Devin mapping in `src/adapters/devin.ts`: Devin restricts text-rendered remote image references to bounded HTTPS URLs and uses a fixed-size marker otherwise.
4 changes: 4 additions & 0 deletions structure/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,7 @@ Exact [model input declarations](config.md#explicit-per-model-capability-declara
Provider-scoped approval reviewer settings are projected by the [catalog owner](catalog.md#provider-scoped-approval-reviewer); this surface retains its existing routing, transport and account-selection behavior.

Renamed fixed-key providers receive [missing reasoning metadata](catalog.md#renamed-destination-reasoning-metadata) during derivation; explicit per-model entries and provider defaults retain precedence.

### Devin multimodal boundary

`src/adapters/devin.ts` forwards data-URL images on the native image field. Remote references become prompt text only when they are bounded HTTPS URLs; unsupported or oversized values become a fixed-size omission marker.
4 changes: 4 additions & 0 deletions structure/transports/byte-accounting.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ plus exact separators, without joining a second full JSON array. `src/lib/admiss
truncates diagnostic text at UTF-8 code-point boundaries without allocating arrays per character;
byte sizing retains TextEncoder's coercion behavior for legacy non-string runtime callers.
These optimizations do not add request queues, retry policies, or RSS-based admission gates.

## Devin remote-image references

`src/adapters/devin.ts` bounds remote image URLs before converting them to text. Values outside that bound become a constant-size marker, so adapter serialization cannot turn a fixed image admission charge into attacker-sized prompt buffers.
4 changes: 4 additions & 0 deletions structure/transports/inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,7 @@ The [explicit model-capability contract](../config.md#explicit-per-model-capabil
Provider-scoped approval reviewer settings are projected by the [catalog owner](../catalog.md#provider-scoped-approval-reviewer); this surface retains its existing routing, transport and account-selection behavior.

Renamed fixed-key providers receive [missing reasoning metadata](../catalog.md#renamed-destination-reasoning-metadata) during derivation; explicit per-model entries and provider defaults retain precedence.

## Devin multimodal transport

`src/adapters/devin.ts` maps inline data URLs to Devin's native image field. It preserves bounded HTTPS image references as text and replaces unsupported or oversized references with a fixed-size marker before protobuf construction.
2 changes: 1 addition & 1 deletion structure/transports/responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ Pool quota producers and account commands follow the [bounded raw-observation co

Live sideband admission and its bounded upstream handshake follow the [runtime contract](../runtime.md#live-sideband-handshake); the ordinary Responses WebSocket exchange remains separate.

Translated Chat request construction uses the [inline-image budget](streaming-health.md#translated-chat-inline-image-budget); the shared normalizer counts retained bytes even when a wire-specific drop callback keeps the image attached.
Translated Chat request construction uses the [inline-image budget](streaming-health.md#translated-chat-inline-image-budget); the shared normalizer counts retained bytes even when a wire-specific drop callback keeps the image attached. At the `src/adapters/` boundary, Devin renders only bounded HTTPS image references as text and replaces all other remote values with a fixed-size omission marker.

The [explicit model-capability contract](../config.md#explicit-per-model-capability-declarations) preserves operator declarations through provider storage and catalog capture; it does not infer upstream capability or change this surface's routing behavior.

Expand Down
11 changes: 11 additions & 0 deletions tests/providers/devin-image-passthrough.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ describe("user image passthrough", () => {
const user = items.find(i => i.role === "user")!;
expect(user.content).toEqual([{ type: "text", text: "[image url: https://example.com/pic.png]" }]);
});

test("an oversized remote image URL is not copied into prompt text", () => {
const attackerControlledSuffix = "a".repeat(9_000);
const items = mapOcxMessagesToDevin(parsedWith([{
role: "user",
content: [{ type: "image", imageUrl: `https://example.com/${attackerControlledSuffix}` }],
}]));
const user = items.find(i => i.role === "user")!;
expect(user.content).toEqual([{ type: "text", text: "[image omitted: unsupported or oversized URL]" }]);
expect(JSON.stringify(user.content)).not.toContain(attackerControlledSuffix);
});
});

describe("tool-result image passthrough", () => {
Expand Down
Loading