summaryrefslogtreecommitdiffhomepage
path: root/packages/transport-contract/src
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-27 20:48:24 +0900
committerAdam Malczewski <[email protected]>2026-06-27 20:48:24 +0900
commit04356c8678ae8dd1d7ddca2d0460b514116adc2e (patch)
tree6c81894ef02d062570b12f4d3a871e58600dcb9c /packages/transport-contract/src
parent3184b10e614ce6249c83aa111368e98f6689f456 (diff)
parentb24ed99e89bc657e8c98c7cef8608e0c0b7594da (diff)
downloaddispatch-04356c8678ae8dd1d7ddca2d0460b514116adc2e.tar.gz
dispatch-04356c8678ae8dd1d7ddca2d0460b514116adc2e.zip
Merge branch 'feature/vision-handoff' into dev
# Conflicts: # packages/session-orchestrator/src/extension.ts # packages/session-orchestrator/src/orchestrator.ts
Diffstat (limited to 'packages/transport-contract/src')
-rw-r--r--packages/transport-contract/src/contract.types.test.ts42
-rw-r--r--packages/transport-contract/src/index.ts41
2 files changed, 83 insertions, 0 deletions
diff --git a/packages/transport-contract/src/contract.types.test.ts b/packages/transport-contract/src/contract.types.test.ts
index 9d3d904..34ff544 100644
--- a/packages/transport-contract/src/contract.types.test.ts
+++ b/packages/transport-contract/src/contract.types.test.ts
@@ -20,6 +20,7 @@ import type {
LspServerState,
LspStatusResponse,
McpStatusResponse,
+ ModelsResponse,
SetConversationComputerRequest,
SetCwdRequest,
SetWorkspaceDefaultComputerRequest,
@@ -55,6 +56,18 @@ const _chatWithoutComputer: ChatRequest = {
message: "hello",
};
+// ─── ChatRequest.images (additive optional) ──────────────────────────────────
+
+const _chatWithImages: ChatRequest = {
+ message: "What's in this screenshot?",
+ images: [{ url: "data:image/png;base64,iVBORw0KGgo=", mimeType: "image/png" }],
+};
+
+const _chatWithHttpImage: ChatRequest = {
+ message: "analyze this",
+ images: [{ url: "https://example.com/diagram.png" }],
+};
+
// ─── Computer list / single response ─────────────────────────────────────────
const _computer: Computer = {
@@ -255,6 +268,35 @@ describe("transport-contract types compile and are exported", () => {
expect(_chatWithComputer.computerId).toBe("prod-box");
});
+ // ─── ChatRequest.images (additive optional) ──────────────────────────────
+
+ it("ChatRequest: images is additive optional (omittable)", () => {
+ expect(_chatWithoutComputer.images).toBeUndefined();
+ });
+
+ it("ChatRequest: carries images (data URL) when set", () => {
+ expect(_chatWithImages.images).toHaveLength(1);
+ expect(_chatWithImages.images?.[0]?.url).toContain("base64");
+ expect(_chatWithImages.images?.[0]?.mimeType).toBe("image/png");
+ });
+
+ it("ChatRequest: carries images (http URL, mimeType optional)", () => {
+ expect(_chatWithHttpImage.images?.[0]?.url).toBe("https://example.com/diagram.png");
+ expect(_chatWithHttpImage.images?.[0]?.mimeType).toBeUndefined();
+ });
+
+ it("ModelsResponse: ModelMetadata carries optional vision flag", () => {
+ const resp: ModelsResponse = {
+ models: ["umans/kimi-k2.7", "umans/glm-5.2"],
+ modelInfo: {
+ "umans/kimi-k2.7": { contextWindow: 200000, vision: true },
+ "umans/glm-5.2": { contextWindow: 128000 },
+ },
+ };
+ expect(resp.modelInfo?.["umans/kimi-k2.7"]?.vision).toBe(true);
+ expect(resp.modelInfo?.["umans/glm-5.2"]?.vision).toBeUndefined();
+ });
+
// ─── Computers ───────────────────────────────────────────────────────────
it("ComputerListResponse: carries entries with usage counts", () => {
diff --git a/packages/transport-contract/src/index.ts b/packages/transport-contract/src/index.ts
index 400d9d5..d5f3000 100644
--- a/packages/transport-contract/src/index.ts
+++ b/packages/transport-contract/src/index.ts
@@ -26,6 +26,7 @@ import type {
ComputerEntry,
ConversationMeta,
ConversationStatus,
+ ImageInput,
QueuedMessage,
ReasoningEffort,
StoredChunk,
@@ -41,6 +42,8 @@ export type {
ComputerEntry,
ConversationMeta,
ConversationStatus,
+ ImageChunk,
+ ImageInput,
QueuedMessage,
ReasoningEffort,
StepMetrics,
@@ -68,6 +71,19 @@ export interface ChatRequest {
readonly message: string;
/**
+ * Images attached to this turn (e.g. a user-pasted screenshot). Each entry's
+ * `url` is a base64 data URL (`data:image/…;base64,…`) or an `http(s)://`
+ * URL. The server converts these to `image` chunks on the persisted user
+ * message. For a VISION-capable model (e.g. kimi), the images are passed
+ * through to the provider natively. For a NON-vision model (e.g. glm-5.2),
+ * the server's vision handoff transcribes each image to a text description
+ * (via a vision-capable model) and feeds that text instead — so a text-only
+ * model can still reason about the image's contents. Optional — omit for a
+ * text-only turn (backward compatible).
+ */
+ readonly images?: readonly ImageInput[];
+
+ /**
* The model to use, as a model name in `<credentialName>/<model>` form — one
* of the exact strings returned by `GET /models`. Omit to use the server's
* default credential + model.
@@ -124,6 +140,14 @@ export interface ModelsResponse {
/** Per-model metadata returned alongside the model catalog. */
export interface ModelMetadata {
readonly contextWindow?: number;
+ /**
+ * Whether this model can natively accept image input (vision/multimodal).
+ * When `true`, image chunks in a user message are passed through to the
+ * provider. When `false`/absent, the server's vision handoff transcribes
+ * images to text before the model sees them. A client may use this to show a
+ * vision badge in the model picker. Optional — absent when unknown.
+ */
+ readonly vision?: boolean;
}
/**
@@ -387,6 +411,23 @@ export interface SystemPromptVariablesResponse {
readonly variables: readonly SystemPromptVariable[];
}
+// ─── Vision settings (global) ──────────────────────────────────────────────────
+
+/**
+ * Response of `GET /settings/vision` — the global vision configuration shared
+ * across all conversations and vision models.
+ */
+export interface VisionSettingsResponse {
+ readonly imageLimit: number;
+ readonly compactionModel: string | null;
+}
+
+/** Body of `PUT /settings/vision` — a partial update. */
+export interface SetVisionSettingsRequest {
+ readonly imageLimit?: number;
+ readonly compactionModel?: string | null;
+}
+
// ─── Message queue (steering) ─────────────────────────────────────────────────
/**