@oar-sh/oar 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/.github/extensions/web-relay/README.md +190 -0
  2. package/.github/extensions/web-relay/extension.mjs +972 -0
  3. package/.github/extensions/web-relay/model-api/model-switching.mjs +338 -0
  4. package/.github/extensions/web-relay/polling/heartbeat.mjs +55 -0
  5. package/.github/extensions/web-relay/polling/polling-loop.mjs +1105 -0
  6. package/.github/extensions/web-relay/runtime/api-client.mjs +34 -0
  7. package/.github/extensions/web-relay/runtime/banner-state.mjs +126 -0
  8. package/.github/extensions/web-relay/runtime/config-loader.mjs +104 -0
  9. package/.github/extensions/web-relay/runtime/debug-log.mjs +27 -0
  10. package/.github/extensions/web-relay/runtime/send-and-wait-errors.mjs +170 -0
  11. package/.github/extensions/web-relay/runtime/session-binding.mjs +78 -0
  12. package/.github/extensions/web-relay/runtime/session-io.mjs +349 -0
  13. package/.github/extensions/web-relay/runtime/session-join-retry.mjs +44 -0
  14. package/.github/extensions/web-relay/runtime/session-registry.mjs +29 -0
  15. package/.github/extensions/web-relay/runtime/session-runtime-manager.mjs +156 -0
  16. package/.github/extensions/web-relay/runtime/session-sync-bridge.mjs +74 -0
  17. package/.github/extensions/web-relay/runtime/worker-websocket-link.mjs +325 -0
  18. package/.github/extensions/web-relay/runtime/workspace-root.mjs +55 -0
  19. package/.github/extensions/web-relay/server-lifecycle/managed-server.mjs +386 -0
  20. package/.github/extensions/web-relay/skills/prompt-context.mjs +160 -0
  21. package/.github/extensions/web-relay/skills/question-bridge.mjs +141 -0
  22. package/.github/extensions/web-relay/skills/question-routing-hooks.mjs +463 -0
  23. package/.github/extensions/web-relay/skills/reasoning-stream.mjs +285 -0
  24. package/.github/extensions/web-relay/skills/subagent-lifecycle.mjs +345 -0
  25. package/.github/extensions/web-relay/skills/tool-activity.mjs +243 -0
  26. package/.github/extensions/web-relay/utils/process-utils.mjs +25 -0
  27. package/.github/extensions/web-relay/utils/token-generation.mjs +58 -0
  28. package/CHANGELOG.md +25 -0
  29. package/LICENSE +21 -0
  30. package/README.md +717 -0
  31. package/bin/bat/oar.bat +46 -0
  32. package/bin/oar.js +665 -0
  33. package/package.json +68 -0
  34. package/server/README.md +1379 -0
  35. package/server/claude-worker/claude-attachments.mjs +134 -0
  36. package/server/claude-worker/claude-preview-tool.mjs +57 -0
  37. package/server/claude-worker/claude-sdk-adapter.mjs +420 -0
  38. package/server/claude-worker/claude-session-process.mjs +2391 -0
  39. package/server/claude-worker/claude-session-test-harness.mjs +236 -0
  40. package/server/claude-worker/claude-session-worker.mjs +186 -0
  41. package/server/claude-worker/claude-transcript-relocator.mjs +189 -0
  42. package/server/claude-worker/claude-turn-publisher.mjs +291 -0
  43. package/server/claude-worker/fixtures/workflow-run-record.json +1 -0
  44. package/server/claude-worker/sdk-message-normalizer.mjs +629 -0
  45. package/server/claude-worker/workflow-progress-digest.mjs +269 -0
  46. package/server/copilot-sdk-runtime.mjs +126 -0
  47. package/server/copilot-worker/copilot-attachments.mjs +97 -0
  48. package/server/copilot-worker/copilot-byok-provider.mjs +121 -0
  49. package/server/copilot-worker/copilot-continuation-signals.mjs +349 -0
  50. package/server/copilot-worker/copilot-plan-board.mjs +110 -0
  51. package/server/copilot-worker/copilot-prompt-context.mjs +86 -0
  52. package/server/copilot-worker/copilot-question-bridge.mjs +216 -0
  53. package/server/copilot-worker/copilot-sdk-adapter.mjs +494 -0
  54. package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +800 -0
  55. package/server/copilot-worker/copilot-sdk-session-process.mjs +1729 -0
  56. package/server/copilot-worker/copilot-sdk-session-worker.mjs +167 -0
  57. package/server/copilot-worker/copilot-sdk-test-harness.mjs +329 -0
  58. package/server/copilot-worker/fixtures/abort-turn.json +588 -0
  59. package/server/copilot-worker/fixtures/ask-user-turn.json +1154 -0
  60. package/server/copilot-worker/fixtures/background-timer-continuation.json +445 -0
  61. package/server/copilot-worker/fixtures/background-timer-turn.json +497 -0
  62. package/server/copilot-worker/fixtures/happy-turn.json +566 -0
  63. package/server/copilot-worker/fixtures/quota-turn.json +647 -0
  64. package/server/copilot-worker/fixtures/reasoning-turn.json +625 -0
  65. package/server/copilot-worker/fixtures/resume-turn.json +700 -0
  66. package/server/copilot-worker/fixtures/subagent-turn.json +1378 -0
  67. package/server/copilot-worker/fixtures/tool-permission-turn.json +1293 -0
  68. package/server/cursor-worker/cursor-ask-user-tool.mjs +83 -0
  69. package/server/cursor-worker/cursor-attachments.mjs +88 -0
  70. package/server/cursor-worker/cursor-context-usage.mjs +85 -0
  71. package/server/cursor-worker/cursor-preview-tool.mjs +44 -0
  72. package/server/cursor-worker/cursor-sdk-adapter.mjs +554 -0
  73. package/server/cursor-worker/cursor-session-worker.mjs +145 -0
  74. package/server/cursor-worker/cursor-subagent-roster.mjs +81 -0
  75. package/server/cursor-worker/cursor-turn-runner.mjs +717 -0
  76. package/server/cursor-worker/sdk-message-normalizer.mjs +485 -0
  77. package/server/db-schema.mjs +932 -0
  78. package/server/features.mjs +106 -0
  79. package/server/grok-worker/acp-client.mjs +434 -0
  80. package/server/grok-worker/acp-host-services.mjs +277 -0
  81. package/server/grok-worker/grok-context-usage.mjs +77 -0
  82. package/server/grok-worker/grok-sdk-adapter.mjs +334 -0
  83. package/server/grok-worker/grok-session-worker.mjs +145 -0
  84. package/server/grok-worker/grok-turn-runner.mjs +523 -0
  85. package/server/grok-worker/sdk-message-normalizer.mjs +311 -0
  86. package/server/migrations/0001-add-structured-answer.mjs +80 -0
  87. package/server/migrations/0002-recent-workspace-roots-path-key.mjs +121 -0
  88. package/server/migrations/0003-push-subscriptions.mjs +87 -0
  89. package/server/public/app/action-confirmations.js +267 -0
  90. package/server/public/app/activity-replay-state.mjs +149 -0
  91. package/server/public/app/api-client.js +1082 -0
  92. package/server/public/app/ask-user-view.js +545 -0
  93. package/server/public/app/attachments-view.js +2112 -0
  94. package/server/public/app/auto-compact-window-options.mjs +59 -0
  95. package/server/public/app/background-tasks-view.mjs +494 -0
  96. package/server/public/app/bootstrap.js +4347 -0
  97. package/server/public/app/claude-auth-ui.js +394 -0
  98. package/server/public/app/claude-thinking-options.mjs +38 -0
  99. package/server/public/app/cli-install-ui.js +455 -0
  100. package/server/public/app/code-copy.mjs +85 -0
  101. package/server/public/app/composer-attachment-cache.mjs +151 -0
  102. package/server/public/app/composer-control-state.mjs +107 -0
  103. package/server/public/app/composer-paste.mjs +132 -0
  104. package/server/public/app/context-tier-options.mjs +68 -0
  105. package/server/public/app/context-usage-view.mjs +338 -0
  106. package/server/public/app/conversation-draft-timestamp-utils.mjs +11 -0
  107. package/server/public/app/conversation-preferences.mjs +91 -0
  108. package/server/public/app/conversation-provider-indicator.mjs +88 -0
  109. package/server/public/app/conversation-view.js +2898 -0
  110. package/server/public/app/cwd-menu-placement.mjs +85 -0
  111. package/server/public/app/cwd-picker.js +605 -0
  112. package/server/public/app/emoji-view.js +101 -0
  113. package/server/public/app/external-link-policy.mjs +80 -0
  114. package/server/public/app/file-preview-navigation.mjs +51 -0
  115. package/server/public/app/font-scaling.js +262 -0
  116. package/server/public/app/git-changes-view.js +339 -0
  117. package/server/public/app/git-diff-model.mjs +109 -0
  118. package/server/public/app/grok-auth-ui.js +387 -0
  119. package/server/public/app/image-downscale.mjs +174 -0
  120. package/server/public/app/in-flight-snapshot.mjs +40 -0
  121. package/server/public/app/infinite-loader.js +164 -0
  122. package/server/public/app/journal-view.js +1141 -0
  123. package/server/public/app/known-cwd-options.mjs +41 -0
  124. package/server/public/app/launch-cwd-path.mjs +23 -0
  125. package/server/public/app/live-message-dedupe.mjs +57 -0
  126. package/server/public/app/message-search-view.js +242 -0
  127. package/server/public/app/model-selector-options.mjs +31 -0
  128. package/server/public/app/model-vision-support.mjs +56 -0
  129. package/server/public/app/new-conversation-model-choice.mjs +67 -0
  130. package/server/public/app/plan-usage-view.mjs +307 -0
  131. package/server/public/app/preview-cards.mjs +273 -0
  132. package/server/public/app/preview-command.mjs +103 -0
  133. package/server/public/app/push-settings.js +370 -0
  134. package/server/public/app/pwa-install.js +456 -0
  135. package/server/public/app/question-schema-view.mjs +71 -0
  136. package/server/public/app/reasoning-effort-labels.mjs +42 -0
  137. package/server/public/app/relay-board-view.js +117 -0
  138. package/server/public/app/relay-dot-state.mjs +69 -0
  139. package/server/public/app/relay-error-ctas.mjs +52 -0
  140. package/server/public/app/relay-prompt-sanitizer.mjs +114 -0
  141. package/server/public/app/relay-thoughts.mjs +98 -0
  142. package/server/public/app/relay-watchdog-policy.mjs +29 -0
  143. package/server/public/app/repo-browser-preferences.mjs +79 -0
  144. package/server/public/app/repo-browser-tree-state.mjs +97 -0
  145. package/server/public/app/router.js +657 -0
  146. package/server/public/app/selection-guard.mjs +93 -0
  147. package/server/public/app/settings-modal.js +1484 -0
  148. package/server/public/app/settings-tabs.js +129 -0
  149. package/server/public/app/slash-autocomplete.mjs +188 -0
  150. package/server/public/app/slash-commands.mjs +183 -0
  151. package/server/public/app/socket-handlers.js +703 -0
  152. package/server/public/app/status-store.mjs +242 -0
  153. package/server/public/app/status-view.mjs +315 -0
  154. package/server/public/app/store.js +1454 -0
  155. package/server/public/app/stream-state.mjs +73 -0
  156. package/server/public/app/streaming-dom-patch.mjs +23 -0
  157. package/server/public/app/sync-outbox.mjs +208 -0
  158. package/server/public/app/tap-actions.js +62 -0
  159. package/server/public/app/thread-order.mjs +54 -0
  160. package/server/public/app/tmux-inspector-view.js +422 -0
  161. package/server/public/app/transcript-separators.mjs +282 -0
  162. package/server/public/app-icon-192.png +0 -0
  163. package/server/public/app-icon-512.png +0 -0
  164. package/server/public/app-icon.svg +4 -0
  165. package/server/public/favicon.ico +0 -0
  166. package/server/public/index.html +5743 -0
  167. package/server/public/manifest.webmanifest +32 -0
  168. package/server/public/sw.js +307 -0
  169. package/server/relay-exit-codes.mjs +4 -0
  170. package/server/relay-self-restart.mjs +170 -0
  171. package/server/relay-tools.md +27 -0
  172. package/server/relay.mjs +1026 -0
  173. package/server/repositories/image-conversation-repository.mjs +317 -0
  174. package/server/repositories/message-repository.mjs +434 -0
  175. package/server/repositories/question-repository.mjs +161 -0
  176. package/server/repositories/session-repository.mjs +320 -0
  177. package/server/repositories/share-visibility-statements.mjs +24 -0
  178. package/server/routes/ask-user-routes.mjs +292 -0
  179. package/server/routes/cache-routes.mjs +44 -0
  180. package/server/routes/git-routes.mjs +92 -0
  181. package/server/routes/messages-routes-test-harness.mjs +111 -0
  182. package/server/routes/messages-routes.mjs +7107 -0
  183. package/server/routes/preview-routes.mjs +108 -0
  184. package/server/routes/push-routes.mjs +59 -0
  185. package/server/routes/relay-board-routes.mjs +268 -0
  186. package/server/routes/sessions-routes.mjs +6150 -0
  187. package/server/server-runtime.mjs +7463 -0
  188. package/server/server.js +24 -0
  189. package/server/services/ask-user-routing-service.mjs +117 -0
  190. package/server/services/cache-rebuild-service.mjs +236 -0
  191. package/server/services/claude-auth-service.mjs +578 -0
  192. package/server/services/claude-context-usage.mjs +240 -0
  193. package/server/services/claude-session-root-service.mjs +228 -0
  194. package/server/services/cli-install-service.mjs +1152 -0
  195. package/server/services/cli-process-runner.mjs +181 -0
  196. package/server/services/cloudflared-tunnel-service.mjs +358 -0
  197. package/server/services/context-snapshot-service.mjs +499 -0
  198. package/server/services/context-usage-view.mjs +160 -0
  199. package/server/services/conversation-preferences-service.mjs +208 -0
  200. package/server/services/cursor-dashboard-usage.mjs +217 -0
  201. package/server/services/cursor-session-root-service.mjs +62 -0
  202. package/server/services/delete-archive-service.mjs +154 -0
  203. package/server/services/drives-path-helpers.mjs +66 -0
  204. package/server/services/fixtures/claude-auth-stub.sh +62 -0
  205. package/server/services/fixtures/cli-install-stub.sh +75 -0
  206. package/server/services/fixtures/grok-stub.sh +86 -0
  207. package/server/services/generated-image-cleanup-service.mjs +114 -0
  208. package/server/services/git-changes-service.mjs +195 -0
  209. package/server/services/github-billing-usage.mjs +133 -0
  210. package/server/services/grok-auth-service.mjs +593 -0
  211. package/server/services/grok-billing-usage.mjs +120 -0
  212. package/server/services/image-context-service.mjs +106 -0
  213. package/server/services/image-operation-service.mjs +135 -0
  214. package/server/services/keyed-mutex-service.mjs +82 -0
  215. package/server/services/mime-sniffer.mjs +118 -0
  216. package/server/services/oar-cli-helpers.mjs +109 -0
  217. package/server/services/oar-state-migration-service.mjs +243 -0
  218. package/server/services/plan-usage-claude.mjs +387 -0
  219. package/server/services/plan-usage-contract.mjs +271 -0
  220. package/server/services/plan-usage-copilot.mjs +421 -0
  221. package/server/services/plan-usage-cursor.mjs +566 -0
  222. package/server/services/plan-usage-grok.mjs +436 -0
  223. package/server/services/plan-usage-service.mjs +487 -0
  224. package/server/services/preview-health-probe.mjs +82 -0
  225. package/server/services/preview-proxy-server.mjs +498 -0
  226. package/server/services/preview-registry-service.mjs +457 -0
  227. package/server/services/preview-static-handler.mjs +237 -0
  228. package/server/services/provider-model-selection.mjs +91 -0
  229. package/server/services/provider-reasoning-effort.mjs +94 -0
  230. package/server/services/push-active-device-service.mjs +57 -0
  231. package/server/services/push-dispatch-service.mjs +459 -0
  232. package/server/services/rate-limit-service.mjs +63 -0
  233. package/server/services/relay-bridge-owner-service.mjs +84 -0
  234. package/server/services/relay-cli-launcher-service.mjs +209 -0
  235. package/server/services/relay-prompt-sanitizer.mjs +137 -0
  236. package/server/services/relay-queue-gate-service.mjs +38 -0
  237. package/server/services/relay-restart-orchestrator-service.mjs +935 -0
  238. package/server/services/relay-singleton-guard.mjs +124 -0
  239. package/server/services/safe-served-content.mjs +57 -0
  240. package/server/services/sdk-session-import-service.mjs +271 -0
  241. package/server/services/sdk-session-sync-service.mjs +207 -0
  242. package/server/services/session-history-refresh-service.mjs +497 -0
  243. package/server/services/session-transcript-service.mjs +419 -0
  244. package/server/services/session-worker-launch-service.mjs +786 -0
  245. package/server/services/session-worker-process-service.mjs +319 -0
  246. package/server/services/session-worker-registry-service.mjs +194 -0
  247. package/server/services/session-worker-stop-service.mjs +141 -0
  248. package/server/services/session-worker-supervisor-service.mjs +898 -0
  249. package/server/services/session-worker-websocket-service.mjs +483 -0
  250. package/server/services/ssh-tunnel-manager-service.mjs +423 -0
  251. package/server/services/status-event-service.mjs +158 -0
  252. package/server/services/tmux-inspector-access-policy.mjs +77 -0
  253. package/server/services/tmux-inspector-socket-service.mjs +191 -0
  254. package/server/services/tmux-inspector-stream-service.mjs +552 -0
  255. package/server/services/tunnel-worker-path-guard.mjs +107 -0
  256. package/server/services/upload-sweep.mjs +60 -0
  257. package/server/services/usage-snapshot-helpers.mjs +209 -0
  258. package/server/services/windows-autostart-service.mjs +115 -0
  259. package/server/services/workspace-root-defaults-service.mjs +34 -0
  260. package/server/services/workspace-root-path-policy.mjs +196 -0
  261. package/server/services/workspace-root-relaunch-service.mjs +132 -0
  262. package/server/services/workspace-symlink-guard.mjs +54 -0
  263. package/server/tools/capture-proxy.mjs +173 -0
  264. package/server/tty-console-bootstrap.mjs +75 -0
  265. package/server/uploads/README.md +36 -0
  266. package/server/uploads/image_input.md +540 -0
  267. package/server/windows-terminal-launcher.mjs +29 -0
  268. package/server/workspace-root.mjs +87 -0
  269. package/shared/ask-user-bridge.mjs +131 -0
  270. package/shared/auto-compact-window.mjs +116 -0
  271. package/shared/background-task-timeout.mjs +52 -0
  272. package/shared/claude-thinking.mjs +101 -0
  273. package/shared/context-window-fallbacks.mjs +148 -0
  274. package/shared/control-poller.mjs +115 -0
  275. package/shared/empty-turn-completion.mjs +20 -0
  276. package/shared/model-catalog-freshness.mjs +11 -0
  277. package/shared/model-descriptors.mjs +118 -0
  278. package/shared/model-id.mjs +147 -0
  279. package/shared/model-refresh.mjs +24 -0
  280. package/shared/openai-reasoning.mjs +13 -0
  281. package/shared/plan-lines.mjs +13 -0
  282. package/shared/preview-instructions.mjs +77 -0
  283. package/shared/preview-tool-core.mjs +191 -0
  284. package/shared/provider-routing.mjs +29 -0
  285. package/shared/question-schema.mjs +315 -0
  286. package/shared/question-timeout.mjs +18 -0
  287. package/shared/stream-emit-gating.mjs +28 -0
  288. package/shared/subagent-run-id.mjs +17 -0
  289. package/shared/thought-cap.mjs +13 -0
  290. package/shared/tool-activity.mjs +98 -0
  291. package/shared/turn-ceiling.mjs +53 -0
  292. package/shared/worker-bootstrap.mjs +44 -0
  293. package/shared/worker-crash-guard.mjs +60 -0
@@ -0,0 +1,540 @@
1
+ # Image input
2
+
3
+ Send images to Copilot sessions as attachments. There are two ways to attach images:
4
+
5
+ * **File attachment** (`type: "file"`): provide an absolute path; the runtime reads the file from disk, converts it to base64, and sends it to the LLM.
6
+ * **Blob attachment** (`type: "blob"`): provide base64-encoded data directly; useful when the image is already in memory (e.g., screenshots, generated images, or data from an API).
7
+
8
+ ## Overview
9
+
10
+ ```mermaid
11
+ sequenceDiagram
12
+ participant App as Your App
13
+ participant SDK as SDK Session
14
+ participant RT as Copilot Runtime
15
+ participant LLM as Vision Model
16
+
17
+ App->>SDK: send({ prompt, attachments: [{ type: "file", path }] })
18
+ SDK->>RT: JSON-RPC with file attachment
19
+ RT->>RT: Read file from disk
20
+ RT->>RT: Detect image, convert to base64
21
+ RT->>RT: Resize if needed (model-specific limits)
22
+ RT->>LLM: image_url content block (base64)
23
+ LLM-->>RT: Response referencing the image
24
+ RT-->>SDK: assistant.message events
25
+ SDK-->>App: event stream
26
+ ```
27
+
28
+ | Concept | Description |
29
+ |---------|-------------|
30
+ | **File attachment** | An attachment with `type: "file"` and an absolute `path` to an image on disk |
31
+ | **Blob attachment** | An attachment with `type: "blob"`, base64-encoded `data`, and a `mimeType`—no disk I/O needed |
32
+ | **Automatic encoding** | For file attachments, the runtime reads the image and converts it to base64 automatically |
33
+ | **Auto-resize** | The runtime automatically resizes or quality-reduces images that exceed model-specific limits |
34
+ | **Vision capability** | The model must have `capabilities.supports.vision = true` to process images |
35
+
36
+ ## Quick start—file attachment
37
+
38
+ Attach an image file to any message using the file attachment type. The path must be an absolute path to an image on disk.
39
+
40
+ <details open>
41
+ <summary><strong>Node.js / TypeScript</strong></summary>
42
+
43
+ ```typescript
44
+ import { CopilotClient } from "@github/copilot-sdk";
45
+
46
+ const client = new CopilotClient();
47
+ await client.start();
48
+
49
+ const session = await client.createSession({
50
+ model: "gpt-4.1",
51
+ onPermissionRequest: async () => ({ kind: "approved" }),
52
+ });
53
+
54
+ await session.send({
55
+ prompt: "Describe what you see in this image",
56
+ attachments: [
57
+ {
58
+ type: "file",
59
+ path: "/absolute/path/to/screenshot.png",
60
+ },
61
+ ],
62
+ });
63
+ ```
64
+
65
+ </details>
66
+
67
+ <details>
68
+ <summary><strong>Python</strong></summary>
69
+
70
+ ```python
71
+ from copilot import CopilotClient
72
+ from copilot.session import PermissionRequestResult
73
+
74
+ client = CopilotClient()
75
+ await client.start()
76
+
77
+ session = await client.create_session(
78
+ on_permission_request=lambda req, inv: PermissionRequestResult(kind="approved"),
79
+ model="gpt-4.1",
80
+ )
81
+
82
+ await session.send(
83
+ "Describe what you see in this image",
84
+ attachments=[
85
+ {
86
+ "type": "file",
87
+ "path": "/absolute/path/to/screenshot.png",
88
+ },
89
+ ],
90
+ )
91
+ ```
92
+
93
+ </details>
94
+
95
+ <details>
96
+ <summary><strong>Go</strong></summary>
97
+
98
+ <!-- docs-validate: hidden -->
99
+ ```go
100
+ package main
101
+
102
+ import (
103
+ "context"
104
+ copilot "github.com/github/copilot-sdk/go"
105
+ )
106
+
107
+ func main() {
108
+ ctx := context.Background()
109
+ client := copilot.NewClient(nil)
110
+ client.Start(ctx)
111
+
112
+ session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
113
+ Model: "gpt-4.1",
114
+ OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
115
+ return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindApproved}, nil
116
+ },
117
+ })
118
+
119
+ path := "/absolute/path/to/screenshot.png"
120
+ session.Send(ctx, copilot.MessageOptions{
121
+ Prompt: "Describe what you see in this image",
122
+ Attachments: []copilot.Attachment{
123
+ &copilot.UserMessageAttachmentFile{
124
+ DisplayName: "screenshot.png",
125
+ Path: path,
126
+ },
127
+ },
128
+ })
129
+ }
130
+ ```
131
+ <!-- /docs-validate: hidden -->
132
+
133
+ ```go
134
+ ctx := context.Background()
135
+ client := copilot.NewClient(nil)
136
+ client.Start(ctx)
137
+
138
+ session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
139
+ Model: "gpt-4.1",
140
+ OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
141
+ return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindApproved}, nil
142
+ },
143
+ })
144
+
145
+ path := "/absolute/path/to/screenshot.png"
146
+ session.Send(ctx, copilot.MessageOptions{
147
+ Prompt: "Describe what you see in this image",
148
+ Attachments: []copilot.Attachment{
149
+ &copilot.UserMessageAttachmentFile{
150
+ DisplayName: "screenshot.png",
151
+ Path: path,
152
+ },
153
+ },
154
+ })
155
+ ```
156
+
157
+ </details>
158
+
159
+ <details>
160
+ <summary><strong>.NET</strong></summary>
161
+
162
+ <!-- docs-validate: hidden -->
163
+ ```csharp
164
+ using GitHub.Copilot.SDK;
165
+
166
+ public static class ImageInputExample
167
+ {
168
+ public static async Task Main()
169
+ {
170
+ await using var client = new CopilotClient();
171
+ await using var session = await client.CreateSessionAsync(new SessionConfig
172
+ {
173
+ Model = "gpt-4.1",
174
+ OnPermissionRequest = (req, inv) =>
175
+ Task.FromResult(new PermissionRequestResult { Kind = PermissionRequestResultKind.Approved }),
176
+ });
177
+
178
+ await session.SendAsync(new MessageOptions
179
+ {
180
+ Prompt = "Describe what you see in this image",
181
+ Attachments = new List<UserMessageAttachment>
182
+ {
183
+ new UserMessageAttachmentFile
184
+ {
185
+ Path = "/absolute/path/to/screenshot.png",
186
+ DisplayName = "screenshot.png",
187
+ },
188
+ },
189
+ });
190
+ }
191
+ }
192
+ ```
193
+ <!-- /docs-validate: hidden -->
194
+
195
+ ```csharp
196
+ using GitHub.Copilot.SDK;
197
+
198
+ await using var client = new CopilotClient();
199
+ await using var session = await client.CreateSessionAsync(new SessionConfig
200
+ {
201
+ Model = "gpt-4.1",
202
+ OnPermissionRequest = (req, inv) =>
203
+ Task.FromResult(new PermissionRequestResult { Kind = PermissionRequestResultKind.Approved }),
204
+ });
205
+
206
+ await session.SendAsync(new MessageOptions
207
+ {
208
+ Prompt = "Describe what you see in this image",
209
+ Attachments = new List<UserMessageAttachment>
210
+ {
211
+ new UserMessageAttachmentFile
212
+ {
213
+ Path = "/absolute/path/to/screenshot.png",
214
+ DisplayName = "screenshot.png",
215
+ },
216
+ },
217
+ });
218
+ ```
219
+
220
+ </details>
221
+
222
+ <details>
223
+ <summary><strong>Java</strong></summary>
224
+
225
+ ```java
226
+ import com.github.copilot.sdk.CopilotClient;
227
+ import com.github.copilot.sdk.events.*;
228
+ import com.github.copilot.sdk.json.*;
229
+ import java.util.List;
230
+
231
+ try (var client = new CopilotClient()) {
232
+ client.start().get();
233
+
234
+ var session = client.createSession(
235
+ new SessionConfig()
236
+ .setModel("gpt-4.1")
237
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
238
+ ).get();
239
+
240
+ session.send(new MessageOptions()
241
+ .setPrompt("Describe what you see in this image")
242
+ .setAttachments(List.of(
243
+ new Attachment("file", "/absolute/path/to/screenshot.png", "screenshot.png")
244
+ ))
245
+ ).get();
246
+ }
247
+ ```
248
+
249
+ </details>
250
+
251
+ ## Quick start—blob attachment
252
+
253
+ When you already have image data in memory (e.g., a screenshot captured by your app, or an image fetched from an API), use a blob attachment to send it directly without writing to disk.
254
+
255
+ <details open>
256
+ <summary><strong>Node.js / TypeScript</strong></summary>
257
+
258
+ ```typescript
259
+ import { CopilotClient } from "@github/copilot-sdk";
260
+
261
+ const client = new CopilotClient();
262
+ await client.start();
263
+
264
+ const session = await client.createSession({
265
+ model: "gpt-4.1",
266
+ onPermissionRequest: async () => ({ kind: "approved" }),
267
+ });
268
+
269
+ const base64ImageData = "..."; // your base64-encoded image
270
+ await session.send({
271
+ prompt: "Describe what you see in this image",
272
+ attachments: [
273
+ {
274
+ type: "blob",
275
+ data: base64ImageData,
276
+ mimeType: "image/png",
277
+ displayName: "screenshot.png",
278
+ },
279
+ ],
280
+ });
281
+ ```
282
+
283
+ </details>
284
+
285
+ <details>
286
+ <summary><strong>Python</strong></summary>
287
+
288
+ ```python
289
+ from copilot import CopilotClient
290
+ from copilot.session import PermissionRequestResult
291
+
292
+ client = CopilotClient()
293
+ await client.start()
294
+
295
+ session = await client.create_session(
296
+ on_permission_request=lambda req, inv: PermissionRequestResult(kind="approved"),
297
+ model="gpt-4.1",
298
+ )
299
+
300
+ base64_image_data = "..." # your base64-encoded image
301
+ await session.send(
302
+ "Describe what you see in this image",
303
+ attachments=[
304
+ {
305
+ "type": "blob",
306
+ "data": base64_image_data,
307
+ "mimeType": "image/png",
308
+ "displayName": "screenshot.png",
309
+ },
310
+ ],
311
+ )
312
+ ```
313
+
314
+ </details>
315
+
316
+ <details>
317
+ <summary><strong>Go</strong></summary>
318
+
319
+ <!-- docs-validate: hidden -->
320
+ ```go
321
+ package main
322
+
323
+ import (
324
+ "context"
325
+ copilot "github.com/github/copilot-sdk/go"
326
+ )
327
+
328
+ func main() {
329
+ ctx := context.Background()
330
+ client := copilot.NewClient(nil)
331
+ client.Start(ctx)
332
+
333
+ session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
334
+ Model: "gpt-4.1",
335
+ OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
336
+ return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindApproved}, nil
337
+ },
338
+ })
339
+
340
+ base64ImageData := "..."
341
+ mimeType := "image/png"
342
+ displayName := "screenshot.png"
343
+ session.Send(ctx, copilot.MessageOptions{
344
+ Prompt: "Describe what you see in this image",
345
+ Attachments: []copilot.Attachment{
346
+ &copilot.UserMessageAttachmentBlob{
347
+ Data: base64ImageData,
348
+ MIMEType: mimeType,
349
+ DisplayName: &displayName,
350
+ },
351
+ },
352
+ })
353
+ }
354
+ ```
355
+ <!-- /docs-validate: hidden -->
356
+
357
+ ```go
358
+ mimeType := "image/png"
359
+ displayName := "screenshot.png"
360
+ session.Send(ctx, copilot.MessageOptions{
361
+ Prompt: "Describe what you see in this image",
362
+ Attachments: []copilot.Attachment{
363
+ &copilot.UserMessageAttachmentBlob{
364
+ Data: base64ImageData, // base64-encoded string
365
+ MIMEType: mimeType,
366
+ DisplayName: &displayName,
367
+ },
368
+ },
369
+ })
370
+ ```
371
+
372
+ </details>
373
+
374
+ <details>
375
+ <summary><strong>.NET</strong></summary>
376
+
377
+ <!-- docs-validate: hidden -->
378
+ ```csharp
379
+ using GitHub.Copilot.SDK;
380
+
381
+ public static class BlobAttachmentExample
382
+ {
383
+ public static async Task Main()
384
+ {
385
+ await using var client = new CopilotClient();
386
+ await using var session = await client.CreateSessionAsync(new SessionConfig
387
+ {
388
+ Model = "gpt-4.1",
389
+ OnPermissionRequest = (req, inv) =>
390
+ Task.FromResult(new PermissionRequestResult { Kind = PermissionRequestResultKind.Approved }),
391
+ });
392
+
393
+ var base64ImageData = "...";
394
+ await session.SendAsync(new MessageOptions
395
+ {
396
+ Prompt = "Describe what you see in this image",
397
+ Attachments = new List<UserMessageAttachment>
398
+ {
399
+ new UserMessageAttachmentBlob
400
+ {
401
+ Data = base64ImageData,
402
+ MimeType = "image/png",
403
+ DisplayName = "screenshot.png",
404
+ },
405
+ },
406
+ });
407
+ }
408
+ }
409
+ ```
410
+ <!-- /docs-validate: hidden -->
411
+
412
+ ```csharp
413
+ await session.SendAsync(new MessageOptions
414
+ {
415
+ Prompt = "Describe what you see in this image",
416
+ Attachments = new List<UserMessageAttachment>
417
+ {
418
+ new UserMessageAttachmentBlob
419
+ {
420
+ Data = base64ImageData,
421
+ MimeType = "image/png",
422
+ DisplayName = "screenshot.png",
423
+ },
424
+ },
425
+ });
426
+ ```
427
+
428
+ </details>
429
+
430
+ <details>
431
+ <summary><strong>Java</strong></summary>
432
+
433
+ ```java
434
+ import com.github.copilot.sdk.CopilotClient;
435
+ import com.github.copilot.sdk.events.*;
436
+ import com.github.copilot.sdk.json.*;
437
+ import java.util.List;
438
+
439
+ try (var client = new CopilotClient()) {
440
+ client.start().get();
441
+
442
+ var session = client.createSession(
443
+ new SessionConfig()
444
+ .setModel("gpt-4.1")
445
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
446
+ ).get();
447
+
448
+ var base64ImageData = "..."; // your base64-encoded image
449
+ session.send(new MessageOptions()
450
+ .setPrompt("Describe what you see in this image")
451
+ .setAttachments(List.of(
452
+ new BlobAttachment()
453
+ .setData(base64ImageData)
454
+ .setMimeType("image/png")
455
+ .setDisplayName("screenshot.png")
456
+ ))
457
+ ).get();
458
+ }
459
+ ```
460
+
461
+ </details>
462
+
463
+ ## Supported formats
464
+
465
+ Supported image formats include JPG, PNG, GIF, and other common image types. For file attachments, the runtime reads the image from disk and converts it as needed. For blob attachments, you provide the base64 data and MIME type directly. Use PNG or JPEG for best results, as these are the most widely supported formats.
466
+
467
+ The model's `capabilities.limits.vision.supported_media_types` field lists the exact MIME types it accepts.
468
+
469
+ ## Automatic processing
470
+
471
+ The runtime automatically processes images to fit within the model's constraints. No manual resizing is required.
472
+
473
+ * Images that exceed the model's dimension or size limits are automatically resized (preserving aspect ratio) or quality-reduced.
474
+ * If an image cannot be brought within limits after processing, it is skipped and not sent to the LLM.
475
+ * The model's `capabilities.limits.vision.max_prompt_image_size` field indicates the maximum image size in bytes.
476
+
477
+ You can check these limits at runtime via the model capabilities object. For the best experience, use reasonably-sized PNG or JPEG images.
478
+
479
+ ## Vision model capabilities
480
+
481
+ Not all models support vision. Check the model's capabilities before sending images.
482
+
483
+ ### Capability fields
484
+
485
+ | Field | Type | Description |
486
+ |-------|------|-------------|
487
+ | `capabilities.supports.vision` | `boolean` | Whether the model can process image inputs |
488
+ | `capabilities.limits.vision.supported_media_types` | `string[]` | MIME types the model accepts (e.g., `["image/png", "image/jpeg"]`) |
489
+ | `capabilities.limits.vision.max_prompt_images` | `number` | Maximum number of images per prompt |
490
+ | `capabilities.limits.vision.max_prompt_image_size` | `number` | Maximum image size in bytes |
491
+
492
+ ### Vision limits type
493
+
494
+ <!-- docs-validate: hidden -->
495
+ ```typescript
496
+ interface VisionCapabilities {
497
+ vision?: {
498
+ supported_media_types: string[];
499
+ max_prompt_images: number;
500
+ max_prompt_image_size: number; // bytes
501
+ };
502
+ }
503
+ ```
504
+ <!-- /docs-validate: hidden -->
505
+ ```typescript
506
+ vision?: {
507
+ supported_media_types: string[];
508
+ max_prompt_images: number;
509
+ max_prompt_image_size: number; // bytes
510
+ };
511
+ ```
512
+
513
+ ## Receiving image results
514
+
515
+ When tools return images (e.g., screenshots or generated charts), the result contains `"image"` content blocks with base64-encoded data.
516
+
517
+ | Field | Type | Description |
518
+ |-------|------|-------------|
519
+ | `type` | `"image"` | Content block type discriminator |
520
+ | `data` | `string` | Base64-encoded image data |
521
+ | `mimeType` | `string` | MIME type (e.g., `"image/png"`) |
522
+
523
+ These image blocks appear in `tool.execution_complete` event results. See the [Streaming Events](./streaming-events.md) guide for the full event lifecycle.
524
+
525
+ ## Tips and limitations
526
+
527
+ | Tip | Details |
528
+ |-----|---------|
529
+ | **Use PNG or JPEG directly** | Avoids conversion overhead—these are sent to the LLM as-is |
530
+ | **Keep images reasonably sized** | Large images may be quality-reduced, which can lose important details |
531
+ | **Use absolute paths for file attachments** | The runtime reads files from disk; relative paths may not resolve correctly |
532
+ | **Use blob attachments for in-memory data** | When you already have base64 data (e.g., screenshots, API responses), blob avoids unnecessary disk I/O |
533
+ | **Check vision support first** | Sending images to a non-vision model wastes tokens without visual understanding |
534
+ | **Multiple images are supported** | Attach several attachments in one message, up to the model's `max_prompt_images` limit |
535
+ | **SVG is not supported** | SVG files are text-based and excluded from image processing |
536
+
537
+ ## See also
538
+
539
+ * [Streaming Events](./streaming-events.md): event lifecycle including tool result content blocks
540
+ * [Steering & Queueing](./steering-and-queueing.md): sending follow-up messages with attachments
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ import { createHash } from 'crypto';
4
+ import path from 'path';
5
+
6
+ function normalizeWorkspaceRoot(workspaceRoot) {
7
+ return path.resolve(String(workspaceRoot || '').trim() || process.cwd());
8
+ }
9
+
10
+ export function buildWindowsTerminalWindowName(workspaceRoot, prefix = 'copilot-relay') {
11
+ const normalizedRoot = normalizeWorkspaceRoot(workspaceRoot).replace(/[\\/]+/g, '/').toLowerCase();
12
+ const fingerprint = createHash('sha256').update(normalizedRoot).digest('hex').slice(0, 12);
13
+ return `${prefix}-${fingerprint}`;
14
+ }
15
+
16
+ export function buildWindowsTerminalForegroundArgs({
17
+ workspaceRoot,
18
+ windowName,
19
+ title = 'Copilot Relay',
20
+ commandPath,
21
+ commandArgs = [],
22
+ }) {
23
+ const args = ['-w', String(windowName || '').trim()];
24
+ args.push('new-tab');
25
+ args.push('--startingDirectory', normalizeWorkspaceRoot(workspaceRoot));
26
+ args.push('--title', String(title || '').trim() || 'Copilot Relay');
27
+ args.push(commandPath, ...commandArgs);
28
+ return args;
29
+ }
@@ -0,0 +1,87 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ import { normalizeDriveLetterOnlyPath } from './services/workspace-root-path-policy.mjs';
5
+
6
+ // Mirrors workspace-root-path-policy.mjs: the platform is a parameter, never an
7
+ // implicit read of the host, so win32 `cd` semantics stay testable off Windows.
8
+ function pathFor(platform) {
9
+ return platform === 'win32' ? path.win32 : path.posix;
10
+ }
11
+
12
+ function isDirectory(targetPath) {
13
+ try {
14
+ return fs.statSync(targetPath).isDirectory();
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ export function resolveWorkspaceRootPath(preferredRoot, fallbackRoot) {
21
+ const candidates = [preferredRoot, fallbackRoot];
22
+ for (const candidate of candidates) {
23
+ const value = String(candidate || '').trim();
24
+ if (!value) continue;
25
+ const resolved = path.resolve(value);
26
+ if (isDirectory(resolved)) return resolved;
27
+ }
28
+ return path.resolve(String(fallbackRoot || process.cwd()).trim() || process.cwd());
29
+ }
30
+
31
+ export function resolveRepositoryWorkspaceRoot(preferredRoot, scriptDir) {
32
+ const repoRoot = path.resolve(String(scriptDir || '').trim() || process.cwd(), '..');
33
+ return resolveWorkspaceRootPath(preferredRoot, repoRoot);
34
+ }
35
+
36
+ export function resolveStartupWorkspaceRoot(scriptDir, preferredRoot = process.env.COPILOT_WORKSPACE_ROOT || process.cwd()) {
37
+ return resolveRepositoryWorkspaceRoot(preferredRoot, scriptDir);
38
+ }
39
+
40
+ export function workspaceRootDisplayName(rootPath) {
41
+ const resolved = path.resolve(String(rootPath || '').trim() || process.cwd());
42
+ const base = path.basename(resolved);
43
+ if (base) return base;
44
+ const driveRoot = path.parse(resolved).root;
45
+ return driveRoot || 'workspace';
46
+ }
47
+
48
+ export function parseCdCommandTarget(text) {
49
+ const source = String(text || '').trim();
50
+ if (!source || source.includes('\n') || source.includes('\r')) return null;
51
+
52
+ const match = /^cd(?:\s+\/d)?(?:\s+(.+))?$/i.exec(source);
53
+ if (!match) return null;
54
+
55
+ let target = String(match[1] || '').trim();
56
+ if (!target) return null;
57
+
58
+ if (
59
+ (target.startsWith('"') && target.endsWith('"'))
60
+ || (target.startsWith("'") && target.endsWith("'"))
61
+ ) {
62
+ target = target.slice(1, -1).trim();
63
+ }
64
+ if (!target) return null;
65
+ if (/[;&|]/.test(target)) return null;
66
+ return target;
67
+ }
68
+
69
+ export function resolveCdCommandPath(
70
+ targetPath,
71
+ currentRoot,
72
+ homeDir = process.env.USERPROFILE || process.env.HOME || process.cwd(),
73
+ platform = process.platform,
74
+ ) {
75
+ const pathApi = pathFor(platform);
76
+ let candidate = normalizeDriveLetterOnlyPath(targetPath);
77
+ if (!candidate) return null;
78
+
79
+ if (candidate === '~') {
80
+ candidate = homeDir;
81
+ } else if (candidate.startsWith('~/') || candidate.startsWith('~\\')) {
82
+ candidate = pathApi.join(homeDir, candidate.slice(2));
83
+ }
84
+
85
+ const baseRoot = pathApi.resolve(String(currentRoot || process.cwd()).trim() || process.cwd());
86
+ return pathApi.resolve(baseRoot, candidate);
87
+ }