@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.
- package/README.md +23 -27
- package/dist/agents-host.d.ts +28 -5
- package/dist/agents-host.js +274 -236
- package/dist/chat/chat-control-plane.d.ts +3 -0
- package/dist/chat/sdk-chat-control-plane.d.ts +4 -3
- package/dist/chat/sdk-chat-control-plane.js +6 -0
- package/dist/cli.js +1 -5
- package/dist/compatibility-gates.d.ts +4 -0
- package/dist/compatibility-gates.js +18 -1
- package/dist/context/claude-file-brief.d.ts +2 -0
- package/dist/context/claude-file-brief.js +83 -0
- package/dist/context/compaction.d.ts +20 -0
- package/dist/context/compaction.js +59 -0
- package/dist/context/injection.d.ts +44 -9
- package/dist/context/injection.js +360 -53
- package/dist/context/main-session-delegation.d.ts +1 -1
- package/dist/context/projection-strategy.d.ts +24 -0
- package/dist/context/projection-strategy.js +90 -0
- package/dist/context/prompt.d.ts +16 -1
- package/dist/context/prompt.js +475 -53
- package/dist/context/resolved-workspace.d.ts +2 -0
- package/dist/context/resolved-workspace.js +64 -0
- package/dist/context/skill-manual.d.ts +14 -0
- package/dist/context/skill-manual.js +21 -0
- package/dist/context/turn-preparation.d.ts +8 -2
- package/dist/context/turn-preparation.js +68 -17
- package/dist/gateway/localhost-gateway.js +18 -8
- package/dist/hosted-turn-content.d.ts +15 -0
- package/dist/hosted-turn-content.js +50 -0
- package/dist/managed-daemon.d.ts +3 -2
- package/dist/managed-daemon.js +198 -37
- package/dist/plugin-sdk.js +276 -359
- package/dist/plugin-sdk.js.map +4 -4
- package/dist/progress-to-activity.d.ts +16 -0
- package/dist/progress-to-activity.js +24 -0
- package/dist/projection-strategy-values.d.ts +4 -0
- package/dist/projection-strategy-values.js +28 -0
- package/dist/providers/acp-progress-collector.d.ts +44 -0
- package/dist/providers/acp-progress-collector.js +130 -0
- package/dist/providers/awaiting-user.d.ts +2 -3
- package/dist/providers/awaiting-user.js +5 -7
- package/dist/providers/claude/activity-metadata.d.ts +14 -0
- package/dist/providers/claude/activity-metadata.js +81 -0
- package/dist/providers/claude/adapter.d.ts +3 -1
- package/dist/providers/claude/adapter.js +10 -0
- package/dist/providers/claude/cli-client.d.ts +9 -1
- package/dist/providers/claude/cli-client.js +270 -126
- package/dist/providers/codex/adapter.d.ts +3 -1
- package/dist/providers/codex/adapter.js +10 -0
- package/dist/providers/codex/cli-client.d.ts +10 -2
- package/dist/providers/codex/cli-client.js +88 -104
- package/dist/providers/codex/project-doc.js +22 -37
- package/dist/providers/copilot/adapter.d.ts +3 -1
- package/dist/providers/copilot/adapter.js +10 -0
- package/dist/providers/copilot/cli-client.d.ts +9 -1
- package/dist/providers/copilot/cli-client.js +68 -85
- package/dist/providers/create-provider.d.ts +3 -1
- package/dist/providers/create-provider.js +36 -9
- package/dist/providers/provider-adapter.d.ts +35 -0
- package/dist/providers/provider-adapter.js +44 -1
- package/dist/state-paths.d.ts +10 -2
- package/dist/state-paths.js +25 -6
- package/dist/task-thread-resolution.d.ts +3 -2
- package/dist/types.d.ts +163 -8
- package/package.json +2 -2
- package/skills/borgee-agent/SKILL.md +127 -38
- package/skills/borgee-agent/references/errors.md +38 -0
- package/skills/borgee-agent/references/task-properties.md +33 -0
- package/skills/borgee-agent/scripts/borgee-agent.mjs +553 -0
- package/skills/borgee-agent/scripts/borgee-agent.py +547 -0
- package/dist/durable-cursor-store.d.ts +0 -5
- package/dist/durable-cursor-store.js +0 -7
- package/skills/borgee-agent/borgee-agent.mjs +0 -562
- 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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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?.
|
|
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
|
-
|
|
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
|
-
|
|
759
|
-
? join(projectedDirectoryPath, basename(promptContext.
|
|
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
|
|
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
|
|
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.
|
|
748
|
+
await this.pruneProjectedGatewayCredentials(projectedDirectoryPath, projectedPromptContext.gatewayCredentialPath);
|
|
799
749
|
await this.copyProjectedFile(payloadPath, projectedPromptContext.channelContextPayloadPath);
|
|
800
|
-
if (resolvedPromptContext.
|
|
801
|
-
await this.copyProjectedFile(resolvedPromptContext.
|
|
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.
|
|
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
|
|
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
|
|
13
|
+
if (!context?.skillRuntime) {
|
|
10
14
|
return [];
|
|
11
15
|
}
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
43
|
-
...(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 =
|
|
67
|
+
const gatewayLines = buildGatewayCredentialLines(context);
|
|
83
68
|
return gatewayLines.length > 0 ? ['', ...gatewayLines] : [];
|
|
84
69
|
})(),
|
|
85
70
|
];
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type
|
|
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;
|