Skip to content

Commit 6843779

Browse files
committed
feat: extract workspace path from OpenCode messages
- Add extractWorkspaceFromMessages() to parse workspace path from system messages sent by OpenCode (e.g., <supervisor> tags) - Update client pooling to be workspace-aware using model:workspace keys - Pass workspace root to Auggie SDK when creating clients - This fixes the issue where the wrapper was reporting home directory instead of the actual project workspace
1 parent ec5c3ba commit 6843779

File tree

1 file changed

+80
-34
lines changed

1 file changed

+80
-34
lines changed

src/server.ts

Lines changed: 80 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ interface ClientPool {
147147
}
148148

149149
interface AuggieSDK {
150-
create: (options: { model?: string; apiKey?: string; apiUrl?: string }) => Promise<AuggieClient>;
150+
create: (options: { model?: string; apiKey?: string; apiUrl?: string; workspaceRoot?: string }) => Promise<AuggieClient>;
151151
}
152152

153153
// Configuration
@@ -657,44 +657,56 @@ async function initAuggie(): Promise<void> {
657657
}
658658
}
659659

660-
async function createAuggieClient(auggieModel: string): Promise<AuggieClient> {
660+
async function createAuggieClient(auggieModel: string, workspaceRoot?: string): Promise<AuggieClient> {
661661
await initAuggie();
662662
if (!AuggieClass) {
663663
throw new Error('Auggie SDK not initialized');
664664
}
665665
const sess = await loadSession();
666-
debugLog('Creating Auggie Client', { model: auggieModel, apiUrl: sess.tenantURL });
666+
// Use provided workspace or fall back to home directory
667+
const workspace = workspaceRoot ?? os.homedir();
668+
debugLog('Creating Auggie Client', { model: auggieModel, apiUrl: sess.tenantURL, workspaceRoot: workspace });
667669
const client = await AuggieClass.create({
668670
model: auggieModel,
669671
apiKey: sess.accessToken,
670672
apiUrl: sess.tenantURL,
673+
workspaceRoot: workspace,
671674
});
672-
console.log(`New Auggie client created for model: ${auggieModel}`);
675+
console.log(`New Auggie client created for model: ${auggieModel} (workspace: ${workspace})`);
673676
return client;
674677
}
675678

676-
async function getAuggieClient(modelId: string): Promise<AuggieClient> {
679+
// Generate pool key combining model and workspace
680+
function getPoolKey(auggieModel: string, workspaceRoot?: string): string {
681+
const workspace = workspaceRoot ?? os.homedir();
682+
return `${auggieModel}:${workspace}`;
683+
}
684+
685+
async function getAuggieClient(modelId: string, workspaceRoot?: string): Promise<AuggieClient> {
677686
const modelConfig = MODEL_MAP[modelId] ?? MODEL_MAP[DEFAULT_MODEL];
678687
if (!modelConfig) {
679688
throw new Error(`Unknown model: ${modelId} and default model not configured`);
680689
}
681690
const auggieModel = modelConfig.auggie;
691+
const poolKey = getPoolKey(auggieModel, workspaceRoot);
682692
debugLog('getAuggieClient', {
683693
requestedModel: modelId,
684694
resolvedAuggieModel: auggieModel,
685695
usingDefault: !MODEL_MAP[modelId],
696+
workspaceRoot: workspaceRoot ?? os.homedir(),
697+
poolKey,
686698
});
687699

688-
clientPools[auggieModel] ??= { available: [], inUse: new Set(), creating: 0 };
700+
clientPools[poolKey] ??= { available: [], inUse: new Set(), creating: 0 };
689701

690-
const pool = clientPools[auggieModel];
702+
const pool = clientPools[poolKey];
691703

692704
if (pool.available.length > 0) {
693705
const client = pool.available.pop();
694706
if (client) {
695707
pool.inUse.add(client);
696708
console.log(
697-
`Reusing client for ${auggieModel} (available: ${String(pool.available.length)}, inUse: ${String(pool.inUse.size)})`
709+
`Reusing client for ${poolKey} (available: ${String(pool.available.length)}, inUse: ${String(pool.inUse.size)})`
698710
);
699711
return client;
700712
}
@@ -704,11 +716,11 @@ async function getAuggieClient(modelId: string): Promise<AuggieClient> {
704716
if (totalClients < POOL_SIZE) {
705717
pool.creating++;
706718
try {
707-
const client = await createAuggieClient(auggieModel);
719+
const client = await createAuggieClient(auggieModel, workspaceRoot);
708720
pool.creating--;
709721
pool.inUse.add(client);
710722
console.log(
711-
`Created new client for ${auggieModel} (available: ${String(pool.available.length)}, inUse: ${String(pool.inUse.size)})`
723+
`Created new client for ${poolKey} (available: ${String(pool.available.length)}, inUse: ${String(pool.inUse.size)})`
712724
);
713725
return client;
714726
} catch (err) {
@@ -717,37 +729,39 @@ async function getAuggieClient(modelId: string): Promise<AuggieClient> {
717729
}
718730
}
719731

720-
console.log(`Pool at capacity for ${auggieModel}, creating temporary client`);
721-
return await createAuggieClient(auggieModel);
732+
console.log(`Pool at capacity for ${poolKey}, creating temporary client`);
733+
return await createAuggieClient(auggieModel, workspaceRoot);
722734
}
723735

724-
function releaseAuggieClient(modelId: string, client: AuggieClient): void {
736+
function releaseAuggieClient(modelId: string, client: AuggieClient, workspaceRoot?: string): void {
725737
const modelConfig = MODEL_MAP[modelId] ?? MODEL_MAP[DEFAULT_MODEL];
726738
if (!modelConfig) return;
727739
const auggieModel = modelConfig.auggie;
728-
const pool = clientPools[auggieModel];
740+
const poolKey = getPoolKey(auggieModel, workspaceRoot);
741+
const pool = clientPools[poolKey];
729742
if (!pool) return;
730743

731744
if (pool.inUse.has(client)) {
732745
pool.inUse.delete(client);
733746
if (pool.available.length < POOL_SIZE) {
734747
pool.available.push(client);
735748
console.log(
736-
`Client returned to pool for ${auggieModel} (available: ${String(pool.available.length)}, inUse: ${String(pool.inUse.size)})`
749+
`Client returned to pool for ${poolKey} (available: ${String(pool.available.length)}, inUse: ${String(pool.inUse.size)})`
737750
);
738751
} else {
739752
void client.close();
740-
console.log(`Pool full, closed client for ${auggieModel}`);
753+
console.log(`Pool full, closed client for ${poolKey}`);
741754
}
742755
}
743756
}
744757

745758
// Discard a client without returning it to the pool (used when client has errors)
746-
function discardAuggieClient(modelId: string, client: AuggieClient, reason?: string): void {
759+
function discardAuggieClient(modelId: string, client: AuggieClient, reason?: string, workspaceRoot?: string): void {
747760
const modelConfig = MODEL_MAP[modelId] ?? MODEL_MAP[DEFAULT_MODEL];
748761
if (!modelConfig) return;
749762
const auggieModel = modelConfig.auggie;
750-
const pool = clientPools[auggieModel];
763+
const poolKey = getPoolKey(auggieModel, workspaceRoot);
764+
const pool = clientPools[poolKey];
751765
if (!pool) return;
752766

753767
if (pool.inUse.has(client)) {
@@ -962,6 +976,28 @@ function formatMessages(messages: ChatMessage[]): string {
962976
.join('\n\n');
963977
}
964978

979+
// Extract workspace root from messages - OpenCode sends this in system message
980+
function extractWorkspaceFromMessages(messages: ChatMessage[]): string | null {
981+
for (const msg of messages) {
982+
if (msg.role === 'system' && msg.content) {
983+
// Pattern 1: <supervisor>The user's workspace is opened at /path/to/workspace.</supervisor>
984+
const supervisorMatch = msg.content.match(
985+
/<supervisor>[^<]*?(?:workspace is opened at|workspace is)\s+[`"']?([^`"'<\n]+)[`"']?/i
986+
);
987+
if (supervisorMatch?.[1]) {
988+
return supervisorMatch[1].trim().replace(/\.$/, '');
989+
}
990+
991+
// Pattern 2: Workspace: /path/to/workspace
992+
const workspaceMatch = msg.content.match(/(?:workspace|working directory|cwd):\s*[`"']?([^\s`"'\n]+)/i);
993+
if (workspaceMatch?.[1]) {
994+
return workspaceMatch[1].trim();
995+
}
996+
}
997+
}
998+
return null;
999+
}
1000+
9651001
// Estimate token counts (rough approximation: ~4 chars per token)
9661002
function estimateTokens(text: string): number {
9671003
return Math.ceil(text.length / 4);
@@ -1255,12 +1291,13 @@ async function callAugmentAPIStreamingInternal(
12551291
res: ServerResponse,
12561292
requestId: string,
12571293
model: string,
1294+
workspaceRoot?: string,
12581295
abortSignal?: AbortSignal
12591296
): Promise<void> {
12601297
const startTime = Date.now();
1261-
console.log(`[${requestId}] 🚀 Starting streaming call to ${modelId} (prompt: ${String(prompt.length)} chars)`);
1298+
console.log(`[${requestId}] 🚀 Starting streaming call to ${modelId} (prompt: ${String(prompt.length)} chars, workspace: ${workspaceRoot ?? 'default'})`);
12621299
1263-
const client = await getAuggieClient(modelId);
1300+
const client = await getAuggieClient(modelId, workspaceRoot);
12641301
client.onSessionUpdate(createStreamCallback(res, model, requestId));
12651302
let hasError = false;
12661303
let caughtError: Error | null = null;
@@ -1301,15 +1338,15 @@ async function callAugmentAPIStreamingInternal(
13011338
// Discard client on session errors or aborts, otherwise return to pool
13021339
if (hasError && caughtError) {
13031340
if (caughtError.message === 'Request aborted') {
1304-
discardAuggieClient(modelId, client, 'request aborted/timeout');
1341+
discardAuggieClient(modelId, client, 'request aborted/timeout', workspaceRoot);
13051342
} else if (isSessionError(caughtError)) {
1306-
discardAuggieClient(modelId, client, 'session/connection error');
1343+
discardAuggieClient(modelId, client, 'session/connection error', workspaceRoot);
13071344
} else {
13081345
// Other errors - still return client to pool
1309-
releaseAuggieClient(modelId, client);
1346+
releaseAuggieClient(modelId, client, workspaceRoot);
13101347
}
13111348
} else {
1312-
releaseAuggieClient(modelId, client);
1349+
releaseAuggieClient(modelId, client, workspaceRoot);
13131350
}
13141351
}
13151352
if (caughtError) {
@@ -1323,10 +1360,11 @@ async function callAugmentAPIStreaming(
13231360
res: ServerResponse,
13241361
requestId: string,
13251362
model: string,
1363+
workspaceRoot?: string,
13261364
abortSignal?: AbortSignal
13271365
): Promise<void> {
13281366
await withRetry(
1329-
() => callAugmentAPIStreamingInternal(prompt, modelId, res, requestId, model, abortSignal),
1367+
() => callAugmentAPIStreamingInternal(prompt, modelId, res, requestId, model, workspaceRoot, abortSignal),
13301368
'Augment API Streaming',
13311369
requestId
13321370
);
@@ -1335,9 +1373,10 @@ async function callAugmentAPIStreaming(
13351373
async function callAugmentAPIInternal(
13361374
prompt: string,
13371375
modelId: string,
1376+
workspaceRoot?: string,
13381377
abortSignal?: AbortSignal
13391378
): Promise<string> {
1340-
const client = await getAuggieClient(modelId);
1379+
const client = await getAuggieClient(modelId, workspaceRoot);
13411380
let hasError = false;
13421381
let caughtError: Error | null = null;
13431382
let result = '';
@@ -1377,15 +1416,15 @@ async function callAugmentAPIInternal(
13771416
// Discard client on session errors or aborts, otherwise return to pool
13781417
if (hasError && caughtError) {
13791418
if (caughtError.message === 'Request aborted') {
1380-
discardAuggieClient(modelId, client, 'request aborted/timeout');
1419+
discardAuggieClient(modelId, client, 'request aborted/timeout', workspaceRoot);
13811420
} else if (isSessionError(caughtError)) {
1382-
discardAuggieClient(modelId, client, 'session/connection error');
1421+
discardAuggieClient(modelId, client, 'session/connection error', workspaceRoot);
13831422
} else {
13841423
// Other errors - still return client to pool
1385-
releaseAuggieClient(modelId, client);
1424+
releaseAuggieClient(modelId, client, workspaceRoot);
13861425
}
13871426
} else {
1388-
releaseAuggieClient(modelId, client);
1427+
releaseAuggieClient(modelId, client, workspaceRoot);
13891428
}
13901429
}
13911430
if (caughtError) {
@@ -1398,10 +1437,11 @@ async function callAugmentAPI(
13981437
prompt: string,
13991438
modelId: string,
14001439
requestId: string,
1440+
workspaceRoot?: string,
14011441
abortSignal?: AbortSignal
14021442
): Promise<string> {
14031443
return withRetry(
1404-
() => callAugmentAPIInternal(prompt, modelId, abortSignal),
1444+
() => callAugmentAPIInternal(prompt, modelId, workspaceRoot, abortSignal),
14051445
'Augment API',
14061446
requestId
14071447
);
@@ -1496,6 +1536,12 @@ async function handleChatCompletions(req: IncomingMessage, res: ServerResponse):
14961536
const stream = body.stream ?? false;
14971537
const model = body.model ?? DEFAULT_MODEL;
14981538
1539+
// Extract workspace root from messages (OpenCode sends this in system message)
1540+
const workspaceRoot = extractWorkspaceFromMessages(messages);
1541+
if (workspaceRoot) {
1542+
structuredLog('info', 'Request', `Extracted workspace: ${workspaceRoot}`, { requestId });
1543+
}
1544+
14991545
// Track model usage
15001546
metrics.requestsByModel[model] = (metrics.requestsByModel[model] ?? 0) + 1;
15011547
@@ -1511,7 +1557,7 @@ async function handleChatCompletions(req: IncomingMessage, res: ServerResponse):
15111557
const prompt = formatMessages(messages);
15121558
structuredLog('info', 'Request', `Processing request`, {
15131559
requestId,
1514-
data: { model, stream, messageCount: messages.length },
1560+
data: { model, stream, messageCount: messages.length, workspace: workspaceRoot ?? 'default' },
15151561
});
15161562
15171563
// Check for abort before making API call
@@ -1533,7 +1579,7 @@ async function handleChatCompletions(req: IncomingMessage, res: ServerResponse):
15331579
res.flushHeaders();
15341580
15351581
try {
1536-
await callAugmentAPIStreaming(prompt, model, res, requestId, model, abortController.signal);
1582+
await callAugmentAPIStreaming(prompt, model, res, requestId, model, workspaceRoot ?? undefined, abortController.signal);
15371583
res.write(createStreamChunk('', model, true));
15381584
res.write('data: [DONE]\n\n');
15391585
cleanup(true);
@@ -1547,7 +1593,7 @@ async function handleChatCompletions(req: IncomingMessage, res: ServerResponse):
15471593
}
15481594
res.end();
15491595
} else {
1550-
const response = await callAugmentAPI(prompt, model, requestId, abortController.signal);
1596+
const response = await callAugmentAPI(prompt, model, requestId, workspaceRoot ?? undefined, abortController.signal);
15511597
res.writeHead(200, { 'Content-Type': 'application/json' });
15521598
res.end(JSON.stringify(createChatResponse(response, model, prompt)));
15531599
cleanup(true);

0 commit comments

Comments
 (0)