diff --git a/api/openapi.yaml b/api/openapi.yaml index ce98a1a..54eb954 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -318,6 +318,43 @@ paths: "404": { $ref: "#/components/responses/Error" } "409": { $ref: "#/components/responses/Error" } + /api/blobs: + post: + operationId: uploadBlob + description: > + Stores an image for use in a message. The body is the raw image; its type is detected from the bytes + and must be PNG, JPEG, GIF or WebP. At most 5 MiB. + requestBody: + required: true + content: + image/png: { schema: { type: string, format: binary } } + image/jpeg: { schema: { type: string, format: binary } } + image/gif: { schema: { type: string, format: binary } } + image/webp: { schema: { type: string, format: binary } } + responses: + "201": + description: The stored blob. + content: + application/json: + schema: { $ref: "#/components/schemas/Blob" } + "400": { $ref: "#/components/responses/Error" } + "413": { $ref: "#/components/responses/Error" } + + /api/blobs/{blobId}: + parameters: + - { name: blobId, in: path, required: true, schema: { type: string } } + get: + operationId: getBlob + responses: + "200": + description: The blob's bytes, with its content type. + content: + image/png: { schema: { type: string, format: binary } } + image/jpeg: { schema: { type: string, format: binary } } + image/gif: { schema: { type: string, format: binary } } + image/webp: { schema: { type: string, format: binary } } + "404": { $ref: "#/components/responses/Error" } + /api/inbox: get: operationId: listInbox @@ -537,16 +574,33 @@ components: properties: prompt: { type: string } model: { type: string } + images: + type: array + items: { type: string } + description: Blob ids from uploadBlob, sent to the agent as images alongside the prompt. MessageCreate: type: object required: [text] properties: - text: { type: string } + text: { type: string, description: May be empty when images are given. } + images: + type: array + items: { type: string } + description: Blob ids from uploadBlob, sent to the agent as images alongside the text. model: type: string description: Model for this turn and later ones. When it differs from the session's model, the session switches before the message is sent. + Blob: + type: object + required: [id, content_type, created_by, created_at] + properties: + id: { type: string } + content_type: { type: string } + created_by: { type: string } + created_at: { type: string, format: date-time } + Attachment: type: object required: [kind] diff --git a/docs/claude-protocol.md b/docs/claude-protocol.md index 3212768..627de76 100644 --- a/docs/claude-protocol.md +++ b/docs/claude-protocol.md @@ -25,7 +25,7 @@ NDJSON both ways: one JSON object per line on stdin and stdout. Non-JSON stdout ## Messages -- User turn, written to stdin: `{"type":"user","message":{"role":"user","content":"..."},"parent_tool_use_id":null,"session_id":"..."}`. +- User turn, written to stdin: `{"type":"user","message":{"role":"user","content":"..."},"parent_tool_use_id":null,"session_id":"..."}`. With images, `content` is an array of Anthropic content blocks instead: `{"type":"image","source":{"type":"base64","media_type":"image/png","data":"..."}}` for each image, then `{"type":"text","text":"..."}`. - Stdout events: `system` (subtype `init` carries `session_id`, `model`, tools, MCP server status), `assistant` and `user` (Anthropic message content blocks: `text`, `tool_use`, `tool_result`), `result` (ends a turn; `subtype` is `success` or `error_during_execution` after an interrupt, with cost, duration, turn count). - `--include-partial-messages` adds `stream_event` lines wrapping the raw API stream (`message_start`, `content_block_start`, `content_block_delta` with `text_delta`, `content_block_stop`, `message_delta`, `message_stop`), each with `parent_tool_use_id` (set inside subagents). The complete `assistant` event for a block still arrives, before that block's `content_block_stop`. Transcripts leave `stream_event` lines out. - The `init` event arrives with the first turn, not at launch, so the session ID is unknown until the first message is sent. diff --git a/docs/operations.md b/docs/operations.md index 12a9233..94b3669 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -13,7 +13,7 @@ ## Data -State lives in the `byre` directory under Go's `os.UserConfigDir()` (on macOS, `~/Library/Application Support/byre`): `byre.db` and `worktrees//`. Task branches are `byre/` in the project's repo. +State lives in the `byre` directory under Go's `os.UserConfigDir()` (on macOS, `~/Library/Application Support/byre`): `byre.db`, `worktrees//`, and `blobs/` (uploaded images; the `blobs` table maps each id to its content type and its path relative to this directory). Task branches are `byre/` in the project's repo. Schema changes are the `migrations` slice in `internal/store/store.go`: named entries, each with a short kebab-case name saying what it does. On open, byre applies, in slice order, every entry whose name isn't in the `migrations` table, each in its own transaction that also records the name. byre is deployed from task branches that each append their own migrations, so after a merge a migration can land before others the database has already applied, and it still runs. Give a new migration a name no other branch will use, and make any data migration safe to run twice. `sqlite3 byre.db 'SELECT * FROM migrations'` lists what has been applied. A database from before the table existed is moved over on open: the first `PRAGMA user_version` entries are recorded as applied, and `user_version` is no longer read or written. @@ -54,6 +54,8 @@ curl -H 'Tailscale-User-Login: you@example.com' -H 'Tailscale-User-Name: You' ht `POST /api/tasks/{id}/sessions` starts a Claude session in the task's worktree and sends the prompt as its first turn; `POST /api/sessions/{id}/messages` starts later turns and `POST /api/sessions/{id}/interrupt` ends the current one. A task has at most one session that isn't `completed`. A message may carry `model`; when it differs from the session's, byre writes it to `agent_sessions.model`, records a `session.model` event (shown as a note), and sends the live CLI a `set_model` control request before the message, so the switch applies from that turn and survives a resume (`--model`) or compaction. The composer's model menu sends `model` only when the choice differs from the session's model, or from the `session.context` model when the session has none. The session's commits use the git identity of the user who created it, so that user must have set one with `PUT /api/me`. +Images: `POST /api/blobs` takes a raw image body (type sniffed from the bytes, PNG, JPEG, GIF or WebP only, at most 5 MiB, `maxBlobBytes` in `internal/server/blobs.go`) and returns its id; `GET /api/blobs/{id}` serves it. A message or session prompt may carry `images`, a list of blob ids (unknown ids are a 400); a message may then have empty text. byre reads each blob when the turn is sent and passes it to Claude as a base64 `image` content block before the text (`claude.Session.Send`). The `user.message` event records the ids as `images`, and a queued turn keeps them in `turn_queue.images`. In the composer, pasting or dropping an image adds a thumbnail with a × to remove it; images upload when the message is sent, and the transcript shows them on the user's message, each linking to the full image. + Compaction is vendor-agnostic: `POST /api/sessions/{id}/compact` (the transcript's **Compact** button, enabled while the session is idle) sends the agent `handoffPrompt` (`internal/agents/compact.go`) as a `byre` message, asking for a handoff covering the goal, decisions and why, current state, dead ends, open questions and files. That turn files no inbox summary and isn't bounced for questions. When it ends normally, its `turn.ended` carries `handoff: true`, the session is completed, and a new session on the same task and model starts with `handoffPreamble` plus the handoff as its first message (actor `byre`). The context meter tracks the new session. Once the handoff turn ends, the transcript folds the request and everything the agent did in that turn into one collapsed note. If byre restarts during the handoff turn, the continuation is lost and the old session simply goes idle. Agents spawn parallel work with the `create_task` MCP tool (`internal/agents/tasks.go`): `title`, `prompt`, `base` and optional `model` (default: the caller's session model). It creates a task in the caller's project exactly as `POST /api/projects/{id}/tasks` does, and starts a session there with `prompt` as the first message (actor `agent`), created by the caller's session creator so commits use that user's git identity. `base` is the agent's choice: `default_branch` starts from the project's default branch, `this_task` from the caller's worktree `HEAD`, which leaves out uncommitted edits from the current turn. The tool returns the new task's id at once; the new session is independent of the caller. diff --git a/docs/plan.md b/docs/plan.md index 5b533af..4b3dad0 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -477,7 +477,7 @@ Record transcripts from the spike as the seed of the contract test suite. - **Status: done**, and running as a service on the Mac Studio. Observability is exposed through `GET /api/admin/status` only; the SPA has no admin view yet. - **M1 — One agent end to end (Claude).** Sessions, streaming, messaging, the interface coverage for Claude, the `ask_human` MCP tool, turn summaries, inbox with all views and response types, web push notifications, restart-and-resume, concurrency caps. - **Done:** sessions, streaming, messaging, interrupt; `ask_human` with validation; each turn's final message filed to the inbox as its summary; inbox with global, project, and task views and every response type, answered as one batch and delivered to the agent as one message; restart-and-resume; global and per-vendor caps with queueing; rate limits as their own inbox state; the web UI (two panes, task switcher, transcript, inbox) with Markdown rendering; the session instructions from "Session configuration", given as an appended system prompt and a per-turn reminder; per-turn commits by byre; bouncing questions asked in plain prose. - - **Not done:** image input (see "Interface coverage"); web push notifications. Compaction is done, as a handoff into a fresh session; model switching is done, per message. + - **Not done:** web push notifications. Compaction is done, as a handoff into a fresh session; model switching is done, per message; image input is done, pasted or dropped into the composer and stored as blobs. - **Later:** subagent views and Mermaid rendering. - **M2 — Seeing the work.** Temp-index diffs and stats, snapshots, timeline scrubbing, terminals, file browser, previews. - **Done:** temp-index diffs and stats, with a Changes view. diff --git a/internal/agents/compact.go b/internal/agents/compact.go index 0cac76f..2cfc043 100644 --- a/internal/agents/compact.go +++ b/internal/agents/compact.go @@ -45,6 +45,6 @@ func (m *Manager) handoff(ctx context.Context, sessionID, handoff string) error return err } - _, err = m.create(ctx, task, creator, handoffPreamble+handoff, sess.Model, "byre") + _, err = m.create(ctx, task, creator, handoffPreamble+handoff, sess.Model, "byre", nil) return err } diff --git a/internal/agents/inbox.go b/internal/agents/inbox.go index 9fcc585..72e2b95 100644 --- a/internal/agents/inbox.go +++ b/internal/agents/inbox.go @@ -250,7 +250,7 @@ func (m *Manager) Deliver(ctx context.Context, sessionID string) error { return err } - _, err = m.turn(ctx, sess, task, pending[0].Response.User, deliveryMessage(pending)) + _, err = m.turn(ctx, sess, task, pending[0].Response.User, deliveryMessage(pending), nil) if errors.Is(err, ErrConflict) { return nil } diff --git a/internal/agents/manager.go b/internal/agents/manager.go index dcd32f1..89b27d3 100644 --- a/internal/agents/manager.go +++ b/internal/agents/manager.go @@ -83,11 +83,11 @@ func New(st *store.Store, limits Limits, worktreeRoot string) *Manager { } } -func (m *Manager) Create(ctx context.Context, task api.Task, creator api.User, prompt, model string) (api.AgentSession, error) { - return m.create(ctx, task, creator, prompt, model, creator.Login) +func (m *Manager) Create(ctx context.Context, task api.Task, creator api.User, prompt, model string, images ...string) (api.AgentSession, error) { + return m.create(ctx, task, creator, prompt, model, creator.Login, images) } -func (m *Manager) create(ctx context.Context, task api.Task, creator api.User, prompt, model, actor string) (api.AgentSession, error) { +func (m *Manager) create(ctx context.Context, task api.Task, creator api.User, prompt, model, actor string, images []string) (api.AgentSession, error) { if creator.GitName == "" || creator.GitEmail == "" { return api.AgentSession{}, ErrNoGitIdentity } @@ -124,10 +124,10 @@ func (m *Manager) create(ctx context.Context, task api.Task, creator api.User, p return api.AgentSession{}, err } - return m.turn(ctx, sess, task, actor, prompt) + return m.turn(ctx, sess, task, actor, prompt, images) } -func (m *Manager) Send(ctx context.Context, sessionID, actor, text, model string) (api.AgentSession, error) { +func (m *Manager) Send(ctx context.Context, sessionID, actor, text, model string, images ...string) (api.AgentSession, error) { sess, err := m.store.GetSession(ctx, sessionID) if err != nil { return api.AgentSession{}, err @@ -150,7 +150,7 @@ func (m *Manager) Send(ctx context.Context, sessionID, actor, text, model string return api.AgentSession{}, err } - return m.turn(ctx, sess, task, actor, text) + return m.turn(ctx, sess, task, actor, text, images) } func (m *Manager) Interrupt(ctx context.Context, sessionID string) (api.AgentSession, error) { @@ -249,7 +249,7 @@ func (m *Manager) Recover(ctx context.Context) error { continue } - err = m.message(ctx, l, "byre", restartNotice) + err = m.message(ctx, l, "byre", restartNotice, nil) if err != nil { return err } @@ -310,7 +310,7 @@ func (m *Manager) Usage(ctx context.Context) (Usage, error) { return u, nil } -func (m *Manager) turn(ctx context.Context, sess api.AgentSession, task api.Task, actor, text string) (api.AgentSession, error) { +func (m *Manager) turn(ctx context.Context, sess api.AgentSession, task api.Task, actor, text string, images []string) (api.AgentSession, error) { m.capMu.Lock() defer m.capMu.Unlock() @@ -320,7 +320,7 @@ func (m *Manager) turn(ctx context.Context, sess api.AgentSession, task api.Task } if room { - return m.start(ctx, sess, task, actor, text, api.AgentSessionStateIdle) + return m.start(ctx, sess, task, actor, text, images, api.AgentSessionStateIdle) } err = transition(ctx, m.store, sess.Id, api.AgentSessionStateIdle, api.AgentSessionStateQueued, actor) @@ -333,6 +333,7 @@ func (m *Manager) turn(ctx context.Context, sess api.AgentSession, task api.Task Vendor: sess.Vendor, Actor: actor, Text: text, + Images: images, CreatedAt: time.Now().UTC(), }) if err != nil { @@ -342,7 +343,7 @@ func (m *Manager) turn(ctx context.Context, sess api.AgentSession, task api.Task return m.store.GetSession(ctx, sess.Id) } -func (m *Manager) start(ctx context.Context, sess api.AgentSession, task api.Task, actor, text string, from api.AgentSessionState) (api.AgentSession, error) { +func (m *Manager) start(ctx context.Context, sess api.AgentSession, task api.Task, actor, text string, images []string, from api.AgentSessionState) (api.AgentSession, error) { err := transition(ctx, m.store, sess.Id, from, api.AgentSessionStateRunning, actor) if err != nil { return api.AgentSession{}, err @@ -360,7 +361,7 @@ func (m *Manager) start(ctx context.Context, sess api.AgentSession, task api.Tas return api.AgentSession{}, err } - err = m.message(ctx, l, actor, text) + err = m.message(ctx, l, actor, text, images) if err != nil { return api.AgentSession{}, err } @@ -430,7 +431,7 @@ func (m *Manager) drain(ctx context.Context) error { return err } - _, err = m.start(ctx, sess, task, q.Actor, q.Text, api.AgentSessionStateQueued) + _, err = m.start(ctx, sess, task, q.Actor, q.Text, q.Images, api.AgentSessionStateQueued) if err != nil { slog.Error("start queued turn", "session", q.SessionID, "err", err) } @@ -467,12 +468,25 @@ func transition(ctx context.Context, st *store.Store, id string, from, to api.Ag return err } -func (m *Manager) message(ctx context.Context, l *live, actor, text string) error { +func (m *Manager) message(ctx context.Context, l *live, actor, text string, images []string) error { + imgs := []claude.Image{} + for _, id := range images { + b, data, err := m.store.ReadBlob(ctx, id) + if err != nil { + return err + } + imgs = append(imgs, claude.Image{MediaType: b.ContentType, Data: data}) + } + head, err := git.Head(l.dir) if err != nil { return err } - err = m.emit(ctx, l, actor, "user.message", map[string]any{"text": text, "head": head}) + payload := map[string]any{"text": text, "head": head} + if len(images) > 0 { + payload["images"] = images + } + err = m.emit(ctx, l, actor, "user.message", payload) if err != nil { return err } @@ -485,7 +499,7 @@ func (m *Manager) message(ctx context.Context, l *live, actor, text string) erro l.rateLimited = nil m.mu.Unlock() - return l.sess.Send(text) + return l.sess.Send(text, imgs...) } func (m *Manager) ensureLive(ctx context.Context, sess api.AgentSession, task api.Task) (*live, error) { diff --git a/internal/agents/tasks.go b/internal/agents/tasks.go index b93198a..693d703 100644 --- a/internal/agents/tasks.go +++ b/internal/agents/tasks.go @@ -119,7 +119,7 @@ func (m *Manager) createTaskFromAgent(ctx context.Context, l *live, raw json.Raw return "", err } - _, err = m.create(ctx, task, creator, args.Prompt, model, agentActor) + _, err = m.create(ctx, task, creator, args.Prompt, model, agentActor, nil) if err != nil { return "", fmt.Errorf("task %s created, but its agent did not start: %w", task.Id, err) } diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 0f7cf1a..0d7cb6c 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -304,6 +304,14 @@ type Attachment struct { // AttachmentKind defines model for Attachment.Kind. type AttachmentKind string +// Blob defines model for Blob. +type Blob struct { + ContentType string `json:"content_type"` + CreatedAt time.Time `json:"created_at"` + CreatedBy string `json:"created_by"` + Id string `json:"id"` +} + // Error defines model for Error. type Error struct { Message string `json:"message"` @@ -412,9 +420,14 @@ type InboxState string // MessageCreate defines model for MessageCreate. type MessageCreate struct { + // Images Blob ids from uploadBlob, sent to the agent as images alongside the text. + Images *[]string `json:"images,omitempty"` + // Model Model for this turn and later ones. When it differs from the session's model, the session switches before the message is sent. Model *string `json:"model,omitempty"` - Text string `json:"text"` + + // Text May be empty when images are given. + Text string `json:"text"` } // Project defines model for Project. @@ -436,8 +449,10 @@ type ProjectCreate struct { // SessionCreate defines model for SessionCreate. type SessionCreate struct { - Model *string `json:"model,omitempty"` - Prompt string `json:"prompt"` + // Images Blob ids from uploadBlob, sent to the agent as images alongside the prompt. + Images *[]string `json:"images,omitempty"` + Model *string `json:"model,omitempty"` + Prompt string `json:"prompt"` } // Status defines model for Status. @@ -581,6 +596,12 @@ type ServerInterface interface { // (GET /api/admin/status) GetStatus(w http.ResponseWriter, r *http.Request) + // (POST /api/blobs) + UploadBlob(w http.ResponseWriter, r *http.Request) + + // (GET /api/blobs/{blobId}) + GetBlob(w http.ResponseWriter, r *http.Request, blobId string) + // (GET /api/events) StreamEvents(w http.ResponseWriter, r *http.Request, params StreamEventsParams) @@ -677,6 +698,46 @@ func (siw *ServerInterfaceWrapper) GetStatus(w http.ResponseWriter, r *http.Requ handler.ServeHTTP(w, r) } +// UploadBlob operation middleware +func (siw *ServerInterfaceWrapper) UploadBlob(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UploadBlob(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetBlob operation middleware +func (siw *ServerInterfaceWrapper) GetBlob(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "blobId" ------------- + var blobId string + + err = runtime.BindStyledParameterWithOptions("simple", "blobId", r.PathValue("blobId"), &blobId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "", ValueIsUnescaped: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "blobId", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetBlob(w, r, blobId) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // StreamEvents operation middleware func (siw *ServerInterfaceWrapper) StreamEvents(w http.ResponseWriter, r *http.Request) { @@ -1555,6 +1616,8 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/sessions/{sessionId}/messages", wrapper.SendMessage) m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/sessions/{sessionId}/compact", wrapper.CompactSession) m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/sessions/{sessionId}/interrupt", wrapper.InterruptSession) + m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/blobs", wrapper.UploadBlob) + m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/blobs/{blobId}", wrapper.GetBlob) m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/inbox", wrapper.ListInbox) m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/inbox/{itemId}", wrapper.GetInboxItem) m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/inbox/responses", wrapper.RespondInbox) @@ -1569,72 +1632,77 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H // const string: with thousands of chunks the chained `+` fold is several // times slower for the Go compiler than parsing a slice literal. var swaggerSpec = []string{ - "5Fzrbxw5cv9XiE4A2UBrpL3bBMjcJ8feCxysL47lxX64LAacZs00T91ki2RLnhj634Mqkv2YZs+MtJLs", - "RT551I9iPX71YLHaX7NC141WoJzNll8zA7bRygL98ZMx2uCPQisHyuFP3jSVLLiTWl38w2qF12xRQs3x", - "1z8b2GTL7J8ueqoX/q698NTu7+/zTIAtjGyQSLb0yywyvBGeRVJvtqDcFVgr/SKN0Q0YJz1rhQHuQKw4", - "MbXRpsZfmeAOzp2sIcszt2sgW2bWGam22X3evbPe4TuT21IkL9daQIV3xkx/gpsWrAPB6IG/MKgbt2M1", - "cGWZK4HdghLanFkmYMPbyi1SLBldAdIG1dbZ8u+ZrJsKalAOTPZb4nnruIMpMzcttMgJLc6Za41i0rI7", - "Lp1UW7bRhnG2MQDMVtqxVgkwxGShVdEaA6rYsYI3FpnseBEVqtHTzvLMtEohGzlBpgIHYspjnn05RwLn", - "t9woXqOx/j4y5RVK8N6Tnlz/77jW5M6nbvHJrbc9N/d55ri9Xs2Y0ptkqO+i4q2ApKr9wyvrV0qTRAvC", - "TSsNCK+xrGegWy5CKEUyWjQgYYTRfAjynkO9/gcUDjl84xwvyjo45p5/8ICNhBpAiVUlFQxuSuVgC4bc", - "oObbBMTo8pIJ7jj75dPPSTRfSyWG2t1IkknIzSaLhPFPvjW8DqpCFSje2FK7LM9aUyWNUYOpuTfBmK1A", - "bMk++CeY1a0pIMlew105pYBMLpmBRp8bqLiTt8DwybS/Ynib8rDZLNlWOmbgVqJpmeFqm2YiCjuHUeu4", - "cQfMcwjfqL2jICUjpeDURfsxkmqwNiDiMOH4YJL2bRKlvHB+yYkoHL38sPPlj0oCEaMJdOwqzYf3evYb", - "o/HnrM3gZsSAVO5ff+wXP8l6e8pEkoHZPKipZ/FoZPirrOBtiRhM6FwIicC1aXytpeJmmCDXWlfAVUZp", - "u4IDr+pKrNI+ZgBzgVhSzsFH2Bo22gD97W8e9NhkHmztMNZwIShz1FrIjaSfxK5PXX79sMSqINUkste+", - "FWj9brV8oLuhMjqlzZniHQbAiSEa7opyBopJsdPMeTKppd+rtf7y3kGdAMEp7rWudHGNv5NgeIzzzSy0", - "nzeoskLkoMCNNpgZIq9AUSrPDHewqmQtk2XI2J8nxRJRXzIqa7+4nMVLObsrdyuSG0TONL1ic2ag0HUN", - "SoBY+Ys54132tQvm2VwyJMdeIao3UvGKhZjI9CaWZa7kjhElplBnVbV7vWAj6ZaM/lmwoYzLUFDmg6t+", - "pa7QxBvn/oZH7Os8UPofleWpqCa1kW43VH2QPcuzbahL4FbCXZZnm51M6/lwbDRgdXX7QJx0L82U6qNd", - "inRQ22ObD/KFT+E1isSeJjeG73xe1vp/Qaxa5WR1OqddQX50capVj2VwVxrg4vRic6D8YeU5ce8h5S6t", - "dPYfuPowycTSdODpQ9XPhhyvZjGNOr/TbG+JDyoHpXrvX/9h35J7WjqZXZtKlb2Ln8zxoChPoOwxQVNA", - "JW/BPE2oRRnmwKfntwzeOx7qHBgN0zCnCw8w/Wd8AStcC+ZE14iShpfCovnIqEcLqRT+XgAmp1lpnNb8", - "9SXTitINZoaY1s5sTGUzW5Kxccd0/d0luytBEVVkjhngTQPcEMWHoWGPbVeC8YUhJU8LyjGn6QKFsSTL", - "jwTQPk46iBC9owD4HFaNyTKYIvdCZFGTtMe1tbQ2mTAH2WBEC5ASV/YOfb2nQb9t24CxIGZqnQ++zJgD", - "6EwL6wNepuaQK6X15QlXglXcgUEc2QX7Fa0uHcM9LhjLNkbXZJuQXM6s73/lw4vM3klXlGCHZX4shaQl", - "Gy8eEDD2zEZPpYz10WfDF+kWhq7eam24minjZxwYtyIzVU2t3dytRq9O2xYQnmmN4WsThrvlHtZzCiqe", - "Q9oB2U4WYMJ7io/QADyK+FS5WjcngCw8l1y723xOuhnyFlZYhs3skMV6td45//QJ3YKKW7eCW1/Lndpi", - "wGJhRZHTTl2emqesMboAa8Ey3/x11Y6F7u4iSTOUmTNS+Q7xCuPHzBP9lmXUguDVx5ECJ5oeM/+5BIpN", - "1k23Ogx4UYatUNiN4Q7uGnYg2HoX7ixS26Ag+SH+rYVVUcmo08QDjpuHBZj9fk9PYACTCQQGpsjHeBsz", - "OYbBvox7JhvbJ4X4z9xeT/G+5hZWoSM6bR7Mx8Wi0rbT1djEv8YiA4Vid9yyGswWBNOGCWkLbgSI00uO", - "FzwlQjZXha5rmRDLC+GrHHvTclsy/yjTXtwQnJnXWjxMiiWXAVKF0q6UaovVERFMt8yOtCqnjTNCURZE", - "CIWHV/SJxzuIDh8T30Ra/aUPkWp/6V1PH3O+dFU6Z9xpc+0MwEMS33hDTKQ7LO5TzHsED3p8D8mGKJPv", - "s9q0eySKLtTHOd7rILDpEe85pSpsCoukvTeyesB+etAYTmw7UDVTjj8b6HYUUYGMWyY3DG7B7DwosWrF", - "WLz1/nnYVKSasF4UYVa/Myl+Djf7xSI9liL+iwqIn5aLZJhE+nzrb0SvHZqM+nrcgDqbcep0Zj3SUu+s", - "O+YDzWj9kr4w7+tykolFBY8J5lmrvGwOBPNdcMukKqqWwupStVWFMU5ZVMYsVwMqU94+cldadiddydKr", - "jYCU0sqeBaM1ojZG/I378EPGkiYP7YOxuYW0TcV3q9nidSvdCmouq9m7s69WeivVcZT6x/IxKwPSQx7m", - "BPulEUlHeTT3ezyeyMw9AWijBz6arXcG6Pzb+GmO7HJxubj03QxQvJHZMvvz4nLxZ3+eURLfF7yRF1zU", - "Ul30WWsLlF5RPppBeS+yZfYf4K5i+B71GP90eflk4ythhcT8yhWYWzCsBF45H7tb3Oku8Nn73MtBJdxQ", - "ghSJc2p++EcX7CesaOmPM8ukwI0zxh4LN3/xx/DSD5rQ2SoG5P+8+q+/LfyflvENbuJ/5tad05Xz9+/Y", - "K+2nPvy9mxbMjjXc8BocmNcYvbB0rjjVzH7njmVk5IhOEcaav3IGeO2XJNsFYlgXfM0Q+VkJXFDs8SjL", - "Rixl+UD7Rzc393mgSaz3JEmeB5L67ShUHHxx3m7nlsQcY2XfWaZjTWQX/+oIC1Kt9ZdZKFB7iJpsNmel", - "3Ja434mteraRxjr2qjui2nIHOfOHNDnb7ORr6sQopiuBL9LzC/ZXWaFVmOLG6DsCgVYQIOA04ywUTmeW", - "0bAQprYzy5Bsyu4/S+uI0Rmj7xloVJXNq3DGvP25xoNfjUcYp7n48JxmjmLIlqtwXpRiqjskPQFkh+PR", - "6WckdMw7KemmoPzAXUH1GpH2adqVIHHTHBhdsHe+cLGIDAzQOWtVkNe/N4XzxUjORtsEst9Qb9P6DrVh", - "NcYXz4ZUdNG261pSX2fB4sOGNvZdNxjjHD7aHayqAolQcBQVUPRVoQWO0TIebHW9cEthrlWx0ZpCdzjA", - "igA3ftTv37XYPVk2GR2U3Y9TrTMt3H93yPlcAmupxOhBkGc/er5Sq3T8x8lPfPrHBz39byc/vQ/Hr8ji", - "e3F/qGboxX/GsmGg47RO6TiF/BDx2nvhQ/V1f58OxWGrG8MXqSXbh9uh0Ppb1KwvEue0+QGeU41Uuc9o", - "sOBVhS7eWqAxYlREm2DSF8iBz6d36UEN/gwO/VjlRJcNynmYx/ZeNewAJxGAFcHHvjf5/MErHvacELre", - "VFWsb2wESMhQYyF8ryFSfh6YjE9QTkLKD0+9+BxYFNxFRT0BVi6+hl9H4vBQ38/kIUfkHsv81DG308Lj", - "wm5KnRfdIdOsM34OxwLP74l0OnBiBdFvM0iCHCHX71O+K/3nM2Ws91zbdQDP7LBt23VIu45cL/K0IZgK", - "P5/9VPxzxJ5BP/WFA4/HyHzUQUU+c0HZB6gwpmAvvoZfRwJU/ArpGQPU6GunGT0FZp/JSzpVPC5KpXRK", - "XPEwkPFcDMy56Rt77X2U9oX9uIrfRvpPokquhN5s2Kut5lXOBBT0+YbN6VQZ8AoXDJSwOW2E+11kzqgZ", - "/TpMyFArngZo/MPDYRhpWffBFIUIToiPt8NhgeV1OFDAR2h+gdGhcL9J79jlvhPoe0FhJ5zax7716p+F", - "759eFL6Re1LTHbcsnHkzTb3IFsTipfaISbBK5cCYtvk2cP1JCQ/XMJJBapqmiPeRye/EqJ3SyKLdqOq3", - "tWRwCftNDHnlndY7uce6919pO19lg3DO6tY6tvZxamrwK1AijPc9U1EwHh48qS54WYyREss+YHynjScq", - "aC++4j9H6omuwnumYuJQsTUotJ66gvCSP658GCvvouiHKpKHJD/1gwdSDSvxrvx+1Z0E52EsgbrY9Muf", - "0SlnuD9EQXmhoE+lF1vp5FZpA68xzXKGxHJK4NyA6HNx3c9xHBjgmKv9c0x6RMtKVYCf9NnKW1A+NkQx", - "zizTd4pJJeALFhJKO+Z0W5TpxnXAVhxJmRhtr0KKEyhORwFDI1JZh4VPEKwXNWe2RelQMdRQim34eExZ", - "Aqfxj+RZDEqaHYmpz+oTUS8J1/gI5hwrOir8vrfW9gs7nwifKyY9j3qKij6xpNkTBIlWQOUwW4O7A1CM", - "0wCK94JuL7wdgXMxB953/mvxE04Vw5DL6QLPnS/6MaTfTScY5HfT6b6j/Va+0n2zOtfE4a4o/7/7CM0w", - "nlZhPmbdufLyE9T6dtx96ofylIgJCOO4bh3Fbqm2PquE5w0U2vgk6Cc7mIGay8RuI0xqfst6pRtGfVzl", - "8jTF3LHxnYFyYTh+w5mFm3xvFuMjJsz1jjXcWhrnpbF26/BZtubFNeVXfH+RnLtAbR2cuHmC6ZiZyEQj", - "4iNSNf8i67bOlj9cXl7Sh5nhz0eN3DxBM9r/TxMndKN/abDu8d8QBKO9ikXav1xevv7+i2Sqy14+Al3R", - "AHs6BOXs0IRpzqRymsqF8fj7fJm84dadb7S54yb0R/pnixKKawxyTjPpwtyTGcTHRGBcsL+FKXppWdNa", - "LKTZJ9i0Fov7EouY/v9PCl9N9A2qnCjhnp2mbAutNpUs0qNxNOj9LSNn+HLiBTrrTxRkYy/n4LHWVXzo", - "JYLJuDtx2glX8AjfZo4i7eWA7ye0HOtiqbEgc7ttdAsLSliG20nvpXXjxp3qdEfT952G7cynb3GNvxZ8", - "4aOvU1pcgwOBP4avtoOPJ5Il0a90IpKASqkrEb5dQGjMhX4mNFh15pL7xO7TjeccqolrzA3WhPsxyy3+", - "sDuc+/v/CwAA//8=", + "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", + "cpN/2wrn4eFfAQAA//8=", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/internal/claude/session.go b/internal/claude/session.go index b193df7..59a7f8c 100644 --- a/internal/claude/session.go +++ b/internal/claude/session.go @@ -5,6 +5,7 @@ import ( "bytes" "context" "crypto/rand" + "encoding/base64" "encoding/hex" "encoding/json" "errors" @@ -359,10 +360,34 @@ func (s *Session) ID() string { return s.id } -func (s *Session) Send(text string) error { +type Image struct { + MediaType string + Data []byte +} + +func (s *Session) Send(text string, images ...Image) error { + var content any = text + if len(images) > 0 { + blocks := []map[string]any{} + for _, img := range images { + blocks = append(blocks, map[string]any{ + "type": "image", + "source": map[string]any{ + "type": "base64", + "media_type": img.MediaType, + "data": base64.StdEncoding.EncodeToString(img.Data), + }, + }) + } + if text != "" { + blocks = append(blocks, map[string]any{"type": "text", "text": text}) + } + content = blocks + } + return s.write(map[string]any{ "type": "user", - "message": map[string]any{"role": "user", "content": text}, + "message": map[string]any{"role": "user", "content": content}, "parent_tool_use_id": nil, "session_id": s.ID(), }) diff --git a/internal/claude/session_test.go b/internal/claude/session_test.go index 61211e5..b1d65ef 100644 --- a/internal/claude/session_test.go +++ b/internal/claude/session_test.go @@ -1,8 +1,13 @@ package claude_test import ( + "bytes" "context" "encoding/json" + "image" + "image/color" + "image/draw" + "image/png" "os" "os/exec" "path/filepath" @@ -198,6 +203,30 @@ func TestInterrupt(t *testing.T) { } } +func TestImageInput(t *testing.T) { + t.Parallel() + ctx := testContext(t) + s := start(t, ctx, claude.Options{Cwd: scratchRepo(t)}) + + img := image.NewRGBA(image.Rect(0, 0, 64, 64)) + draw.Draw(img, img.Bounds(), &image.Uniform{C: color.RGBA{R: 255, A: 255}}, image.Point{}, draw.Src) + buf := bytes.Buffer{} + err := png.Encode(&buf, img) + if err != nil { + t.Fatalf("encode png: %v", err) + } + + err = s.Send("Without using any tools, what single colour fills the attached image? Reply with just the colour name.", claude.Image{MediaType: "image/png", Data: buf.Bytes()}) + if err != nil { + t.Fatalf("send: %v", err) + } + text := waitResult(t, ctx, s, nil) + + if !strings.Contains(strings.ToLower(text), "red") { + t.Fatalf("model didn't see the image; assistant said %q", text) + } +} + func TestResume(t *testing.T) { t.Parallel() ctx := testContext(t) diff --git a/internal/claude/testdata/transcripts/TestImageInput.jsonl b/internal/claude/testdata/transcripts/TestImageInput.jsonl new file mode 100644 index 0000000..f39b796 --- /dev/null +++ b/internal/claude/testdata/transcripts/TestImageInput.jsonl @@ -0,0 +1,8 @@ +{"dir":"send","msg":{"request":{"hooks":null,"subtype":"initialize"},"request_id":"req_b7ae1940e644e81d","type":"control_request"}} +{"dir":"recv","msg":{"type":"control_response","response":{"subtype":"success","request_id":"req_b7ae1940e644e81d","response":{"commands":[{"name":"deep-research","description":"Deep research harness — fan-out web searches, fetch sources, adversarially verify claims, synthesize a cited report. (dynamic workflow)","argumentHint":"","builtin":true},{"name":"design","description":"Make a new Design artifact from a brief","argumentHint":"[what to design]","builtin":true},{"name":"slides","description":"Make a new Slides deck artifact from a brief","argumentHint":"[what the deck is about]","builtin":true},{"name":"design-sync","description":"Push a React design system to claude.ai/design. This runs a converter that bundles the real component code (from Storybook or a bare package) and uploads it. Use when the user runs /design-sync or says \"sync my design system to Claude Design\".","argumentHint":"[\u003cproject hint, e.g. \"Acme DS\"\u003e]","builtin":true},{"name":"dataviz","description":"Use this skill whenever you are about to create ANY chart, graph, plot, dashboard, or data visualization, in ANY output medium — an HTML or React artifact, inline SVG, plotting code in any library (matplotlib, plotly, d3, Recharts, …), an image/PNG you will render and upload, or a chart shared into Slack. Read it BEFORE writing the first line of chart code, choosing chart colors, building a stat tile / meter / KPI row, or laying out a dashboard. When the destination is a first-party document connector (host-designated, never self-described) that renders live charts, hand it the rows (inline, or as an uploaded data file the chart cites) rather than a rendered PNG/SVG — a picture of a chart loses hover, data inspection and per-value comments. Produces visualizations that read as one system — elegant, accessible, consistent in light and dark — using a brand-neutral placeholder palette you swap for your own. Teaches a design-system-agnostic method: a form heuristic, a color formula with a runnable validator, mark specs, and interaction rules. A validated default palette is documented in `references/palette.md` — swap that file's values for your brand's. Triggers on: \"chart\", \"graph\", \"plot\", \"data viz\", \"visualization\", \"dashboard\", \"analytics\", \"visualize data\", \"categorical colors\", \"sequential / diverging palette\", \"stat tile\", \"sparkline\", \"heatmap\", \"legend\", \"axis\", \"tooltip\", \"chart colors\", \"color by series\".","argumentHint":"","builtin":true},{"name":"artifact-diagramming","description":"Diagramming know-how for Artifacts - when a picture earns its place, how to draw one that shows the real mechanism, and the inline-SVG mechanics that keep it legible in both themes.","argumentHint":"","builtin":true},{"name":"artifact-capabilities","description":"Runtime capabilities a published Artifact page can be granted — behavior static HTML cannot provide on its own, such as the page reading live or connected data, remembering what people do on it (a poll, a sign-up sheet, a checklist, a document edited in place — it saves new versions of itself), keeping state shared across viewers, knowing who is viewing, asking Claude a question of its own, storing files people add, handing the viewer a file to save, or using the viewer's camera, microphone, location, screen or device motion. Serves this user's live capability roster and the typed call definitions. Load it whenever any such runtime behavior would make an artifact more useful, before writing the page.","argumentHint":"","builtin":true},{"name":"update-config","description":"Use this skill to configure the Claude Code harness via settings.json. Automated behaviors (\"from now on when X\", \"each time X\", \"whenever X\", \"before/after X\") require hooks configured in settings.json - the harness executes these, not Claude, so memory/preferences cannot fulfill them. Also use for: permissions (\"allow X\", \"add permission\", \"move permission to\"), env vars (\"set X=Y\"), hook troubleshooting, or any changes to settings.json/settings.local.json files. Examples: \"allow npm commands\", \"add bq permission to global settings\", \"move permission to user settings\", \"set DEBUG=true\", \"when claude stops show X\". For simple settings like theme/model, suggest the /config command.","argumentHint":"","builtin":true},{"name":"verify","description":"Verify that a code change actually does what it's supposed to by exercising it end-to-end and observing behavior — drive the affected flow, not just tests or typecheck. Run before committing nontrivial changes; bootstraps this repo's project verify skill if none exists yet. Don't invoke it on a diff that only touches tests, docs, or other code with no runtime surface to drive (a change to product source always has one) — there's nothing to observe.","argumentHint":"","builtin":true},{"name":"debug","description":"Enable debug logging for this session and help diagnose issues","argumentHint":"[issue description]","builtin":true},{"name":"code-review","description":"Review the current diff, or a PR number/branch/path target, for correctness bugs (plus reuse/simplification/efficiency cleanups where the model's review recipe covers them) at the given effort level (low|medium|high|xhigh|max: from few high-confidence findings up to many, some of them uncertain; ultra: deep multi-agent review in the cloud); with no level given, it reuses the level you typed last. Pass --comment to post findings as inline PR comments, or --fix to apply the findings to the working tree after the review. Pass --max-findings \u003cn\u003e to report up to n findings, or --max-findings all for every finding. The choice stays until you pass --max-findings default. For ultra on a GitHub.com PR target, --post asks to post the finished review’s findings to the PR as a single comment from the user’s GitHub account (not a review; the launch dialog still confirms in interactive sessions, while non-interactive mode posts on the flag alone) and --no-post hides that option.","argumentHint":"[low|medium|high|xhigh|max|ultra] [--fix] [--comment] [\u003cpr#\u003e|\u003cbranch\u003e|\u003cpath\u003e] [--max-findings \u003cn\u003e|all]","aliases":["review"],"builtin":true},{"name":"simplify","description":"Review the changed code for reuse, simplification, efficiency, and altitude cleanups, then apply the fixes. Quality only — it does not hunt for bugs; use /code-review for that.","argumentHint":"[\u003ctarget\u003e]","builtin":true},{"name":"batch","description":"Research and plan a large-scale change, then execute it in parallel across 5–30 isolated worktree agents that each open a PR.","argumentHint":"\u003cinstruction\u003e","builtin":true},{"name":"fewer-permission-prompts","description":"Scan your transcripts for common read-only Bash and MCP tool calls, then add a prioritized allowlist to project .claude/settings.json to reduce permission prompts.","argumentHint":"","builtin":true},{"name":"doctor","description":"Health-check the user's Claude Code setup and fix issues: diagnose installation health — what the `claude doctor` terminal diagnostics cover — from local data (duplicate or leftover installs, PATH, unparseable settings files, broken or colliding agent definitions, skills whose frontmatter fails to parse); find unused skills, MCP servers, and plugins versus their context cost and disable dead weight; deduplicate local CLAUDE.md files against checked-in ones; trim checked-in CLAUDE.md files by cutting content a session could derive from the codebase (directory layouts, tech-stack lists, architecture overviews) while keeping gotchas, rationale, and non-standard conventions; migrate always-loaded CLAUDE.md guidance into lazy skills and nested CLAUDE.md files; flag slow hooks and context-heavy extensions; check the installed version is current; make auto mode the default permission mode; and pre-approve frequently denied read-only commands. Use when the user asks for a doctor run, checkup, audit, tune-up, or cleanup of their Claude Code setup or configuration.","argumentHint":"[prompt-audit [\u003cpath\u003e]]","aliases":["checkup"],"builtin":true},{"name":"loop","description":"Run a prompt or slash command on a recurring interval (e.g. /loop 5m /foo). Omit the interval to let the model self-pace.","argumentHint":"[interval] [prompt]","aliases":["proactive"],"builtin":true},{"name":"schedule","description":"Create, update, list, or run scheduled cloud agents (routines) that execute on a cron schedule.","argumentHint":"","aliases":["routines"],"builtin":true},{"name":"claude-api","description":"Reference for the Claude API / Anthropic SDK — model ids, pricing, params, streaming, tool use, MCP, agents, caching, token counting, model migration.\nTRIGGER — read BEFORE opening the target file; don't skip because it \"looks like a one-liner\" — whenever: the prompt names Claude/Anthropic in any form (Claude, Anthropic, Fable, Opus, Sonnet, Haiku, `anthropic`, `@anthropic-ai`, `claude-*`, `us.anthropic.*`, `[1m]`); the user asks about an LLM (pricing/model choice/limits/caching) — never answer from memory; OR the task is LLM-shaped with provider unstated (agent/MCP/tool-definition/multi-agent/RAG/LLM-judge/computer-use; generate/summarize/extract/classify/rewrite/converse over NL; debugging refusals/cutoffs/streaming/tool-calls/tokens).\nSKIP only when another provider is being worked on (overrides all triggers): OpenAI/GPT/Gemini/Llama/Mistral/Cohere/Ollama named in the query; OR `grep -rE 'openai|langchain_openai|google.generativeai|genai|mistralai|cohere|ollama'` over the project hits (run this grep FIRST if no provider named — don't Read the file).","argumentHint":"","builtin":true},{"name":"workflow-authoring","description":"Reference for writing a Workflow tool script (script API and gotchas, resume, quality patterns, worked examples). Load before authoring a script for a workflow the user already opted into; it does not itself authorize running one.","argumentHint":"","builtin":true},{"name":"run","description":"Launch and drive this project's app to see a change working. Use when asked to run, start, or screenshot the app, or to confirm a change works in the real app (not just tests). First looks for a project skill that already covers launching the app; otherwise falls back to built-in patterns per project type (CLI, server, TUI, Electron, browser-driven, library).","argumentHint":"","builtin":true},{"name":"run-skill-generator","description":"Author or improve the run-\u003cunit\u003e skill - a per-project skill that tells agents how to build, launch, and drive this project's app. Use when the user asks to set up the project, get it running, write run instructions, or verify build/run steps work from a clean environment.","argumentHint":"","builtin":true},{"name":"plugin-authoring","description":"Make a mod: a change to Claude's own interface or behaviour, such as a live pane or panel, a band above the prompt, a status line, a toast, a slash command, or a hook on tool calls or prompts. Use to make, change or debug a mod or its hooks module.","argumentHint":"","builtin":true},{"name":"advisor","description":"Let Claude consult a stronger model at key moments","argumentHint":"[fable|opus|sonnet|off]","builtin":true},{"name":"agents","description":"(removed) Ask Claude to create/manage subagents, or edit .claude/agents/","argumentHint":"","builtin":true},{"name":"auto-mode-setup","description":"Teach auto mode about your environment, plus optional rule tweaks","argumentHint":"[--request-id \u003cuuid\u003e] (--wizard posture=… scope=… depth=… --propose | --expect-sha256 \u003c64-hex\u003e --apply-file \u003cpath\u003e)","builtin":true},{"name":"autocompact","description":"Configure the auto-compact window size","argumentHint":"[auto|\u003ctokens\u003e]","builtin":true},{"name":"clear","description":"Start a new session with empty context; previous session stays on disk (resumable with /resume)","argumentHint":"[name]","aliases":["reset","new"],"builtin":true},{"name":"color","description":"Set the prompt bar color for this session","argumentHint":"[red|blue|green|yellow|purple|orange|pink|cyan|default]","builtin":true},{"name":"compact","description":"Free up context by summarizing the conversation so far","argumentHint":"\u003coptional custom summarization instructions\u003e","builtin":true},{"name":"config","description":"Set a setting by key","argumentHint":"key=value","aliases":["settings"],"builtin":true},{"name":"output-style","description":"List output styles or switch to one","argumentHint":"[style]","builtin":true},{"name":"context","description":"Show current context usage","argumentHint":"","builtin":true},{"name":"effort","description":"Set effort level for model usage","argumentHint":"\u003clow|medium|high|xhigh|max|auto|ultracode [on|off]\u003e","builtin":true},{"name":"fast","description":"Toggle fast mode (Opus 5.5)","argumentHint":"[on|off]","builtin":true},{"name":"focus","description":"Toggle focus view: just your prompt, summary, and response","argumentHint":"[on|off]","builtin":true},{"name":"heapdump","description":"Dump the JS heap to the Desktop; on Linux with no Desktop folder, the home directory","argumentHint":"","builtin":true},{"name":"init","description":"Initialize a new CLAUDE.md file with codebase documentation","argumentHint":"","builtin":true},{"name":"mcp","description":"Manage MCP servers","argumentHint":"[reconnect|enable|disable [\u003cserver\u003e|all]]","builtin":true},{"name":"import","description":"Import config from another AI coding agent","argumentHint":"","builtin":true},{"name":"model","description":"Set the AI model for Claude Code","argumentHint":"\u003cmodel\u003e","builtin":true},{"name":"__remote-workflow","description":"Run the workflow script delivered in this session environment (server-launched sessions only)","argumentHint":"","builtin":true},{"name":"workflow-launch-exec","description":"Execute a server-launched workflow handoff (workflow_launch event sessions only)","argumentHint":"","builtin":true},{"name":"reload-plugins","description":"Activate pending plugin changes in the current session","argumentHint":"[--force]","builtin":true},{"name":"reload-skills","description":"Pick up skills added or changed on disk during this session","argumentHint":"","builtin":true},{"name":"rename","description":"Rename the current conversation","argumentHint":"[name]","aliases":["name"],"builtin":true},{"name":"ultrareview","description":"Start a cloud agent that finds and verifies bugs in your branch (~15-25 min, $5-$25 USD) · Runs in a cloud session. See https://code.claude.com/docs/en/claude-code-on-the-web","argumentHint":"","builtin":true},{"name":"security-review","description":"Complete a security review of the pending changes on the current branch","argumentHint":"","builtin":true},{"name":"usage-credits","description":"Configure usage credits or request them from your admin when you hit a limit","argumentHint":"","builtin":true},{"name":"extra-usage","description":"Renamed to /usage-credits","argumentHint":"","builtin":true},{"name":"usage","description":"Show session cost, plan usage, and what's contributing to your limits","argumentHint":"","aliases":["cost","stats"],"builtin":true},{"name":"insights","description":"Generate a report analyzing your Claude Code sessions","argumentHint":"","builtin":true},{"name":"recap","description":"Generate a one-line session recap now","argumentHint":"","builtin":true},{"name":"skill-doctor","description":"Show which loaded skills are unused and costing context","argumentHint":"","builtin":true},{"name":"goal","description":"Set a goal — keep working until the condition is met","argumentHint":"","builtin":true},{"name":"design-consent","description":"Grant Claude agent access to your Design projects","argumentHint":"","builtin":true},{"name":"design-revoke","description":"Revoke Claude agent access to your Design projects","argumentHint":"","builtin":true},{"name":"list-agents","description":"List subagents, teammates, and other Claude sessions you can message","argumentHint":"","aliases":["peers"],"builtin":true},{"name":"team-onboarding","description":"Help teammates ramp on Claude Code with a guide from your usage","argumentHint":"","builtin":true},{"name":"docs","description":"docs (editable docs people share and comment on; the default for any document, named as a doc or not: a document, report, proposal, resume, cover letter, letter, contract, policy, form, template, worksheet, essay, handbook, guide, how-to, cheat sheet, SOP or other writing to keep, share, collaborate on, send, submit, print or sign; a doc exports to Word, PDF, Markdown or Google Docs, so needing a file to send, attach, upload, submit or print is no reason to pick Word, and a file nobody asked for is a doc, not Word; a plan, comparison, summary or notes asked in chat stays in chat; a pasted claude.ai artifact link may be a doc: check with docs tools first; Word or another file format named, tracked changes wanted, or a .docx to change or use as a template → that format's skill): making one → if no docs-connector instructions are in context, call its `guide` (topic.instructions) first; then create the doc (headings only, no body) before any search, file read or plan, even with files attached. (claude.ai sync)","argumentHint":"","aliases":["anthropic-skills:docs"]},{"name":"docx","description":"Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx) or Word templates (.dotx). Triggers include: any mention of Microsoft Word Documents, such as 'Word doc', 'word document', '.docx', '.dotx', 'microsoft doc'. Also use when extracting or reorganizing content from .docx or .dotx files, inserting or replacing images in documents, find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a deliverable as a Word or .docx file (to download, email or print), use this skill. However, if they ask for a document, page, report, memo, or notes WITHOUT naming a file format and the session offers Claude's own dedicated document or page skill or connector, use that instead, even if they will email or print it. Do NOT use for PDFs, spreadsheets, Google Docs, or coding unrelated to document generation. (claude.ai sync)","argumentHint":"","aliases":["anthropic-skills:docx"]},{"name":"google-workspace","description":"Read this before the first Google Drive, Docs, Sheets or Slides connector call whenever the task creates or changes a Google file. Use this skill whenever the user wants to create or change a Google Doc, Sheet or Slides file in their Google Drive. Triggers include: a request that names Google Docs, Sheets, Slides or Drive and asks to make, edit, format, copy or rename a file; a docs.google.com link with a request to change that file, even a one-line fix or suggested edits; and any follow-up change to a Google file from earlier in the chat, even \"change it\" or \"add a tab\". Includes helper scripts for document positions, cell ranges and slide layout. However, if the user asks for a doc, deck or spreadsheet without naming Google, or gives a Google file only as source material for something new, use Claude's own output type instead. Do NOT use for read-only questions about a Google file, or for Word, Excel, PowerPoint or PDF files. (claude.ai sync)","argumentHint":"","aliases":["anthropic-skills:google-workspace"]},{"name":"import-memory","description":"Import a memory export from another AI assistant into Claude's memory — conversationally, additively, and with the content treated as data. (claude.ai sync)","argumentHint":"","aliases":["anthropic-skills:import-memory"]},{"name":"morning","description":"Render the user's morning brief as a styled HTML artifact, or set it up as a recurring weekday task. Use only when the user explicitly asks to run, see, or set up their morning brief, or if they invoke /morning by name. A question about their day, schedule, or calendar is not by itself a request for the brief; answer it directly instead. (claude.ai sync)","argumentHint":"","aliases":["anthropic-skills:morning"]},{"name":"pdf","description":"Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill. (claude.ai sync)","argumentHint":"","aliases":["anthropic-skills:pdf"]},{"name":"pptx","description":"Use this skill any time a .pptx or .potx file is involved in any way — as input, output, or both. This includes: creating slide decks, pitch decks, or presentations as PowerPoint (.pptx) files; reading, parsing, or extracting text from any .pptx or .potx file (even if the extracted content will be used elsewhere, like in an email, summary, or creating a different type of slide deck); editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates (.potx), layouts, speaker notes, or comments. Trigger whenever the user asks for a PowerPoint or .pptx file, or references a .pptx or .potx filename, regardless of what they plan to do with the content afterward. However, when the user asks for a deck, slides, a slide deck, or a presentation without naming a file format, default to using a dedicated slide-deck artifact type or a separate slides skill if this session offers one; otherwise, use this skill. (claude.ai sync)","argumentHint":"","aliases":["anthropic-skills:pptx"]},{"name":"skill-creator","description":"Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy. (claude.ai sync)","argumentHint":"","aliases":["anthropic-skills:skill-creator"]},{"name":"xlsx","description":"Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .xltx, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like \"the xlsx in my downloads\") — and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved. (claude.ai sync)","argumentHint":"","aliases":["anthropic-skills:xlsx"]}],"agents":[{"name":"claude","description":"Catch-all for any task that doesn't fit a more specific agent. FleetView's default when no agent name is typed."},{"name":"claude-code-guide","description":"Use this agent when the user asks questions (\"Can Claude...\", \"Does Claude...\", \"How do I...\") about: (1) Claude Code (the CLI tool) - features, hooks, slash commands, MCP servers, settings, IDE integrations, keyboard shortcuts; (2) Claude Agent SDK - building custom agents; (3) Claude API (formerly Anthropic API) - Messages API for directly passing messages to Claude, Tool Runner (`client.beta.messages.tool_runner`) for running an agentic loop over your own tools, manual tool-use loops, Managed Agents for server-hosted agents with a managed sandbox, prompt caching, and general Anthropic SDK usage; (4) Claude Tag (Claude in Slack) - what it is, setting it up for a Slack workspace, `/install-slack-app`; (5) `claude plugin eval` (writing and running plugin eval suites, its JSON/report, sandbox, CI) and the `/skill-doctor` report. **IMPORTANT:** Before spawning a new agent, check if there is already a running or recently completed claude-code-guide agent that you can continue via SendMessage.","model":"haiku"},{"name":"Explore","description":"Fast read-only search agent for locating code. Use it to find files by pattern (eg. \"src/components/**/*.tsx\"), grep for symbols or keywords (eg. \"API endpoints\"), or answer \"where is X defined / which files reference Y.\" Do NOT use it for code review, design-doc auditing, cross-file consistency checks, or open-ended analysis — it reads excerpts rather than whole files and will miss content past its read window. When calling, specify search breadth: \"quick\" for a single targeted lookup, \"medium\" for moderate exploration, or \"very thorough\" to search across multiple locations and naming conventions.","model":"inherit"},{"name":"general-purpose","description":"General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you."},{"name":"Plan","description":"Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a task. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs.","model":"inherit"},{"name":"statusline-setup","description":"Use this agent to configure the user's Claude Code status line setting.","model":"sonnet"}],"output_style":"default","available_output_styles":["default","Proactive","Concise","Explanatory","Learning"],"user_output_styles_dir":"/Users/flamingcow/.claude/output-styles","models":[{"value":"default","resolvedModel":"claude-opus-5-5","displayName":"Default (recommended)","description":"Opus 5.5 · Best for everyday, complex tasks","supportsEffort":true,"supportedEffortLevels":["low","medium","high","xhigh","max"],"supportsAdaptiveThinking":true,"supportsFastMode":true,"supportsAutoMode":true},{"value":"opus","resolvedModel":"claude-opus-5-5","displayName":"Opus 5.5","description":"For complex work and everyday tasks","supportsEffort":true,"supportedEffortLevels":["low","medium","high","xhigh","max"],"supportsAdaptiveThinking":true,"supportsFastMode":true,"supportsAutoMode":true},{"value":"fable","resolvedModel":"claude-fable-5-1","displayName":"Fable 5.1","description":"For your toughest challenges","supportsEffort":true,"supportedEffortLevels":["low","medium","high","xhigh","max"],"supportsAdaptiveThinking":true,"supportsAutoMode":true},{"value":"sonnet","resolvedModel":"claude-sonnet-5-5","displayName":"Sonnet 5.5","description":"Most efficient for simpler tasks","supportsEffort":true,"supportedEffortLevels":["low","medium","high","xhigh","max"],"supportsAdaptiveThinking":true,"supportsAutoMode":true},{"value":"haiku","resolvedModel":"claude-haiku-5-5","displayName":"Haiku 5.5","description":"Fastest for quick answers","supportsEffort":true,"supportedEffortLevels":["low","medium","high","xhigh","max"],"supportsAdaptiveThinking":true,"supportsAutoMode":true},{"value":"claude-haiku-4-5-20251001","resolvedModel":"claude-haiku-4-5-20251001","displayName":"Haiku 4.5","description":"Fastest for quick answers"},{"value":"claude-sonnet-5","resolvedModel":"claude-sonnet-5","displayName":"Sonnet 5","description":"Efficient for routine tasks","supportsEffort":true,"supportedEffortLevels":["low","medium","high","xhigh","max"],"supportsAdaptiveThinking":true,"supportsAutoMode":true},{"value":"claude-opus-5","resolvedModel":"claude-opus-5","displayName":"Opus 5","description":"Best for everyday, complex tasks","supportsEffort":true,"supportedEffortLevels":["low","medium","high","xhigh","max"],"supportsAdaptiveThinking":true,"supportsFastMode":true,"supportsAutoMode":true},{"value":"claude-fable-5","resolvedModel":"claude-fable-5","displayName":"Fable 5","description":"Most capable for your hardest and longest-running tasks","supportsEffort":true,"supportedEffortLevels":["low","medium","high","xhigh","max"],"supportsAdaptiveThinking":true,"supportsAutoMode":true},{"value":"claude-opus-4-8","resolvedModel":"claude-opus-4-8","displayName":"Opus 4.8","description":"Best for everyday, complex tasks","supportsEffort":true,"supportedEffortLevels":["low","medium","high","xhigh","max"],"supportsAdaptiveThinking":true,"supportsFastMode":true,"supportsAutoMode":true},{"value":"claude-opus-4-7","resolvedModel":"claude-opus-4-7","displayName":"Opus 4.7","description":"Best for everyday, complex tasks","supportsEffort":true,"supportedEffortLevels":["low","medium","high","xhigh","max"],"supportsAdaptiveThinking":true,"supportsAutoMode":true},{"value":"claude-opus-4-6","resolvedModel":"claude-opus-4-6","displayName":"Opus 4.6","description":"Best for everyday, complex tasks","supportsEffort":true,"supportedEffortLevels":["low","medium","high","max"],"supportsAdaptiveThinking":true,"supportsAutoMode":true},{"value":"claude-sonnet-4-6","resolvedModel":"claude-sonnet-4-6","displayName":"Sonnet 4.6","description":"Efficient for routine tasks","supportsEffort":true,"supportedEffortLevels":["low","medium","high","max"],"supportsAdaptiveThinking":true,"supportsAutoMode":true}],"account":{"email":"[redacted]","organization":"[redacted]'s Organization","subscriptionType":"Claude Max","apiProvider":"firstParty"},"pid":84647,"claude_code_version":"2.1.296","current_permission_mode":"bypassPermissions","feedback_mode":{"kind":"post"},"analytics_disabled":false,"remote_control_auto_enable":false,"remote_control_auto_connect_default":false,"remote_control_available":true,"remote_control_auto_on_by_default":false,"ide_rc_auto_enable_gate":true,"fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","session_state":"idle","capabilities":["ui_surface_v1"]},"pending_permission_requests":[],"pending_user_dialog_requests":[]}}} +{"dir":"send","msg":{"message":{"content":[{"source":{"data":"iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAAZElEQVR4nOzPUQkAIBTAQBH7V9YQfhwPdgm2c9dsWwf8akBrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0B7AQAA///jRQGCmaQfxwAAAABJRU5ErkJggg==","media_type":"image/png","type":"base64"},"type":"image"},{"text":"Without using any tools, what single colour fills the attached image? Reply with just the colour name.","type":"text"}],"role":"user"},"parent_tool_use_id":null,"session_id":"","type":"user"}} +{"dir":"recv","msg":{"type":"system","subtype":"init","cwd":"/private/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestImageInput3452885842/001","session_id":"9427c7e7-455d-4edd-a89f-4a84a692cbad","tools":["Task","Artifact","ArtifactComments","ArtifactData","Bash","CronCreate","CronDelete","CronList","DesignSync","Edit","EnterWorktree","ExitWorktree","ListAgents","LSP","Monitor","NotebookEdit","PushNotification","Read","RemoteTrigger","ReportFindings","ScheduleWakeup","SendMessage","Skill","TaskStop","ToolSearch","WebFetch","WebSearch","Workflow","Write"],"mcp_servers":[{"name":"claude.ai Claude Docs","status":"pending","source":"claudeai"},{"name":"claude.ai Heliosian","status":"pending","source":"claudeai"},{"name":"claude.ai Fastmail","status":"pending","source":"claudeai"},{"name":"claude.ai Google Calendar","status":"needs-auth","source":"claudeai"},{"name":"claude.ai Gmail","status":"needs-auth","source":"claudeai"},{"name":"claude.ai Google Drive","status":"needs-auth","source":"claudeai"}],"model":"claude-sonnet-5-5","permissionMode":"bypassPermissions","slash_commands":["deep-research","design","slides","design-sync","dataviz","artifact-diagramming","artifact-capabilities","update-config","verify","debug","code-review","simplify","batch","fewer-permission-prompts","doctor","loop","schedule","claude-api","workflow-authoring","run","run-skill-generator","plugin-authoring","advisor","agents","auto-mode-setup","autocompact","clear","color","compact","config","output-style","context","effort","fast","focus","heapdump","init","mcp","import","model","__remote-workflow","workflow-launch-exec","reload-plugins","reload-skills","rename","ultrareview","security-review","usage-credits","extra-usage","usage","insights","recap","skill-doctor","goal","design-consent","design-revoke","list-agents","team-onboarding","anthropic-skills:docs","anthropic-skills:docx","anthropic-skills:google-workspace","anthropic-skills:import-memory","anthropic-skills:morning","anthropic-skills:pdf","anthropic-skills:pptx","anthropic-skills:skill-creator","anthropic-skills:xlsx"],"terminal_slash_commands":["doctor","color","focus","reload-plugins"],"apiKeySource":"none","claude_code_version":"2.1.296","output_style":"default","agents":["claude","claude-code-guide","Explore","general-purpose","Plan","statusline-setup"],"skills":["deep-research","design","slides","design-sync","dataviz","artifact-diagramming","artifact-capabilities","update-config","verify","debug","code-review","simplify","batch","fewer-permission-prompts","doctor","loop","schedule","claude-api","workflow-authoring","run","run-skill-generator","plugin-authoring","anthropic-skills:docs","anthropic-skills:docx","anthropic-skills:google-workspace","anthropic-skills:import-memory","anthropic-skills:morning","anthropic-skills:pdf","anthropic-skills:pptx","anthropic-skills:skill-creator","anthropic-skills:xlsx"],"plugins":[{"name":"clangd-lsp","path":"/Users/flamingcow/.claude/plugins/cache/claude-plugins-official/clangd-lsp/1.0.0","source":"clangd-lsp@claude-plugins-official","version":"1.0.0"},{"name":"cc-plugin-agents-md","path":"builtin","source":"cc-plugin-agents-md@builtin"},{"name":"cc-plugin-telemetry","path":"builtin","source":"cc-plugin-telemetry@builtin"},{"name":"cc-plugin-plugin-authoring","path":"builtin","source":"cc-plugin-plugin-authoring@builtin"}],"capabilities":["interrupt_receipt_v1","interrupt_cancel_queued_v1","interrupt_send_now_v1","msg_lifecycle_v1","request_marker_lists_v1","sdk_mcp_tools_list_changed","sdk_mcp_manifests","mcp_read_resource_v1","mcp_tool_ui_meta_v1","ui_surface_v1"],"analytics_disabled":false,"product_feedback_disabled":false,"uuid":"4d1560c5-885a-404e-bf66-9b612b2044ea","memory_paths":{"auto":"/Users/flamingcow/.claude/projects/-private-var-folders-r4-m529kfnd537-x0xfbgcgqfs40000gn-T-TestImageInput3452885842-001/memory/"},"scratchpad_path":"/private/tmp/claude-501/-private-var-folders-r4-m529kfnd537-x0xfbgcgqfs40000gn-T-TestImageInput3452885842-001/9427c7e7-455d-4edd-a89f-4a84a692cbad/scratchpad","messaging_socket_path":"/tmp/cc-socks/84647.sock","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","per_turn_effort_active":true,"view_mode":"default"}} +{"dir":"recv","msg":{"type":"system","subtype":"status","status":"requesting","session_id":"9427c7e7-455d-4edd-a89f-4a84a692cbad","uuid":"5a918793-c9fd-4550-ba44-c6a279d007d5"}} +{"dir":"recv","msg":{"type":"rate_limit_event","rate_limit_info":{"status":"allowed","resetsAt":1792036200,"rateLimitType":"five_hour","overageStatus":"rejected","overageDisabledReason":"org_level_disabled","isUsingOverage":false,"unifiedWindows":{"five_hour":{"utilization":0.15,"resetsAt":1792036200},"seven_day":{"utilization":0.33,"resetsAt":1792270800}}},"uuid":"80582691-eb6d-4494-977e-dac1cd8430e0","session_id":"9427c7e7-455d-4edd-a89f-4a84a692cbad"}} +{"dir":"recv","msg":{"type":"assistant","message":{"model":"claude-sonnet-5-5","id":"msg_011Cg3GmkVxw3Na4CsLv2XYA","type":"message","role":"assistant","content":[{"type":"text","text":"Red"}],"container":null,"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":15356,"cache_read_input_tokens":22367,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":15356},"output_tokens":4,"service_tier":"standard","inference_geo":"global"},"input_transformations":[],"diagnostics":null,"context_management":null},"parent_tool_use_id":null,"session_id":"9427c7e7-455d-4edd-a89f-4a84a692cbad","uuid":"19a2c5c7-5d40-4bdf-a4ea-4adf2d12ee92","timestamp":"2026-10-15T02:57:54.843Z","request_id":"req_011Cg3GmkGKx1MQWSD4krkZH"}} +{"dir":"recv","msg":{"duration_api_ms":1909,"stop_reason":"end_turn","session_id":"9427c7e7-455d-4edd-a89f-4a84a692cbad","total_cost_usd":0.0638324,"usage":{"input_tokens":2,"cache_creation_input_tokens":15356,"cache_read_input_tokens":22367,"output_tokens":4,"output_tokens_details":{"thinking_tokens":0},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":15356,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":4,"cache_read_input_tokens":22367,"cache_creation_input_tokens":15356,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":15356},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":1192,"outputTokens":17,"cacheReadInputTokens":0,"cacheCreationInputTokens":0,"webSearchRequests":0,"costUSD":0.0001277,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":0,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"},"claude-sonnet-5-5":{"inputTokens":2,"outputTokens":4,"cacheReadInputTokens":22367,"cacheCreationInputTokens":15356,"webSearchRequests":0,"costUSD":0.0637047,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":0,"canonicalModel":"claude-sonnet-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"Red","ttft_ms":1341,"type":"result","duration_ms":1361,"uuid":"c419b73f-0a69-4c34-8e78-37bab60565d8","ttft_stream_ms":1337,"time_to_request_ms":29,"first_content_frame_ms":1338,"queued_turn_count":0,"result_index":0}} diff --git a/internal/server/blobs.go b/internal/server/blobs.go new file mode 100644 index 0000000..3cd9570 --- /dev/null +++ b/internal/server/blobs.go @@ -0,0 +1,83 @@ +package server + +import ( + "errors" + "fmt" + "io" + "net/http" + "os" + + "g.fc.run/misc/byre/internal/store" +) + +const maxBlobBytes = 5 << 20 + +var imageTypes = map[string]bool{ + "image/png": true, + "image/jpeg": true, + "image/gif": true, + "image/webp": true, +} + +func (s *Server) UploadBlob(w http.ResponseWriter, r *http.Request) { + data, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxBlobBytes)) + maxErr := &http.MaxBytesError{} + if errors.As(err, &maxErr) { + writeError(w, http.StatusRequestEntityTooLarge, fmt.Errorf("images are limited to %d MiB", maxBlobBytes>>20)) + return + } + if err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + contentType := http.DetectContentType(data) + if !imageTypes[contentType] { + writeError(w, http.StatusBadRequest, fmt.Errorf("unsupported image type %s; use PNG, JPEG, GIF or WebP", contentType)) + return + } + + b, err := s.store.CreateBlob(r.Context(), contentType, data, actor(r)) + if err != nil { + writeError(w, http.StatusInternalServerError, err) + return + } + writeJSON(w, http.StatusCreated, b) +} + +func (s *Server) GetBlob(w http.ResponseWriter, r *http.Request, blobID string) { + b, path, err := s.store.GetBlob(r.Context(), blobID) + if err != nil { + writeStoreError(w, err) + return + } + + f, err := os.Open(path) + if err != nil { + writeError(w, http.StatusInternalServerError, err) + return + } + defer f.Close() + + w.Header().Set("Content-Type", b.ContentType) + w.Header().Set("Cache-Control", "private, max-age=31536000, immutable") + http.ServeContent(w, r, "", b.CreatedAt, f) +} + +func (s *Server) images(w http.ResponseWriter, r *http.Request, ids *[]string) ([]string, bool) { + if ids == nil { + return nil, true + } + for _, id := range *ids { + _, _, err := s.store.GetBlob(r.Context(), id) + if errors.Is(err, store.ErrNotFound) { + writeError(w, http.StatusBadRequest, err) + return nil, false + } + if err != nil { + writeError(w, http.StatusInternalServerError, err) + return nil, false + } + } + return *ids, true +} diff --git a/internal/server/server.go b/internal/server/server.go index 6aabf76..335f47e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -495,7 +495,12 @@ func (s *Server) CreateSession(w http.ResponseWriter, r *http.Request, taskID st model = *req.Model } - sess, err := s.agents.Create(r.Context(), t, u, req.Prompt, model) + images, ok := s.images(w, r, req.Images) + if !ok { + return + } + + sess, err := s.agents.Create(r.Context(), t, u, req.Prompt, model, images...) if err != nil { writeAgentError(w, err) return @@ -518,8 +523,13 @@ func (s *Server) SendMessage(w http.ResponseWriter, r *http.Request, sessionID s return } - if strings.TrimSpace(req.Text) == "" { - writeError(w, http.StatusBadRequest, errors.New("text is required")) + images, ok := s.images(w, r, req.Images) + if !ok { + return + } + + if strings.TrimSpace(req.Text) == "" && len(images) == 0 { + writeError(w, http.StatusBadRequest, errors.New("text or images are required")) return } @@ -528,7 +538,7 @@ func (s *Server) SendMessage(w http.ResponseWriter, r *http.Request, sessionID s model = *req.Model } - sess, err := s.agents.Send(r.Context(), sessionID, actor(r), req.Text, model) + sess, err := s.agents.Send(r.Context(), sessionID, actor(r), req.Text, model, images...) if err != nil { writeAgentError(w, err) return diff --git a/internal/store/blob.go b/internal/store/blob.go new file mode 100644 index 0000000..b72f2b6 --- /dev/null +++ b/internal/store/blob.go @@ -0,0 +1,64 @@ +package store + +import ( + "context" + "database/sql" + "errors" + "os" + "path/filepath" + "time" + + "g.fc.run/misc/byre/internal/api" +) + +func (s *Store) CreateBlob(ctx context.Context, contentType string, data []byte, actor string) (api.Blob, error) { + b := api.Blob{ + Id: NewID(), + ContentType: contentType, + CreatedBy: actor, + CreatedAt: time.Now().UTC(), + } + rel := filepath.Join("blobs", b.Id) + + err := os.MkdirAll(filepath.Join(s.dataDir(), "blobs"), 0o755) + if err != nil { + return b, err + } + err = os.WriteFile(filepath.Join(s.dataDir(), rel), data, 0o644) + if err != nil { + return b, err + } + + _, err = s.db.ExecContext(ctx, `INSERT INTO blobs (id, content_type, path, created_by, created_at) VALUES (?, ?, ?, ?, ?)`, + b.Id, b.ContentType, rel, b.CreatedBy, formatTime(b.CreatedAt)) + return b, err +} + +func (s *Store) GetBlob(ctx context.Context, id string) (api.Blob, string, error) { + b := api.Blob{} + rel := "" + createdAt := "" + err := s.db.QueryRowContext(ctx, `SELECT id, content_type, path, created_by, created_at FROM blobs WHERE id = ?`, id). + Scan(&b.Id, &b.ContentType, &rel, &b.CreatedBy, &createdAt) + if errors.Is(err, sql.ErrNoRows) { + return b, "", errors.Join(ErrNotFound, errors.New("no blob "+id)) + } + if err != nil { + return b, "", err + } + b.CreatedAt, err = parseTime(createdAt) + return b, filepath.Join(s.dataDir(), rel), err +} + +func (s *Store) ReadBlob(ctx context.Context, id string) (api.Blob, []byte, error) { + b, path, err := s.GetBlob(ctx, id) + if err != nil { + return b, nil, err + } + data, err := os.ReadFile(path) + return b, data, err +} + +func (s *Store) dataDir() string { + return filepath.Dir(s.path) +} diff --git a/internal/store/blob_test.go b/internal/store/blob_test.go new file mode 100644 index 0000000..394cd47 --- /dev/null +++ b/internal/store/blob_test.go @@ -0,0 +1,63 @@ +package store_test + +import ( + "context" + "path/filepath" + "slices" + "testing" + "time" + + "g.fc.run/misc/byre/internal/api" + "g.fc.run/misc/byre/internal/store" +) + +func TestQueuedTurnKeepsImages(t *testing.T) { + ctx := context.Background() + st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), func(api.Event) {}) + if err != nil { + t.Fatalf("open store: %v", err) + } + t.Cleanup(func() { st.Close() }) + + now := time.Now().UTC() + _, err = st.EnsureUser(ctx, "alice", "Alice") + if err != nil { + t.Fatalf("ensure user: %v", err) + } + err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) + if err != nil { + t.Fatalf("create project: %v", err) + } + err = st.CreateTask(ctx, api.Task{Id: "t1", ProjectId: "p1", Title: "t1", Branch: "t1", WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}) + if err != nil { + t.Fatalf("create task: %v", err) + } + err = st.CreateSession(ctx, api.AgentSession{Id: "s1", TaskId: "t1", Vendor: api.Claude, VendorSessionId: "s1", State: api.AgentSessionStateIdle, Role: api.Implementer, CreatedBy: "alice", CreatedAt: now}, "p1") + if err != nil { + t.Fatalf("create session: %v", err) + } + + b, err := st.CreateBlob(ctx, "image/png", []byte("not really a png"), "alice") + if err != nil { + t.Fatalf("create blob: %v", err) + } + got, data, err := st.ReadBlob(ctx, b.Id) + if err != nil { + t.Fatalf("read blob: %v", err) + } + if got.ContentType != "image/png" || got.CreatedBy != "alice" || string(data) != "not really a png" { + t.Fatalf("blob round trip: %+v %q", got, data) + } + + err = st.EnqueueTurn(ctx, store.QueuedTurn{SessionID: "s1", Actor: "alice", Text: "look", Images: []string{b.Id}, CreatedAt: now}) + if err != nil { + t.Fatalf("enqueue: %v", err) + } + queued, err := st.QueuedTurns(ctx) + if err != nil { + t.Fatalf("queued turns: %v", err) + } + if len(queued) != 1 || !slices.Equal(queued[0].Images, []string{b.Id}) { + t.Fatalf("queued turn lost its images: %+v", queued) + } +} diff --git a/internal/store/migrate_internal_test.go b/internal/store/migrate_internal_test.go index 5a480c4..c5a6343 100644 --- a/internal/store/migrate_internal_test.go +++ b/internal/store/migrate_internal_test.go @@ -156,6 +156,44 @@ func TestMigrateRunsMigrationAddedBeforeApplied(t *testing.T) { } } +func TestBlobsMigrationOverPositionalCopy(t *testing.T) { + db := openTestDB(t, filepath.Join(t.TempDir(), "byre.db")) + err := migrate(db, migrations[:9]) + if err != nil { + t.Fatalf("migrate: %v", err) + } + insertReport(t, db, "r1", `{"text":"x"}`) + _, err = db.Exec(` + CREATE TABLE blobs (id TEXT PRIMARY KEY, content_type TEXT NOT NULL, path TEXT NOT NULL, created_by TEXT NOT NULL REFERENCES users(login), created_at TEXT NOT NULL); + INSERT INTO blobs VALUES ('b1', 'image/png', 'blobs/b1', 'alice', 'x'); + ALTER TABLE turn_queue ADD COLUMN images TEXT NOT NULL DEFAULT '[]'; + INSERT INTO turn_queue (session_id, actor, text, created_at) VALUES ('s1', 'alice', 'queued', 'x'); + `) + if err != nil { + t.Fatalf("positional copy: %v", err) + } + + err = migrate(db, migrations) + if err != nil { + t.Fatalf("migrate: %v", err) + } + + text := "" + images := "" + err = db.QueryRow(`SELECT text, images FROM turn_queue WHERE session_id = 's1'`).Scan(&text, &images) + if err != nil { + t.Fatalf("queued turn: %v", err) + } + if text != "queued" || images != "[]" { + t.Fatalf("queued turn %q %q", text, images) + } + blobs := 0 + err = db.QueryRow(`SELECT count(*) FROM blobs`).Scan(&blobs) + if err != nil || blobs != 1 { + t.Fatalf("blobs %d: %v", blobs, err) + } +} + func TestFlattenReportPayloadsRunsTwice(t *testing.T) { db := openTestDB(t, filepath.Join(t.TempDir(), "byre.db")) err := migrate(db, migrations) diff --git a/internal/store/queue.go b/internal/store/queue.go index f29625a..a612cc8 100644 --- a/internal/store/queue.go +++ b/internal/store/queue.go @@ -3,6 +3,7 @@ package store import ( "context" "database/sql" + "encoding/json" "errors" "time" @@ -14,6 +15,7 @@ type QueuedTurn struct { Vendor api.AgentSessionVendor Actor string Text string + Images []string CreatedAt time.Time } @@ -44,16 +46,20 @@ func (s *Store) CountRunning(ctx context.Context) (map[api.AgentSessionVendor]in } func (s *Store) EnqueueTurn(ctx context.Context, q QueuedTurn) error { + images, err := json.Marshal(append([]string{}, q.Images...)) + if err != nil { + return err + } return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { - _, err := tx.ExecContext(ctx, `INSERT INTO turn_queue (session_id, actor, text, created_at) VALUES (?, ?, ?, ?)`, - q.SessionID, q.Actor, q.Text, formatTime(q.CreatedAt)) + _, err := tx.ExecContext(ctx, `INSERT INTO turn_queue (session_id, actor, text, images, created_at) VALUES (?, ?, ?, ?, ?)`, + q.SessionID, q.Actor, q.Text, string(images), formatTime(q.CreatedAt)) return err }) } func (s *Store) QueuedTurns(ctx context.Context) ([]QueuedTurn, error) { rows, err := s.db.QueryContext(ctx, ` - SELECT q.session_id, s.vendor, q.actor, q.text, q.created_at + SELECT q.session_id, s.vendor, q.actor, q.text, q.images, q.created_at FROM turn_queue q JOIN agent_sessions s ON s.id = q.session_id ORDER BY q.created_at `) @@ -66,8 +72,13 @@ func (s *Store) QueuedTurns(ctx context.Context) ([]QueuedTurn, error) { for rows.Next() { q := QueuedTurn{} vendor := "" + images := "" createdAt := "" - err = rows.Scan(&q.SessionID, &vendor, &q.Actor, &q.Text, &createdAt) + err = rows.Scan(&q.SessionID, &vendor, &q.Actor, &q.Text, &images, &createdAt) + if err != nil { + return nil, err + } + err = json.Unmarshal([]byte(images), &q.Images) if err != nil { return nil, err } diff --git a/internal/store/store.go b/internal/store/store.go index 2679260..474b0f8 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -140,6 +140,25 @@ UPDATE tasks SET status = 'discarded' WHERE status = 'completed'; `}, {"dismiss-inbox-of-closed-tasks", ` UPDATE inbox_items SET state = 'dismissed', resolved_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), snoozed_until = NULL WHERE state = 'open' AND task_id IN (SELECT id FROM tasks WHERE status != 'active'); +`}, {"add-blobs-and-queued-images", ` +CREATE TABLE IF NOT EXISTS blobs ( + id TEXT PRIMARY KEY, + content_type TEXT NOT NULL, + path TEXT NOT NULL, + created_by TEXT NOT NULL REFERENCES users(login), + created_at TEXT NOT NULL +); + +CREATE TABLE turn_queue_next ( + session_id TEXT PRIMARY KEY REFERENCES agent_sessions(id), + actor TEXT NOT NULL, + text TEXT NOT NULL, + images TEXT NOT NULL DEFAULT '[]', + created_at TEXT NOT NULL +); +INSERT INTO turn_queue_next (session_id, actor, text, created_at) SELECT session_id, actor, text, created_at FROM turn_queue; +DROP TABLE turn_queue; +ALTER TABLE turn_queue_next RENAME TO turn_queue; `}} type Store struct { diff --git a/web/dist/api.js b/web/dist/api.js index b750d5d..18a4f40 100644 --- a/web/dist/api.js +++ b/web/dist/api.js @@ -24,7 +24,11 @@ export async function request(method, path, opts = {}) { url += `?${query}`; } const init = { method: method.toUpperCase() }; - if (opts.body !== undefined) { + if (opts.body instanceof Blob) { + init.headers = { "Content-Type": opts.body.type || "application/octet-stream" }; + init.body = opts.body; + } + else if (opts.body !== undefined) { init.headers = { "Content-Type": "application/json" }; init.body = JSON.stringify(opts.body); } diff --git a/web/dist/api.js.map b/web/dist/api.js.map index 1d5550a..81b3546 100644 --- a/web/dist/api.js.map +++ b/web/dist/api.js.map @@ -1 +1 @@ -{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/dist/task.js b/web/dist/task.js index b3e7c25..09fd54e 100644 --- a/web/dist/task.js +++ b/web/dist/task.js @@ -285,22 +285,131 @@ function keepDraft(input, key) { }); return () => composerDrafts.delete(key); } +const imageDrafts = new Map(); +const imageTypes = ["image/png", "image/jpeg", "image/gif", "image/webp"]; +const maxImageBytes = 5 << 20; +function imageTray(form, input, key) { + const el = h("div", { class: "compose-images" }); + const render = () => { + const imgs = imageDrafts.get(key) ?? []; + el.hidden = imgs.length === 0; + replace(el, ...imgs.map((img) => { + const remove = h("button", { type: "button", class: "remove", title: "Remove image" }, "×"); + remove.onclick = () => { + URL.revokeObjectURL(img.url); + const rest = (imageDrafts.get(key) ?? []).filter((i) => i !== img); + if (rest.length > 0) { + imageDrafts.set(key, rest); + } + else { + imageDrafts.delete(key); + } + render(); + }; + return h("div", { class: "thumb" }, h("img", { src: img.url, alt: "attached image" }), remove); + })); + }; + const add = (files) => { + const added = []; + for (const f of files) { + if (!imageTypes.includes(f.type)) { + alert(`${f.name || "That file"} isn't a PNG, JPEG, GIF or WebP image.`); + continue; + } + if (f.size > maxImageBytes) { + alert(`${f.name || "That image"} is over ${maxImageBytes >> 20} MiB.`); + continue; + } + added.push({ file: f, url: URL.createObjectURL(f) }); + } + if (added.length === 0) { + return false; + } + imageDrafts.set(key, [...(imageDrafts.get(key) ?? []), ...added]); + render(); + return true; + }; + input.addEventListener("paste", (e) => { + const files = [...(e.clipboardData?.files ?? [])].filter((f) => f.type.startsWith("image/")); + if (files.length > 0) { + e.preventDefault(); + add(files); + } + }); + form.addEventListener("dragover", (e) => { + if (e.dataTransfer?.types.includes("Files")) { + e.preventDefault(); + form.classList.add("dropping"); + } + }); + form.addEventListener("dragleave", (e) => { + if (!form.contains(e.relatedTarget)) { + form.classList.remove("dropping"); + } + }); + form.addEventListener("drop", (e) => { + form.classList.remove("dropping"); + const files = [...(e.dataTransfer?.files ?? [])]; + if (files.length > 0) { + e.preventDefault(); + add(files); + } + }); + render(); + return { + el, + take: () => { + const imgs = imageDrafts.get(key) ?? []; + imageDrafts.delete(key); + render(); + return imgs; + }, + restore: (imgs) => { + if (imgs.length > 0) { + imageDrafts.set(key, [...imgs, ...(imageDrafts.get(key) ?? [])]); + } + render(); + }, + }; +} +async function uploadImages(imgs) { + if (imgs.length === 0) { + return undefined; + } + const ids = []; + for (const img of imgs) { + const blob = await post("/api/blobs", { body: img.file }); + ids.push(blob.id); + } + return ids; +} +function releaseImages(imgs) { + for (const img of imgs) { + URL.revokeObjectURL(img.url); + } +} function startForm(taskId) { - const prompt = h("textarea", { placeholder: "What should the agent do?", rows: 4, required: true }); + const prompt = h("textarea", { placeholder: "What should the agent do? Paste or drop images to attach them.", rows: 4, required: true }); const clearDraft = keepDraft(prompt, `start:${taskId}`); const model = modelSelect(`start:${taskId}`, models[0]); - const form = h("form", { class: "compose" }, prompt, h("div", { class: "compose-actions" }, model, h("button", { type: "submit" }, "Start agent"))); + const form = h("form", { class: "compose" }); + const tray = imageTray(form, prompt, `start:${taskId}`); + form.append(tray.el, prompt, h("div", { class: "compose-actions" }, model, h("button", { type: "submit" }, "Start agent"))); form.onsubmit = async (e) => { e.preventDefault(); const value = prompt.value; + const imgs = tray.take(); prompt.value = ""; clearDraft(); try { - await post("/api/tasks/{taskId}/sessions", { path: { taskId }, body: { prompt: value, model: model.value } }); + const images = await uploadImages(imgs); + await post("/api/tasks/{taskId}/sessions", { path: { taskId }, body: { prompt: value, model: model.value, images } }); + releaseImages(imgs); } catch (err) { composerDrafts.set(`start:${taskId}`, value); prompt.value = value; + tray.restore(imgs); alert(errorText(err)); } }; @@ -318,7 +427,10 @@ function modelSelect(key, current) { return select; } function messageForm(taskId, sessionId, sessionState, currentModel) { - const text = h("textarea", { placeholder: sessionState === "idle" ? "Message the agent" : "Wait for the turn to end, or interrupt it", rows: 3 }); + const text = h("textarea", { + placeholder: sessionState === "idle" ? "Message the agent. Paste or drop images to attach them." : "Wait for the turn to end, or interrupt it", + rows: 3, + }); const clearDraft = keepDraft(text, `message:${taskId}`); const send = h("button", { type: "submit", disabled: sessionState !== "idle" }, "Send"); const interrupt = h("button", { type: "button", class: "danger", disabled: sessionState !== "running" && sessionState !== "queued" }, sessionState === "queued" ? "Cancel queued turn" : "Interrupt"); @@ -331,25 +443,31 @@ function messageForm(taskId, sessionId, sessionState, currentModel) { } }; const model = modelSelect(`message:${sessionId}`, currentModel); - const form = h("form", { class: "compose" }, text, h("div", { class: "compose-actions" }, model, interrupt, send)); + const form = h("form", { class: "compose" }); + const tray = imageTray(form, text, `message:${taskId}`); + form.append(tray.el, text, h("div", { class: "compose-actions" }, model, interrupt, send)); form.onsubmit = async (e) => { e.preventDefault(); const value = text.value; - if (!value.trim()) { + if (!value.trim() && !imageDrafts.has(`message:${taskId}`)) { return; } + const imgs = tray.take(); text.value = ""; clearDraft(); try { + const images = await uploadImages(imgs); await post("/api/sessions/{sessionId}/messages", { path: { sessionId }, - body: { text: value, model: model.value !== currentModel ? model.value : undefined }, + body: { text: value, images, model: model.value !== currentModel ? model.value : undefined }, }); modelChoices.delete(`message:${sessionId}`); + releaseImages(imgs); } catch (err) { composerDrafts.set(`message:${taskId}`, value); text.value = value; + tray.restore(imgs); alert(errorText(err)); } }; @@ -430,7 +548,7 @@ function renderEvent(ev, ctx) { } return h("details", { class: "note" }, h("summary", {}, first, " ", when), markdown(rest.join("\n"), "text md")); } - return h("div", { class: "msg user" }, h("div", { class: "who" }, ev.actor, " ", when), markdown(String(p["text"]), "text md")); + return h("div", { class: "msg user" }, h("div", { class: "who" }, ev.actor, " ", when), messageImages(p["images"]), String(p["text"] ?? "").trim() ? markdown(String(p["text"]), "text md") : null); case "agent.message": ctx.lastMessage = h("div", { class: "msg agent" }, markdown(String(p["text"]), "text md")); return ctx.lastMessage; @@ -483,6 +601,15 @@ function renderEvent(ev, ctx) { } return null; } +function messageImages(ids) { + if (!Array.isArray(ids) || ids.length === 0) { + return null; + } + return h("div", { class: "msg-images" }, ...ids.map((id) => { + const src = `/api/blobs/${encodeURIComponent(String(id))}`; + return h("a", { href: src, target: "_blank", rel: "noopener" }, h("img", { src, alt: "attached image", loading: "lazy" })); + })); +} async function closeTask(task, prompt, path) { try { const u = await get("/api/tasks/{taskId}/unmerged", { path: { taskId: task.id } }); diff --git a/web/dist/task.js.map b/web/dist/task.js.map index 69168e9..b36246d 100644 --- a/web/dist/task.js.map +++ b/web/dist/task.js.map @@ -1 +1 @@ -{"version":3,"file":"task.js","sourceRoot":"","sources":["../src/task.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"task.js","sourceRoot":"","sources":["../src/task.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/src/api.gen.ts b/web/src/api.gen.ts index b5c4b62..32e8f50 100644 --- a/web/src/api.gen.ts +++ b/web/src/api.gen.ts @@ -299,6 +299,41 @@ export interface paths { patch?: never; trace?: never; }; + "/api/blobs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** @description Stores an image for use in a message. The body is the raw image; its type is detected from the bytes and must be PNG, JPEG, GIF or WebP. At most 5 MiB. */ + post: operations["uploadBlob"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/blobs/{blobId}": { + parameters: { + query?: never; + header?: never; + path: { + blobId: string; + }; + cookie?: never; + }; + get: operations["getBlob"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/inbox": { parameters: { query?: never; @@ -491,12 +526,24 @@ export interface components { SessionCreate: { prompt: string; model?: string; + /** @description Blob ids from uploadBlob, sent to the agent as images alongside the prompt. */ + images?: string[]; }; MessageCreate: { + /** @description May be empty when images are given. */ text: string; + /** @description Blob ids from uploadBlob, sent to the agent as images alongside the text. */ + images?: string[]; /** @description Model for this turn and later ones. When it differs from the session's model, the session switches before the message is sent. */ model?: string; }; + Blob: { + id: string; + content_type: string; + created_by: string; + /** Format: date-time */ + created_at: string; + }; Attachment: { /** @enum {string} */ kind: "file" | "diff" | "image" | "diagram" | "task" | "snapshot" | "url"; @@ -1120,6 +1167,61 @@ export interface operations { 409: components["responses"]["Error"]; }; }; + uploadBlob: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "image/png": string; + "image/jpeg": string; + "image/gif": string; + "image/webp": string; + }; + }; + responses: { + /** @description The stored blob. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Blob"]; + }; + }; + 400: components["responses"]["Error"]; + 413: components["responses"]["Error"]; + }; + }; + getBlob: { + parameters: { + query?: never; + header?: never; + path: { + blobId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The blob's bytes, with its content type. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "image/png": string; + "image/jpeg": string; + "image/gif": string; + "image/webp": string; + }; + }; + 404: components["responses"]["Error"]; + }; + }; listInbox: { parameters: { query?: { diff --git a/web/src/api.ts b/web/src/api.ts index 0badf6c..9c3b9d3 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -23,7 +23,11 @@ type Op

= NonNullable; type Params = O extends { parameters: infer X } ? X : never; type PathParams = Params extends { path: infer X } ? X : never; type QueryParams = Params extends { query?: infer X } ? X : never; -type Body = O extends { requestBody: { content: { "application/json": infer B } } } ? B : never; +type Body = O extends { requestBody: { content: { "application/json": infer B } } } + ? B + : O extends { requestBody: { content: Record<`image/${string}`, unknown> } } + ? Blob + : never; type Success = O extends { responses: infer R } ? R[Extract] : never; type Result = Success extends { content: { "application/json": infer J } } ? J : never; @@ -64,7 +68,10 @@ export async function request>( } const init: RequestInit = { method: method.toUpperCase() }; - if (opts.body !== undefined) { + if (opts.body instanceof Blob) { + init.headers = { "Content-Type": opts.body.type || "application/octet-stream" }; + init.body = opts.body; + } else if (opts.body !== undefined) { init.headers = { "Content-Type": "application/json" }; init.body = JSON.stringify(opts.body); } diff --git a/web/src/task.ts b/web/src/task.ts index 3c7baad..4970252 100644 --- a/web/src/task.ts +++ b/web/src/task.ts @@ -325,21 +325,141 @@ function keepDraft(input: HTMLTextAreaElement, key: string): () => void { return () => composerDrafts.delete(key); } +type PendingImage = { file: Blob; url: string }; + +const imageDrafts = new Map(); + +const imageTypes = ["image/png", "image/jpeg", "image/gif", "image/webp"]; +const maxImageBytes = 5 << 20; + +function imageTray(form: HTMLFormElement, input: HTMLTextAreaElement, key: string): { el: HTMLElement; take: () => PendingImage[]; restore: (imgs: PendingImage[]) => void } { + const el = h("div", { class: "compose-images" }); + const render = () => { + const imgs = imageDrafts.get(key) ?? []; + el.hidden = imgs.length === 0; + replace( + el, + ...imgs.map((img) => { + const remove = h("button", { type: "button", class: "remove", title: "Remove image" }, "×"); + remove.onclick = () => { + URL.revokeObjectURL(img.url); + const rest = (imageDrafts.get(key) ?? []).filter((i) => i !== img); + if (rest.length > 0) { + imageDrafts.set(key, rest); + } else { + imageDrafts.delete(key); + } + render(); + }; + return h("div", { class: "thumb" }, h("img", { src: img.url, alt: "attached image" }), remove); + }), + ); + }; + const add = (files: Iterable) => { + const added: PendingImage[] = []; + for (const f of files) { + if (!imageTypes.includes(f.type)) { + alert(`${f.name || "That file"} isn't a PNG, JPEG, GIF or WebP image.`); + continue; + } + if (f.size > maxImageBytes) { + alert(`${f.name || "That image"} is over ${maxImageBytes >> 20} MiB.`); + continue; + } + added.push({ file: f, url: URL.createObjectURL(f) }); + } + if (added.length === 0) { + return false; + } + imageDrafts.set(key, [...(imageDrafts.get(key) ?? []), ...added]); + render(); + return true; + }; + + input.addEventListener("paste", (e) => { + const files = [...(e.clipboardData?.files ?? [])].filter((f) => f.type.startsWith("image/")); + if (files.length > 0) { + e.preventDefault(); + add(files); + } + }); + form.addEventListener("dragover", (e) => { + if (e.dataTransfer?.types.includes("Files")) { + e.preventDefault(); + form.classList.add("dropping"); + } + }); + form.addEventListener("dragleave", (e) => { + if (!form.contains(e.relatedTarget as Node | null)) { + form.classList.remove("dropping"); + } + }); + form.addEventListener("drop", (e) => { + form.classList.remove("dropping"); + const files = [...(e.dataTransfer?.files ?? [])]; + if (files.length > 0) { + e.preventDefault(); + add(files); + } + }); + + render(); + return { + el, + take: () => { + const imgs = imageDrafts.get(key) ?? []; + imageDrafts.delete(key); + render(); + return imgs; + }, + restore: (imgs) => { + if (imgs.length > 0) { + imageDrafts.set(key, [...imgs, ...(imageDrafts.get(key) ?? [])]); + } + render(); + }, + }; +} + +async function uploadImages(imgs: PendingImage[]): Promise { + if (imgs.length === 0) { + return undefined; + } + const ids: string[] = []; + for (const img of imgs) { + const blob = await post("/api/blobs", { body: img.file }); + ids.push(blob.id); + } + return ids; +} + +function releaseImages(imgs: PendingImage[]): void { + for (const img of imgs) { + URL.revokeObjectURL(img.url); + } +} + function startForm(taskId: string): HTMLElement { - const prompt = h("textarea", { placeholder: "What should the agent do?", rows: 4, required: true }); + const prompt = h("textarea", { placeholder: "What should the agent do? Paste or drop images to attach them.", rows: 4, required: true }); const clearDraft = keepDraft(prompt, `start:${taskId}`); const model = modelSelect(`start:${taskId}`, models[0]!); - const form = h("form", { class: "compose" }, prompt, h("div", { class: "compose-actions" }, model, h("button", { type: "submit" }, "Start agent"))); + const form = h("form", { class: "compose" }); + const tray = imageTray(form, prompt, `start:${taskId}`); + form.append(tray.el, prompt, h("div", { class: "compose-actions" }, model, h("button", { type: "submit" }, "Start agent"))); form.onsubmit = async (e) => { e.preventDefault(); const value = prompt.value; + const imgs = tray.take(); prompt.value = ""; clearDraft(); try { - await post("/api/tasks/{taskId}/sessions", { path: { taskId }, body: { prompt: value, model: model.value } }); + const images = await uploadImages(imgs); + await post("/api/tasks/{taskId}/sessions", { path: { taskId }, body: { prompt: value, model: model.value, images } }); + releaseImages(imgs); } catch (err) { composerDrafts.set(`start:${taskId}`, value); prompt.value = value; + tray.restore(imgs); alert(errorText(err)); } }; @@ -360,7 +480,10 @@ function modelSelect(key: string, current: string): HTMLSelectElement { } function messageForm(taskId: string, sessionId: string, sessionState: string, currentModel: string): HTMLElement { - const text = h("textarea", { placeholder: sessionState === "idle" ? "Message the agent" : "Wait for the turn to end, or interrupt it", rows: 3 }); + const text = h("textarea", { + placeholder: sessionState === "idle" ? "Message the agent. Paste or drop images to attach them." : "Wait for the turn to end, or interrupt it", + rows: 3, + }); const clearDraft = keepDraft(text, `message:${taskId}`); const send = h("button", { type: "submit", disabled: sessionState !== "idle" }, "Send"); const interrupt = h( @@ -377,24 +500,30 @@ function messageForm(taskId: string, sessionId: string, sessionState: string, cu }; const model = modelSelect(`message:${sessionId}`, currentModel); - const form = h("form", { class: "compose" }, text, h("div", { class: "compose-actions" }, model, interrupt, send)); + const form = h("form", { class: "compose" }); + const tray = imageTray(form, text, `message:${taskId}`); + form.append(tray.el, text, h("div", { class: "compose-actions" }, model, interrupt, send)); form.onsubmit = async (e) => { e.preventDefault(); const value = text.value; - if (!value.trim()) { + if (!value.trim() && !imageDrafts.has(`message:${taskId}`)) { return; } + const imgs = tray.take(); text.value = ""; clearDraft(); try { + const images = await uploadImages(imgs); await post("/api/sessions/{sessionId}/messages", { path: { sessionId }, - body: { text: value, model: model.value !== currentModel ? model.value : undefined }, + body: { text: value, images, model: model.value !== currentModel ? model.value : undefined }, }); modelChoices.delete(`message:${sessionId}`); + releaseImages(imgs); } catch (err) { composerDrafts.set(`message:${taskId}`, value); text.value = value; + tray.restore(imgs); alert(errorText(err)); } }; @@ -492,7 +621,13 @@ function renderEvent(ev: ByreEvent, ctx: Ctx): HTMLElement | null { } return h("details", { class: "note" }, h("summary", {}, first, " ", when), markdown(rest.join("\n"), "text md")); } - return h("div", { class: "msg user" }, h("div", { class: "who" }, ev.actor, " ", when), markdown(String(p["text"]), "text md")); + return h( + "div", + { class: "msg user" }, + h("div", { class: "who" }, ev.actor, " ", when), + messageImages(p["images"]), + String(p["text"] ?? "").trim() ? markdown(String(p["text"]), "text md") : null, + ); case "agent.message": ctx.lastMessage = h("div", { class: "msg agent" }, markdown(String(p["text"]), "text md")); @@ -557,6 +692,20 @@ function renderEvent(ev: ByreEvent, ctx: Ctx): HTMLElement | null { return null; } +function messageImages(ids: unknown): HTMLElement | null { + if (!Array.isArray(ids) || ids.length === 0) { + return null; + } + return h( + "div", + { class: "msg-images" }, + ...ids.map((id) => { + const src = `/api/blobs/${encodeURIComponent(String(id))}`; + return h("a", { href: src, target: "_blank", rel: "noopener" }, h("img", { src, alt: "attached image", loading: "lazy" })); + }), + ); +} + async function closeTask(task: Task, prompt: (u: Unmerged) => string, path: "/api/tasks/{taskId}/merge" | "/api/tasks/{taskId}/discard") { try { const u = await get("/api/tasks/{taskId}/unmerged", { path: { taskId: task.id } }); diff --git a/web/style.css b/web/style.css index f1dc046..618301d 100644 --- a/web/style.css +++ b/web/style.css @@ -832,6 +832,51 @@ details.note .md { margin-right: auto; } +.compose.dropping textarea { + border-color: var(--accent); +} + +.compose-images { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 8px; +} + +.compose-images .thumb { + position: relative; +} + +.compose-images img, +.msg-images img { + display: block; + max-width: 120px; + max-height: 90px; + border: 1px solid var(--border); + border-radius: 6px; +} + +.compose-images .remove { + position: absolute; + top: -6px; + right: -6px; + padding: 0 6px; + line-height: 18px; + border-radius: 9px; +} + +.msg-images { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin: 4px 0; +} + +.msg-images img { + max-width: 240px; + max-height: 180px; +} + .inbox-changes { flex-shrink: 0; }