@borgee/agents-host 0.2.35 → 0.2.56

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 (74) hide show
  1. package/README.md +23 -27
  2. package/dist/agents-host.d.ts +28 -5
  3. package/dist/agents-host.js +274 -236
  4. package/dist/chat/chat-control-plane.d.ts +3 -0
  5. package/dist/chat/sdk-chat-control-plane.d.ts +4 -3
  6. package/dist/chat/sdk-chat-control-plane.js +6 -0
  7. package/dist/cli.js +1 -5
  8. package/dist/compatibility-gates.d.ts +4 -0
  9. package/dist/compatibility-gates.js +18 -1
  10. package/dist/context/claude-file-brief.d.ts +2 -0
  11. package/dist/context/claude-file-brief.js +83 -0
  12. package/dist/context/compaction.d.ts +20 -0
  13. package/dist/context/compaction.js +59 -0
  14. package/dist/context/injection.d.ts +44 -9
  15. package/dist/context/injection.js +360 -53
  16. package/dist/context/main-session-delegation.d.ts +1 -1
  17. package/dist/context/projection-strategy.d.ts +24 -0
  18. package/dist/context/projection-strategy.js +90 -0
  19. package/dist/context/prompt.d.ts +16 -1
  20. package/dist/context/prompt.js +475 -53
  21. package/dist/context/resolved-workspace.d.ts +2 -0
  22. package/dist/context/resolved-workspace.js +64 -0
  23. package/dist/context/skill-manual.d.ts +14 -0
  24. package/dist/context/skill-manual.js +21 -0
  25. package/dist/context/turn-preparation.d.ts +8 -2
  26. package/dist/context/turn-preparation.js +68 -17
  27. package/dist/gateway/localhost-gateway.js +18 -8
  28. package/dist/hosted-turn-content.d.ts +15 -0
  29. package/dist/hosted-turn-content.js +50 -0
  30. package/dist/managed-daemon.d.ts +3 -2
  31. package/dist/managed-daemon.js +198 -37
  32. package/dist/plugin-sdk.js +276 -359
  33. package/dist/plugin-sdk.js.map +4 -4
  34. package/dist/progress-to-activity.d.ts +16 -0
  35. package/dist/progress-to-activity.js +24 -0
  36. package/dist/projection-strategy-values.d.ts +4 -0
  37. package/dist/projection-strategy-values.js +28 -0
  38. package/dist/providers/acp-progress-collector.d.ts +44 -0
  39. package/dist/providers/acp-progress-collector.js +130 -0
  40. package/dist/providers/awaiting-user.d.ts +2 -3
  41. package/dist/providers/awaiting-user.js +5 -7
  42. package/dist/providers/claude/activity-metadata.d.ts +14 -0
  43. package/dist/providers/claude/activity-metadata.js +81 -0
  44. package/dist/providers/claude/adapter.d.ts +3 -1
  45. package/dist/providers/claude/adapter.js +10 -0
  46. package/dist/providers/claude/cli-client.d.ts +9 -1
  47. package/dist/providers/claude/cli-client.js +270 -126
  48. package/dist/providers/codex/adapter.d.ts +3 -1
  49. package/dist/providers/codex/adapter.js +10 -0
  50. package/dist/providers/codex/cli-client.d.ts +10 -2
  51. package/dist/providers/codex/cli-client.js +88 -104
  52. package/dist/providers/codex/project-doc.js +22 -37
  53. package/dist/providers/copilot/adapter.d.ts +3 -1
  54. package/dist/providers/copilot/adapter.js +10 -0
  55. package/dist/providers/copilot/cli-client.d.ts +9 -1
  56. package/dist/providers/copilot/cli-client.js +68 -85
  57. package/dist/providers/create-provider.d.ts +3 -1
  58. package/dist/providers/create-provider.js +36 -9
  59. package/dist/providers/provider-adapter.d.ts +35 -0
  60. package/dist/providers/provider-adapter.js +44 -1
  61. package/dist/state-paths.d.ts +10 -2
  62. package/dist/state-paths.js +25 -6
  63. package/dist/task-thread-resolution.d.ts +3 -2
  64. package/dist/types.d.ts +163 -8
  65. package/package.json +2 -2
  66. package/skills/borgee-agent/SKILL.md +127 -38
  67. package/skills/borgee-agent/references/errors.md +38 -0
  68. package/skills/borgee-agent/references/task-properties.md +33 -0
  69. package/skills/borgee-agent/scripts/borgee-agent.mjs +553 -0
  70. package/skills/borgee-agent/scripts/borgee-agent.py +547 -0
  71. package/dist/durable-cursor-store.d.ts +0 -5
  72. package/dist/durable-cursor-store.js +0 -7
  73. package/skills/borgee-agent/borgee-agent.mjs +0 -562
  74. package/skills/borgee-agent/borgee-agent.py +0 -469
@@ -28,6 +28,11 @@ interface CodexAcpRuntime {
28
28
  mode?: number;
29
29
  }): Promise<void>;
30
30
  unlink(path: string): Promise<void>;
31
+ fetch(input: string | URL, init?: RequestInit): Promise<Response>;
32
+ }
33
+ interface CodexHostedImageInputConfig {
34
+ borgeeBaseUrl?: string;
35
+ agentApiKey?: string;
31
36
  }
32
37
  export declare class CodexCliClient {
33
38
  private readonly command;
@@ -35,6 +40,7 @@ export declare class CodexCliClient {
35
40
  private readonly sessionStore?;
36
41
  private readonly resolveSessionStoreAgentId;
37
42
  private readonly logger;
43
+ private readonly imageInputConfig;
38
44
  private readonly runtime;
39
45
  private readonly channels;
40
46
  /**
@@ -70,7 +76,7 @@ export declare class CodexCliClient {
70
76
  private sessionStoreWriteQueue;
71
77
  private sessionCapabilities;
72
78
  private readonly idleBackendShutdown;
73
- constructor(command: string, args?: string[], runtimeOverrides?: Partial<CodexAcpRuntime>, sessionStore?: CodexChannelSessionStore | undefined, resolveSessionStoreAgentId?: () => string | undefined, logger?: DebugLogger);
79
+ constructor(command: string, args?: string[], runtimeOverrides?: Partial<CodexAcpRuntime>, sessionStore?: CodexChannelSessionStore | undefined, resolveSessionStoreAgentId?: () => string | undefined, logger?: DebugLogger, imageInputConfig?: CodexHostedImageInputConfig);
74
80
  generateReply(turn: PreparedProviderTurnInput, options?: ProviderGenerateOptions): Promise<string>;
75
81
  generateReply(channelId: string, prompt: string, options?: ProviderGenerateOptions): Promise<string>;
76
82
  dispose(): Promise<void>;
@@ -90,9 +96,11 @@ export declare class CodexCliClient {
90
96
  private resolveProjectedContextDirectory;
91
97
  private buildProjectedPromptContext;
92
98
  private copyProjectedFile;
93
- private pruneProjectedGatewayAuthFiles;
99
+ private pruneProjectedGatewayCredentials;
94
100
  private refreshProjectedPromptContextBestEffort;
95
101
  private rewritePromptContextPaths;
102
+ private buildPromptInput;
103
+ private fetchImageBlock;
96
104
  private runTurn;
97
105
  private raceWithFatal;
98
106
  private invalidateSession;
@@ -5,7 +5,9 @@ import { createRequire } from 'node:module';
5
5
  import spawn from 'cross-spawn';
6
6
  import { PROTOCOL_VERSION, client, methods, ndJsonStream, } from '@agentclientprotocol/sdk';
7
7
  import { HostLogger, summarizeChildStderr, summarizeError } from '../../debug.js';
8
+ import { AcpProgressCollector } from '../acp-progress-collector.js';
8
9
  import { assertCodexProjectDocumentSize, buildCodexProjectDocument } from './project-doc.js';
10
+ import { isGatewayCredentialSidecarBasename } from '../../context/injection.js';
9
11
  import { IDLE_BACKEND_SHUTDOWN_DISABLED_MS, IdleBackendShutdownScheduler, } from '../idle-backend-shutdown.js';
10
12
  const SESSION_TAINTED_ERRORS = new WeakSet();
11
13
  const DEFAULT_IDLE_SESSION_TTL_MS = 2 * 24 * 60 * 60 * 1000;
@@ -27,8 +29,6 @@ const DEFAULT_SESSION_CAPABILITIES = {
27
29
  const DEFAULT_CODEX_COMMAND = 'codex-acp';
28
30
  const DEFAULT_CODEX_ARGS = [];
29
31
  const CODEX_CONTEXT_ROOT_DIRNAME = 'codex-context';
30
- const PROJECTED_GATEWAY_AUTH_FILENAME = '.localhost-gateway-auth.json';
31
- const PROJECTED_GATEWAY_AUTH_PREFIX = '.localhost-gateway-auth.';
32
32
  const PROJECTION_UNAVAILABLE_PLACEHOLDER = '[codex-projection-unavailable]';
33
33
  const QUEUED_TURN_DROPPED_MESSAGE = 'Codex ACP session was reset after a failed turn; queued turns were dropped instead of replaying them on a fresh session';
34
34
  const require = createRequire(import.meta.url);
@@ -59,10 +59,8 @@ const DEFAULT_RUNTIME = {
59
59
  unlink: async (path) => {
60
60
  await fs.unlink(path);
61
61
  },
62
+ fetch: async (input, init) => fetch(input, init),
62
63
  };
63
- function hasVisibleText(value) {
64
- return typeof value === 'string' && value.trim().length > 0;
65
- }
66
64
  function cloneDirectories(directories) {
67
65
  return directories ? [...directories] : undefined;
68
66
  }
@@ -103,85 +101,7 @@ function serializePersistedSessionRecord(record) {
103
101
  ...(record.cwd ? { cwd: record.cwd } : {}),
104
102
  });
105
103
  }
106
- function formatToolProgress(title, status) {
107
- const normalizedTitle = hasVisibleText(title) ? title.trim() : null;
108
- switch (status) {
109
- case 'completed':
110
- return normalizedTitle ? `Completed ${normalizedTitle}` : 'Completed tool call';
111
- case 'failed':
112
- return normalizedTitle ? `Failed ${normalizedTitle}` : 'Tool call failed';
113
- case 'pending':
114
- case 'in_progress':
115
- case undefined:
116
- case null:
117
- return normalizedTitle ? `Running ${normalizedTitle}…` : 'Running tool…';
118
- default:
119
- return normalizedTitle ? `${status} ${normalizedTitle}` : status;
120
- }
121
- }
122
- function formatPlanProgress(entries) {
123
- const current = entries.find((entry) => entry.status === 'in_progress') ??
124
- entries.find((entry) => entry.status === 'pending') ??
125
- entries[0];
126
- return hasVisibleText(current?.content) ? `Plan: ${current.content.trim()}` : null;
127
- }
128
- class CodexProgressCollector {
129
- onProgress;
130
- publicText = '';
131
- lastPublished = null;
132
- toolTitles = new Map();
133
- constructor(onProgress) {
134
- this.onProgress = onProgress;
135
- }
136
- consume(update) {
137
- switch (update.update.sessionUpdate) {
138
- case 'agent_message_chunk':
139
- if (update.update.content.type !== 'text') {
140
- return;
141
- }
142
- this.publicText += update.update.content.text;
143
- this.publish(this.publicText);
144
- return;
145
- case 'tool_call':
146
- this.toolTitles.set(update.update.toolCallId, update.update.title);
147
- this.publishFallback(formatToolProgress(update.update.title, update.update.status));
148
- return;
149
- case 'tool_call_update': {
150
- const nextTitle = update.update.title ?? this.toolTitles.get(update.update.toolCallId);
151
- if (hasVisibleText(nextTitle)) {
152
- this.toolTitles.set(update.update.toolCallId, nextTitle);
153
- }
154
- this.publishFallback(formatToolProgress(nextTitle, update.update.status));
155
- return;
156
- }
157
- case 'plan':
158
- this.publishFallback(formatPlanProgress(update.update.entries.map((entry) => ({
159
- content: entry.content,
160
- status: entry.status,
161
- }))));
162
- return;
163
- default:
164
- return;
165
- }
166
- }
167
- getFinalText() {
168
- return this.publicText.trim();
169
- }
170
- publishFallback(text) {
171
- if (hasVisibleText(this.publicText)) {
172
- return;
173
- }
174
- this.publish(text);
175
- }
176
- publish(text) {
177
- if (!this.onProgress || !hasVisibleText(text) || text === this.lastPublished) {
178
- return;
179
- }
180
- this.lastPublished = text;
181
- this.onProgress({ text });
182
- }
183
- }
184
- function createDeferredTurn(channelId, prompt, promptContext, sessionPersistence, options) {
104
+ function createDeferredTurn(channelId, preparedTurn, sessionPersistence, options) {
185
105
  let settled = false;
186
106
  let resolvePromise;
187
107
  let rejectPromise;
@@ -191,8 +111,7 @@ function createDeferredTurn(channelId, prompt, promptContext, sessionPersistence
191
111
  });
192
112
  return {
193
113
  channelId,
194
- prompt,
195
- promptContext,
114
+ preparedTurn,
196
115
  sessionPersistence,
197
116
  options,
198
117
  promise,
@@ -210,6 +129,28 @@ function createDeferredTurn(channelId, prompt, promptContext, sessionPersistence
210
129
  },
211
130
  };
212
131
  }
132
+ function isAbsoluteHttpUrl(value) {
133
+ return value.startsWith('http://') || value.startsWith('https://');
134
+ }
135
+ function resolveHostedAttachmentUrl(rawUrl, borgeeBaseUrl) {
136
+ if (isAbsoluteHttpUrl(rawUrl)) {
137
+ return rawUrl;
138
+ }
139
+ const base = borgeeBaseUrl.replace(/\/$/, '');
140
+ const path = rawUrl.startsWith('/') ? rawUrl : `/${rawUrl}`;
141
+ return `${base}${path}`;
142
+ }
143
+ function isSameOrigin(left, right) {
144
+ try {
145
+ return new URL(left).origin === new URL(right).origin;
146
+ }
147
+ catch {
148
+ return false;
149
+ }
150
+ }
151
+ function asImagePart(part) {
152
+ return part.type === 'image' ? part : null;
153
+ }
213
154
  function normalizeError(error) {
214
155
  return error instanceof Error ? error : new Error(String(error));
215
156
  }
@@ -296,6 +237,7 @@ export class CodexCliClient {
296
237
  sessionStore;
297
238
  resolveSessionStoreAgentId;
298
239
  logger;
240
+ imageInputConfig;
299
241
  runtime;
300
242
  channels = new Map();
301
243
  /**
@@ -333,12 +275,13 @@ export class CodexCliClient {
333
275
  sessionStoreWriteQueue = Promise.resolve();
334
276
  sessionCapabilities = DEFAULT_SESSION_CAPABILITIES;
335
277
  idleBackendShutdown;
336
- constructor(command, args = [], runtimeOverrides = {}, sessionStore, resolveSessionStoreAgentId = () => undefined, logger = new HostLogger()) {
278
+ constructor(command, args = [], runtimeOverrides = {}, sessionStore, resolveSessionStoreAgentId = () => undefined, logger = new HostLogger(), imageInputConfig = {}) {
337
279
  this.command = command;
338
280
  this.args = args;
339
281
  this.sessionStore = sessionStore;
340
282
  this.resolveSessionStoreAgentId = resolveSessionStoreAgentId;
341
283
  this.logger = logger;
284
+ this.imageInputConfig = imageInputConfig;
342
285
  this.runtime = { ...DEFAULT_RUNTIME, ...runtimeOverrides };
343
286
  this.idleBackendShutdown = new IdleBackendShutdownScheduler({
344
287
  idleShutdownMs: this.runtime.idleBackendShutdownMs,
@@ -359,12 +302,19 @@ export class CodexCliClient {
359
302
  if (this.fatalError) {
360
303
  throw this.fatalError;
361
304
  }
362
- const preparedTurn = typeof channelIdOrTurn === 'string'
305
+ const preparedTurnBase = typeof channelIdOrTurn === 'string'
363
306
  ? {
364
307
  channelId: channelIdOrTurn,
308
+ incomingContent: '',
309
+ incomingParts: [],
365
310
  prompt: typeof promptOrOptions === 'string' ? promptOrOptions : '',
366
311
  }
367
312
  : channelIdOrTurn;
313
+ const preparedTurn = {
314
+ ...preparedTurnBase,
315
+ incomingContent: preparedTurnBase.incomingContent ?? '',
316
+ incomingParts: preparedTurnBase.incomingParts ?? [],
317
+ };
368
318
  const options = typeof channelIdOrTurn === 'string'
369
319
  ? maybeOptions
370
320
  : promptOrOptions;
@@ -372,7 +322,7 @@ export class CodexCliClient {
372
322
  const state = this.getOrCreateChannelState(sessionRouting.key, preparedTurn.channelId, sessionRouting.persistence);
373
323
  this.clearIdleTimer(state);
374
324
  this.idleBackendShutdown.cancel();
375
- const turn = createDeferredTurn(preparedTurn.channelId, preparedTurn.prompt, preparedTurn.promptContext, sessionRouting.persistence, options);
325
+ const turn = createDeferredTurn(preparedTurn.channelId, preparedTurn, sessionRouting.persistence, options);
376
326
  state.queue.push(turn);
377
327
  this.processChannelQueue(sessionRouting.key, state);
378
328
  return turn.promise;
@@ -536,8 +486,8 @@ export class CodexCliClient {
536
486
  }
537
487
  state.activeTurn = turn;
538
488
  try {
539
- state.cwd = await this.resolveSessionCwd(turn.promptContext);
540
- const projectedPromptContext = await this.refreshProjectedPromptContextBestEffort(turn.channelId, turn.promptContext);
489
+ state.cwd = await this.resolveSessionCwd(turn.preparedTurn.promptContext);
490
+ const projectedPromptContext = await this.refreshProjectedPromptContextBestEffort(turn.channelId, turn.preparedTurn.promptContext);
541
491
  state.additionalDirectories = this.resolveSessionAdditionalDirectories(projectedPromptContext);
542
492
  state.visibilityKey = this.resolveSessionVisibilityKey(projectedPromptContext);
543
493
  await this.ensureStarted();
@@ -545,7 +495,8 @@ export class CodexCliClient {
545
495
  const session = await this.getOrCreateSession(channelId, state);
546
496
  let reply;
547
497
  try {
548
- reply = await this.runTurn(session, this.rewritePromptContextPaths(turn.prompt, turn.promptContext, projectedPromptContext?.promptContext), turn.options);
498
+ const promptInput = await this.buildPromptInput(turn.preparedTurn, projectedPromptContext?.promptContext);
499
+ reply = await this.runTurn(session, promptInput, turn.options);
549
500
  }
550
501
  catch (error) {
551
502
  if (isSessionTainted(error)) {
@@ -703,7 +654,7 @@ export class CodexCliClient {
703
654
  }
704
655
  }
705
656
  async resolveSessionCwd(promptContext) {
706
- return promptContext?.taskWorkspace?.rootPath ?? this.runtime.cwd;
657
+ return promptContext?.resolvedWorkspace?.rootPath ?? this.runtime.cwd;
707
658
  }
708
659
  async recycleSessionIfInjectionScopeChanged(channelId, state) {
709
660
  const session = state.session;
@@ -740,7 +691,7 @@ export class CodexCliClient {
740
691
  return JSON.stringify({
741
692
  directoryPath: projectedPromptContext.directoryPath,
742
693
  channelContextPayloadPath: projectedPromptContext.promptContext.channelContextPayloadPath,
743
- gatewayAuthPath: projectedPromptContext.promptContext.gatewayAuthPath ?? null,
694
+ gatewayCredentialPath: projectedPromptContext.promptContext.gatewayCredentialPath ?? null,
744
695
  });
745
696
  }
746
697
  resolveProjectedContextDirectory(payloadPath) {
@@ -755,8 +706,8 @@ export class CodexCliClient {
755
706
  return {
756
707
  ...promptContext,
757
708
  channelContextPayloadPath: join(projectedDirectoryPath, basename(payloadPath)),
758
- gatewayAuthPath: promptContext.gatewayAuthPath
759
- ? join(projectedDirectoryPath, basename(promptContext.gatewayAuthPath))
709
+ gatewayCredentialPath: promptContext.gatewayCredentialPath
710
+ ? join(projectedDirectoryPath, basename(promptContext.gatewayCredentialPath))
760
711
  : undefined,
761
712
  };
762
713
  }
@@ -767,12 +718,11 @@ export class CodexCliClient {
767
718
  mode: 0o600,
768
719
  });
769
720
  }
770
- async pruneProjectedGatewayAuthFiles(directoryPath, keepPath) {
721
+ async pruneProjectedGatewayCredentials(directoryPath, keepPath) {
771
722
  const keepFilename = keepPath ? basename(keepPath) : null;
772
723
  const entries = await this.runtime.readdir(directoryPath);
773
724
  await Promise.all(entries
774
- .filter((entry) => entry === PROJECTED_GATEWAY_AUTH_FILENAME
775
- || (entry.startsWith(PROJECTED_GATEWAY_AUTH_PREFIX) && entry.endsWith('.json')))
725
+ .filter((entry) => isGatewayCredentialSidecarBasename(entry))
776
726
  .filter((entry) => entry !== keepFilename)
777
727
  .map(async (entry) => {
778
728
  await this.runtime.unlink(join(directoryPath, entry)).catch((error) => {
@@ -795,10 +745,10 @@ export class CodexCliClient {
795
745
  }
796
746
  try {
797
747
  await this.runtime.mkdir(projectedDirectoryPath, { recursive: true, mode: 0o700 });
798
- await this.pruneProjectedGatewayAuthFiles(projectedDirectoryPath, projectedPromptContext.gatewayAuthPath);
748
+ await this.pruneProjectedGatewayCredentials(projectedDirectoryPath, projectedPromptContext.gatewayCredentialPath);
799
749
  await this.copyProjectedFile(payloadPath, projectedPromptContext.channelContextPayloadPath);
800
- if (resolvedPromptContext.gatewayAuthPath && projectedPromptContext.gatewayAuthPath) {
801
- await this.copyProjectedFile(resolvedPromptContext.gatewayAuthPath, projectedPromptContext.gatewayAuthPath);
750
+ if (resolvedPromptContext.gatewayCredentialPath && projectedPromptContext.gatewayCredentialPath) {
751
+ await this.copyProjectedFile(resolvedPromptContext.gatewayCredentialPath, projectedPromptContext.gatewayCredentialPath);
802
752
  }
803
753
  const content = buildCodexProjectDocument(projectedPromptContext);
804
754
  assertCodexProjectDocumentSize(content);
@@ -827,7 +777,7 @@ export class CodexCliClient {
827
777
  let rewritten = prompt;
828
778
  const replacements = [
829
779
  [sourceContext.channelContextPayloadPath, targetContext?.channelContextPayloadPath],
830
- [sourceContext.gatewayAuthPath, targetContext?.gatewayAuthPath],
780
+ [sourceContext.gatewayCredentialPath, targetContext?.gatewayCredentialPath],
831
781
  ];
832
782
  for (const [sourcePath, targetPath] of replacements) {
833
783
  if (!sourcePath || sourcePath === targetPath) {
@@ -837,12 +787,46 @@ export class CodexCliClient {
837
787
  }
838
788
  return rewritten;
839
789
  }
790
+ async buildPromptInput(turn, projectedPromptContext) {
791
+ const prompt = this.rewritePromptContextPaths(turn.prompt, turn.promptContext, projectedPromptContext);
792
+ const imageParts = turn.incomingParts.map(asImagePart).filter((part) => part !== null);
793
+ if (imageParts.length === 0) {
794
+ return prompt;
795
+ }
796
+ const imageBlocks = await Promise.all(imageParts.map(async (part) => this.fetchImageBlock(part.attachment.url, part.attachment.contentType)));
797
+ return [
798
+ { type: 'text', text: prompt },
799
+ ...imageBlocks,
800
+ ];
801
+ }
802
+ async fetchImageBlock(rawUrl, contentType) {
803
+ const baseUrl = this.imageInputConfig.borgeeBaseUrl?.trim();
804
+ const agentApiKey = this.imageInputConfig.agentApiKey?.trim();
805
+ if (!baseUrl || !agentApiKey) {
806
+ throw new Error('Codex image input requires Borgee base URL and agent API key');
807
+ }
808
+ const resolvedUrl = resolveHostedAttachmentUrl(rawUrl, baseUrl);
809
+ if (!isSameOrigin(resolvedUrl, baseUrl)) {
810
+ throw new Error(`Codex image input only supports Borgee-origin attachments: ${resolvedUrl}`);
811
+ }
812
+ const init = { headers: { Authorization: `Bearer ${agentApiKey}` } };
813
+ const response = await this.runtime.fetch(resolvedUrl, init);
814
+ if (!response.ok) {
815
+ throw new Error(`Codex image fetch failed with ${response.status} ${response.statusText}`.trim());
816
+ }
817
+ const blob = Buffer.from(await response.arrayBuffer()).toString('base64');
818
+ return {
819
+ type: 'image',
820
+ mimeType: contentType,
821
+ data: blob,
822
+ };
823
+ }
840
824
  async runTurn(session, prompt, options) {
841
825
  const promptPromise = this.raceWithFatal(session.prompt(prompt));
842
826
  const promptFailure = new Promise((_, reject) => {
843
827
  void promptPromise.catch((error) => reject(markSessionTainted(error)));
844
828
  });
845
- const collector = new CodexProgressCollector(options?.onProgress);
829
+ const collector = new AcpProgressCollector(options?.onProgress);
846
830
  for (;;) {
847
831
  let update;
848
832
  try {
@@ -1,59 +1,44 @@
1
- import { basename } from 'node:path';
2
1
  import { buildAttentionSummaryLines } from '../../context/attention.js';
3
2
  import { buildCollaborationCapabilityDeclarationSummaryLines, buildMissedCollaborationDiagnosticSummaryLines, } from '../../context/collaboration-capabilities-diagnostics.js';
4
3
  import { buildCollaborationOutcomeSummaryLines } from '../../context/collaboration-outcome.js';
5
4
  import { MAIN_SESSION_DELEGATION_LINES } from '../../context/main-session-delegation.js';
5
+ import { buildResolvedWorkspaceGuidanceLines } from '../../context/resolved-workspace.js';
6
+ import { buildSkillManualLines, buildSkillManualReadLine } from '../../context/skill-manual.js';
6
7
  import { buildTaskThreadCollaborationSummaryLines } from '../../context/task-thread-collaboration.js';
7
8
  const PROJECT_DOC_MAX_BYTES = 32 * 1024;
9
+ // Every command the CLI carries needs the gateway credential file, so the capability is only
10
+ // worth naming in a workspace that also names one; otherwise the model is handed an invocation
11
+ // template it cannot fill.
8
12
  function buildSkillRuntimeLines(context) {
9
- if (!context?.skillRuntime || !context.channelContextPayloadPath) {
13
+ if (!context?.skillRuntime) {
10
14
  return [];
11
15
  }
12
- return [
13
- 'Read-only local skill runtime bootstrap is available in this workspace.',
14
- `Context payload: ${context.channelContextPayloadPath}`,
15
- `Skill guide: ${context.skillRuntime.skillMarkdownPath}`,
16
- `Node CLI: node ${context.skillRuntime.nodeCliPath} --context ${context.channelContextPayloadPath} --print-bootstrap`,
17
- `Python CLI: python3 ${context.skillRuntime.pythonCliPath} --context ${context.channelContextPayloadPath} --print-bootstrap`,
18
- 'These CLIs are local-only, may only access the loopback gateway surfaced by the host, and must not mutate files or the local environment.',
19
- ];
16
+ return context.gatewayCredentialPath
17
+ ? buildSkillManualLines(context.skillRuntime)
18
+ : [buildSkillManualReadLine(context.skillRuntime)];
20
19
  }
21
- function buildGatewayLines(context) {
22
- if (!context?.localhostGateway || !context.channelContextPayloadPath) {
20
+ // The credential file is the whole handoff: it carries the gateway's base URL along with the
21
+ // channel id and the token, and the CLI reads all three out of it. Naming the base URL separately
22
+ // would only invite a hand-rolled request that carries the token off the CLI's one path.
23
+ function buildGatewayCredentialLines(context) {
24
+ if (!context?.localhostGateway || !context.gatewayCredentialPath) {
23
25
  return [];
24
26
  }
25
- const lines = [
26
- 'A loopback-only localhost gateway may be available for this channel.',
27
- `Gateway base URL: ${context.localhostGateway.baseUrl}`,
28
- 'Use the packaged local CLI with the context payload above for gateway access.',
29
- ];
30
- if (context.gatewayAuthPath) {
31
- lines.push(`Per-turn gateway auth sidecar: ${context.gatewayAuthPath}`, 'If a later turn prompt advertises a different auth sidecar path, prefer the prompt-visible path for that turn.');
32
- }
33
- return lines;
27
+ return [`Gateway credential file: ${context.gatewayCredentialPath}`];
34
28
  }
35
29
  export function buildCodexProjectDocument(context) {
30
+ const sessionDelegationLines = context?.skillRuntime ? [] : MAIN_SESSION_DELEGATION_LINES;
36
31
  const lines = [
37
32
  '# Borgee channel workspace',
38
33
  '',
39
34
  'This workspace belongs to one Borgee channel session hosted by agents-host.',
40
35
  'Keep visible replies concise, do not claim actions you did not perform, and prefer the per-turn prompt when it provides newer turn-local details.',
41
36
  '',
42
- ...MAIN_SESSION_DELEGATION_LINES,
43
- ...(context?.channelContextPayloadPath
44
- ? [
45
- '',
46
- `Context payload file: ${context.channelContextPayloadPath}`,
47
- `Workspace anchor: ${basename(context.channelContextPayloadPath)}`,
48
- ]
49
- : []),
50
- ...(context?.taskWorkspace
51
- ? [
52
- '',
53
- `Writable task workspace root: ${context.taskWorkspace.rootPath}`,
54
- 'That writable task workspace is local to agents-host for the current task thread and does not imply that the target repository has already been checked out there.',
55
- ]
56
- : []),
37
+ ...sessionDelegationLines,
38
+ ...(() => {
39
+ const workspaceLines = buildResolvedWorkspaceGuidanceLines(context);
40
+ return workspaceLines.length > 0 ? ['', ...workspaceLines] : [];
41
+ })(),
57
42
  ...(() => {
58
43
  const collaborationOutcomeLines = buildCollaborationOutcomeSummaryLines(context?.collaborationOutcome);
59
44
  return collaborationOutcomeLines.length > 0 ? ['', ...collaborationOutcomeLines] : [];
@@ -79,7 +64,7 @@ export function buildCodexProjectDocument(context) {
79
64
  return skillRuntimeLines.length > 0 ? ['', ...skillRuntimeLines] : [];
80
65
  })(),
81
66
  ...(() => {
82
- const gatewayLines = buildGatewayLines(context);
67
+ const gatewayLines = buildGatewayCredentialLines(context);
83
68
  return gatewayLines.length > 0 ? ['', ...gatewayLines] : [];
84
69
  })(),
85
70
  ];
@@ -1,10 +1,12 @@
1
- import type { ProviderAdapter } from '../provider-adapter.js';
1
+ import { type ProviderAdapter } from '../provider-adapter.js';
2
2
  import type { ProviderGenerateOptions, ProviderInput, ProviderReply } from '../../types.js';
3
3
  import { ProviderTurnPreparer } from '../../context/turn-preparation.js';
4
4
  import { CopilotCliClient } from './cli-client.js';
5
+ export declare const COPILOT_HOSTED_PROVIDER_CAPABILITIES: import("../provider-adapter.js").ProviderCapabilities;
5
6
  export declare class CopilotProviderAdapter implements ProviderAdapter {
6
7
  private readonly cli;
7
8
  private readonly turnPreparer;
9
+ readonly capabilities: import("../provider-adapter.js").ProviderCapabilities;
8
10
  constructor(cli: CopilotCliClient, turnPreparer: ProviderTurnPreparer);
9
11
  generateReply(input: ProviderInput, options?: ProviderGenerateOptions): Promise<ProviderReply>;
10
12
  dispose(): Promise<void>;
@@ -1,7 +1,17 @@
1
+ import { createHostedProviderCapabilities } from '../provider-adapter.js';
1
2
  import { createAwaitingUserProgressHandler, parseProviderReply } from '../awaiting-user.js';
3
+ export const COPILOT_HOSTED_PROVIDER_CAPABILITIES = createHostedProviderCapabilities({
4
+ imageInputTransport: {
5
+ source: 'transport-derived',
6
+ support: 'supported',
7
+ reason: 'real-media-delivered',
8
+ delivery: ['blob'],
9
+ },
10
+ });
2
11
  export class CopilotProviderAdapter {
3
12
  cli;
4
13
  turnPreparer;
14
+ capabilities = COPILOT_HOSTED_PROVIDER_CAPABILITIES;
5
15
  constructor(cli, turnPreparer) {
6
16
  this.cli = cli;
7
17
  this.turnPreparer = turnPreparer;
@@ -17,12 +17,17 @@ interface CopilotAcpRuntime {
17
17
  idleShutdownGracePeriodMs: number;
18
18
  shutdownGracePeriodMs: number;
19
19
  shutdownForceKillWaitMs: number;
20
+ fetch(input: string | URL, init?: RequestInit): Promise<Response>;
20
21
  }
21
22
  interface CopilotPermissionPolicyOptions {
22
23
  gateEnabled?: boolean;
23
24
  policyMode?: InternalPolicyMode;
24
25
  auditSink?: AuthorizationAuditSinkLike;
25
26
  }
27
+ interface CopilotHostedImageInputConfig {
28
+ borgeeBaseUrl?: string;
29
+ agentApiKey?: string;
30
+ }
26
31
  /**
27
32
  * Persistent ACP-backed client for the GitHub Copilot CLI (`copilot --acp`).
28
33
  *
@@ -37,6 +42,7 @@ export declare class CopilotCliClient {
37
42
  private readonly resolveSessionStoreAgentId;
38
43
  private readonly logger;
39
44
  private readonly permissionPolicy;
45
+ private readonly imageInputConfig;
40
46
  private readonly runtime;
41
47
  private readonly channels;
42
48
  /**
@@ -72,7 +78,7 @@ export declare class CopilotCliClient {
72
78
  private sessionStoreWriteQueue;
73
79
  private sessionCapabilities;
74
80
  private readonly idleBackendShutdown;
75
- constructor(command: string, _ignoredArgs?: string[], runtimeOverrides?: Partial<CopilotAcpRuntime>, sessionStore?: CopilotChannelSessionStore | undefined, resolveSessionStoreAgentId?: () => string | undefined, logger?: DebugLogger, permissionPolicy?: CopilotPermissionPolicyOptions);
81
+ constructor(command: string, _ignoredArgs?: string[], runtimeOverrides?: Partial<CopilotAcpRuntime>, sessionStore?: CopilotChannelSessionStore | undefined, resolveSessionStoreAgentId?: () => string | undefined, logger?: DebugLogger, permissionPolicy?: CopilotPermissionPolicyOptions, imageInputConfig?: CopilotHostedImageInputConfig);
76
82
  generateReply(turn: PreparedProviderTurnInput, options?: ProviderGenerateOptions): Promise<string>;
77
83
  generateReply(channelId: string, prompt: string, options?: ProviderGenerateOptions): Promise<string>;
78
84
  dispose(): Promise<void>;
@@ -86,6 +92,8 @@ export declare class CopilotCliClient {
86
92
  private restoreSession;
87
93
  private clearBufferedSessionReplay;
88
94
  private resolveSessionCwd;
95
+ private buildPromptInput;
96
+ private fetchImageBlock;
89
97
  private recycleSessionIfCwdChanged;
90
98
  private runTurn;
91
99
  private raceWithFatal;