opencode-qoder-bridge 0.1.10 → 0.1.11

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.
@@ -0,0 +1,641 @@
1
+ import { forkSession, listSessions } from "@qoder-ai/qoder-agent-sdk";
2
+ import { listModels } from "./models.js";
3
+ import { getLiveUsage, formatUsageReport } from "./usage.js";
4
+ import { summarize, formatCost } from "./cost.js";
5
+ import { clearAllSessions, deleteQoderSessionForCwd, getQoderSessionForCwd } from "./session-store.js";
6
+ import { describeError } from "./logger.js";
7
+ import { formatMcpStatuses, openSdkControlSession, withMcpControlTimeout } from "./sdk-control.js";
8
+ const CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/g;
9
+ const CONTROL_CHAR_TEST = /[\u0000-\u001f\u007f-\u009f]/;
10
+ const MAX_ARGUMENTS = 32_768;
11
+ const MCP_AUTH_TTL_MS = 10 * 60 * 1000;
12
+ export const QODER_COMMANDS = [
13
+ {
14
+ name: "qoder_usage",
15
+ title: "Qoder Usage",
16
+ description: "Show live Qoder quota and local cost/token totals.",
17
+ argumentHint: "",
18
+ },
19
+ {
20
+ name: "qoder_models",
21
+ title: "Qoder Models",
22
+ description: "List available Qoder models and capabilities.",
23
+ argumentHint: "",
24
+ },
25
+ {
26
+ name: "qoder_sessions",
27
+ title: "Qoder Sessions",
28
+ description: "List recent Qoder sessions.",
29
+ argumentHint: "optional: [directory] [limit]",
30
+ },
31
+ {
32
+ name: "qoder_session_reset",
33
+ title: "Reset Qoder Session",
34
+ description: "Reset a persisted Qoder session key, or all sessions.",
35
+ argumentHint: "optional: session key or all",
36
+ },
37
+ {
38
+ name: "qoder_session_fork",
39
+ title: "Fork Qoder Session",
40
+ description: "Fork a Qoder session without changing the active mapping.",
41
+ argumentHint: "optional: sessionId dir title upToMessageId",
42
+ },
43
+ {
44
+ name: "qoder_mcp_status",
45
+ title: "Qoder MCP Status",
46
+ description: "Inspect Qoder MCP connection and OAuth status.",
47
+ argumentHint: "",
48
+ },
49
+ {
50
+ name: "qoder_mcp_auth",
51
+ title: "Qoder MCP OAuth",
52
+ description: "Start or complete Qoder MCP OAuth.",
53
+ argumentHint: "server [callbackUrl] [redirectUri]",
54
+ },
55
+ {
56
+ name: "qoder_plan_mode",
57
+ title: "Qoder Plan Mode",
58
+ description: "Show Qoder Plan Mode status and configuration guidance.",
59
+ argumentHint: "",
60
+ },
61
+ ];
62
+ export function safeDisplay(value, fallback, maxLength = 512) {
63
+ if (typeof value !== "string" || !value)
64
+ return fallback;
65
+ const clean = value.replace(CONTROL_CHARS, " ").slice(0, maxLength);
66
+ return clean || fallback;
67
+ }
68
+ export async function closePendingMcpAuth(pendingMcpAuth, serverName) {
69
+ const pending = pendingMcpAuth.get(serverName);
70
+ if (!pending)
71
+ return;
72
+ pendingMcpAuth.delete(serverName);
73
+ clearTimeout(pending.timer);
74
+ await pending.close();
75
+ }
76
+ export async function closeAllPendingMcpAuth(pendingMcpAuth) {
77
+ const names = [...pendingMcpAuth.keys()];
78
+ await Promise.all(names.map(async (name) => {
79
+ try {
80
+ await closePendingMcpAuth(pendingMcpAuth, name);
81
+ }
82
+ catch {
83
+ // Best-effort cleanup during plugin shutdown.
84
+ }
85
+ }));
86
+ }
87
+ async function savePendingMcpAuth(pendingMcpAuth, serverName, session) {
88
+ await closePendingMcpAuth(pendingMcpAuth, serverName);
89
+ const pending = {
90
+ query: session.query,
91
+ close: session.close,
92
+ timer: undefined,
93
+ };
94
+ const timer = setTimeout(() => {
95
+ if (pendingMcpAuth.get(serverName) !== pending)
96
+ return;
97
+ pendingMcpAuth.delete(serverName);
98
+ void pending.close().catch(() => undefined);
99
+ }, MCP_AUTH_TTL_MS);
100
+ if (typeof timer.unref === "function")
101
+ timer.unref();
102
+ pending.timer = timer;
103
+ pendingMcpAuth.set(serverName, pending);
104
+ }
105
+ export async function runQoderUsage(_context) {
106
+ try {
107
+ const lines = [];
108
+ const live = await getLiveUsage();
109
+ lines.push(live ? formatUsageReport(live) : "Live usage unavailable (not logged in or Qoder runtime unavailable).");
110
+ const summary = summarize();
111
+ lines.push("");
112
+ lines.push("Local Cost Ledger");
113
+ lines.push(` Total cost: ${formatCost(summary.totalCostUsd)}`);
114
+ lines.push(` Turns: ${summary.turnCount}`);
115
+ lines.push(` Tokens: ${summary.totalInputTokens} in / ${summary.totalOutputTokens} out`);
116
+ const models = Object.entries(summary.byModel);
117
+ if (models.length > 0) {
118
+ lines.push(" By model:");
119
+ for (const [name, bucket] of models) {
120
+ lines.push(` ${name}: ${formatCost(bucket.costUsd)} (${bucket.turns} turns)`);
121
+ }
122
+ }
123
+ return { title: "Qoder Usage", output: lines.join("\n") };
124
+ }
125
+ catch (error) {
126
+ return { title: "Qoder Usage", output: `Failed to load usage: ${describeError(error)}`, variant: "error" };
127
+ }
128
+ }
129
+ export async function runQoderModels(context) {
130
+ try {
131
+ const models = listModels(context.modelEnvironment ?? process.env, context.modelOptions ?? {});
132
+ const lines = ["Qoder Models"];
133
+ for (const model of models) {
134
+ lines.push(` ${safeDisplay(model.id, "unknown", 256)}: ${safeDisplay(model.name, "unknown", 512)}`);
135
+ lines.push(` context ${model.limit.context}, output ${model.limit.output}, price ${model.multiplier}x`);
136
+ lines.push(` vision ${model.attachment ? "yes" : "no"}, reasoning ${model.reasoning ? "yes" : "no"}`);
137
+ }
138
+ return { title: "Qoder Models", output: lines.join("\n") };
139
+ }
140
+ catch (error) {
141
+ return { title: "Qoder Models", output: `Failed to list models: ${describeError(error)}`, variant: "error" };
142
+ }
143
+ }
144
+ export async function runQoderSessions(_context, input = {}) {
145
+ try {
146
+ const limit = typeof input.limit === "number" && input.limit > 0 ? input.limit : 10;
147
+ const dir = typeof input.dir === "string" && input.dir.trim() ? input.dir.trim() : undefined;
148
+ const sessionsResult = await listSessions({
149
+ limit: Math.max(1, Math.min(100, Math.floor(limit))),
150
+ ...(dir ? { dir } : {}),
151
+ });
152
+ const sessions = Array.isArray(sessionsResult) ? sessionsResult : [];
153
+ if (sessions.length === 0)
154
+ return { title: "Qoder Sessions", output: "No recent Qoder sessions found." };
155
+ const lines = ["Recent Qoder Sessions"];
156
+ for (const session of sessions) {
157
+ const item = isRecord(session) ? session : {};
158
+ const sessionId = safeDisplay(item.sessionId, "unknown", 256);
159
+ const title = typeof item.customTitle === "string" && item.customTitle
160
+ ? safeDisplay(item.customTitle, sessionId, 512)
161
+ : typeof item.summary === "string" && item.summary
162
+ ? safeDisplay(item.summary, sessionId, 512)
163
+ : sessionId;
164
+ const lastModified = item.lastModified;
165
+ const dateValue = typeof lastModified === "string" || typeof lastModified === "number"
166
+ ? new Date(lastModified)
167
+ : null;
168
+ const date = dateValue && !Number.isNaN(dateValue.getTime()) ? dateValue.toLocaleString() : "unknown";
169
+ const branch = safeDisplay(item.gitBranch, "n/a", 256);
170
+ const cwd = safeDisplay(item.cwd, "n/a", 1024);
171
+ lines.push(`- [${sessionId.slice(0, 8)}] ${title}`);
172
+ lines.push(` Updated: ${date} | Branch: ${branch} | Path: ${cwd}`);
173
+ }
174
+ return { title: "Qoder Sessions", output: lines.join("\n") };
175
+ }
176
+ catch (error) {
177
+ return { title: "Qoder Sessions", output: `Failed to list sessions: ${describeError(error)}`, variant: "error" };
178
+ }
179
+ }
180
+ export async function runQoderSessionReset(context, key) {
181
+ try {
182
+ const target = typeof key === "string" && key.trim() ? key.trim() : context.configuredSessionKey;
183
+ if (!target) {
184
+ return {
185
+ title: "Qoder Session",
186
+ output: "No session key specified and none configured. Provide a key or use 'all' to reset all sessions.",
187
+ variant: "warning",
188
+ };
189
+ }
190
+ if (target.toLowerCase() === "all") {
191
+ await clearAllSessions();
192
+ return { title: "Qoder Session", output: "Reset all persisted Qoder sessions.", variant: "success" };
193
+ }
194
+ await deleteQoderSessionForCwd(target, context.configuredCwd, context.configuredSessionId || target);
195
+ return {
196
+ title: "Qoder Session",
197
+ output: `Reset persisted Qoder session: ${safeDisplay(target, "unknown", 512)}`,
198
+ variant: "success",
199
+ };
200
+ }
201
+ catch (error) {
202
+ return { title: "Qoder Session", output: `Failed to reset session: ${describeError(error)}`, variant: "error" };
203
+ }
204
+ }
205
+ export async function runQoderSessionFork(context, input = {}) {
206
+ try {
207
+ const requestedId = typeof input.sessionId === "string" ? input.sessionId.trim() : "";
208
+ const dir = typeof input.dir === "string" && input.dir.trim() ? input.dir.trim() : context.configuredCwd;
209
+ let sourceId = requestedId || context.configuredSessionId;
210
+ if (!sourceId && context.configuredSessionKey) {
211
+ const persisted = await getQoderSessionForCwd(context.configuredSessionKey, dir);
212
+ sourceId = persisted?.qoderSessionId;
213
+ }
214
+ if (!sourceId) {
215
+ return {
216
+ title: "Qoder Session Fork",
217
+ output: "No source session ID is available. Provide sessionId or configure session persistence first.",
218
+ variant: "warning",
219
+ };
220
+ }
221
+ const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : undefined;
222
+ const upToMessageId = typeof input.upToMessageId === "string" && input.upToMessageId.trim()
223
+ ? input.upToMessageId.trim()
224
+ : undefined;
225
+ const forked = await forkSession(sourceId, {
226
+ dir,
227
+ ...(title ? { title } : {}),
228
+ ...(upToMessageId ? { upToMessageId } : {}),
229
+ });
230
+ return {
231
+ title: "Qoder Session Fork",
232
+ output: [
233
+ `Forked session ${safeDisplay(sourceId, "unknown", 256)}.`,
234
+ `New session ID: ${safeDisplay(forked.sessionId, "unknown", 256)}`,
235
+ "The active provider mapping was left unchanged; use the new ID as sessionId when you want to continue the fork.",
236
+ ].join("\n"),
237
+ variant: "success",
238
+ };
239
+ }
240
+ catch (error) {
241
+ return { title: "Qoder Session Fork", output: `Failed to fork session: ${describeError(error)}`, variant: "error" };
242
+ }
243
+ }
244
+ export async function runQoderMcpStatus(context) {
245
+ let control;
246
+ try {
247
+ control = await openSdkControlSession(context.configuredBridgeOptions, context.configuredCwd);
248
+ const statuses = await withMcpControlTimeout(control.query.mcpServerStatus(), "status request");
249
+ return { title: "Qoder MCP Status", output: formatMcpStatuses(statuses) };
250
+ }
251
+ catch (error) {
252
+ return { title: "Qoder MCP Status", output: `Failed to inspect MCP status: ${describeError(error)}`, variant: "error" };
253
+ }
254
+ finally {
255
+ if (control)
256
+ await control.close();
257
+ }
258
+ }
259
+ export async function runQoderMcpAuth(context, input) {
260
+ const serverName = typeof input.server === "string" ? input.server.trim() : "";
261
+ const callbackUrl = typeof input.callbackUrl === "string" ? input.callbackUrl.trim() : "";
262
+ const redirectUri = typeof input.redirectUri === "string" ? input.redirectUri.trim() : "";
263
+ if (!serverName || serverName.length > 256 || CONTROL_CHAR_TEST.test(serverName)) {
264
+ return { title: "Qoder MCP OAuth", output: "Provide a valid MCP server name.", variant: "warning" };
265
+ }
266
+ if (callbackUrl && (callbackUrl.length > 16_384 || CONTROL_CHAR_TEST.test(callbackUrl))) {
267
+ return { title: "Qoder MCP OAuth", output: "The callback URL is invalid or too long.", variant: "warning" };
268
+ }
269
+ if (redirectUri && (redirectUri.length > 16_384 || CONTROL_CHAR_TEST.test(redirectUri))) {
270
+ return { title: "Qoder MCP OAuth", output: "The redirect URI is invalid or too long.", variant: "warning" };
271
+ }
272
+ const pending = context.pendingMcpAuth.get(serverName);
273
+ if (callbackUrl && !pending) {
274
+ return {
275
+ title: "Qoder MCP OAuth",
276
+ output: `No pending OAuth flow for ${safeDisplay(serverName, "unknown")}. Call qoder_mcp_auth without callbackUrl first, then authorize using the returned URL.`,
277
+ variant: "warning",
278
+ };
279
+ }
280
+ if (callbackUrl && pending) {
281
+ try {
282
+ await withMcpControlTimeout(pending.query.mcpSubmitOAuthCallbackUrl(serverName, callbackUrl), "OAuth callback");
283
+ context.pendingMcpAuth.delete(serverName);
284
+ clearTimeout(pending.timer);
285
+ await pending.close();
286
+ return {
287
+ title: "Qoder MCP OAuth",
288
+ output: `OAuth authentication completed for ${safeDisplay(serverName, "unknown")}. Run qoder_mcp_status to verify the connection.`,
289
+ variant: "success",
290
+ };
291
+ }
292
+ catch (error) {
293
+ return {
294
+ title: "Qoder MCP OAuth",
295
+ output: `OAuth callback failed: ${describeError(error)} The pending flow was retained for another callback attempt.`,
296
+ variant: "error",
297
+ };
298
+ }
299
+ }
300
+ await closePendingMcpAuth(context.pendingMcpAuth, serverName);
301
+ let control;
302
+ try {
303
+ control = await openSdkControlSession(context.configuredBridgeOptions, context.configuredCwd);
304
+ const result = await withMcpControlTimeout(control.query.mcpAuthenticate(serverName, redirectUri || undefined), "OAuth authentication");
305
+ if (!result.requiresUserAction) {
306
+ await control.close();
307
+ control = undefined;
308
+ return {
309
+ title: "Qoder MCP OAuth",
310
+ output: `${safeDisplay(serverName, "unknown")} is already authenticated (or was refreshed silently).`,
311
+ variant: "success",
312
+ };
313
+ }
314
+ if (!result.authUrl) {
315
+ await control.close();
316
+ control = undefined;
317
+ return {
318
+ title: "Qoder MCP OAuth",
319
+ output: `Qoder requires user action for ${safeDisplay(serverName, "unknown")}, but did not return an authorization URL.`,
320
+ variant: "warning",
321
+ };
322
+ }
323
+ await savePendingMcpAuth(context.pendingMcpAuth, serverName, control);
324
+ control = undefined;
325
+ return {
326
+ title: "Qoder MCP OAuth",
327
+ output: [
328
+ `Authorize ${safeDisplay(serverName, "unknown")} by opening this URL:`,
329
+ safeDisplay(result.authUrl, "(authorization URL unavailable)", 16_384),
330
+ "After the redirect, call qoder_mcp_auth again with the same server and the complete callbackUrl.",
331
+ `The pending flow expires in ${Math.round(MCP_AUTH_TTL_MS / 60_000)} minutes.`,
332
+ ].join("\n"),
333
+ };
334
+ }
335
+ catch (error) {
336
+ return { title: "Qoder MCP OAuth", output: `Failed to start OAuth: ${describeError(error)}`, variant: "error" };
337
+ }
338
+ finally {
339
+ if (control)
340
+ await control.close();
341
+ }
342
+ }
343
+ export function runQoderPlanMode() {
344
+ const lines = [
345
+ "Qoder Plan Mode",
346
+ "Plan Mode instructs Qoder to analyze and plan changes without modifying files or running tool actions.",
347
+ "",
348
+ "Configuration in ~/.config/opencode/opencode.json:",
349
+ " \"provider\": {",
350
+ " \"qoder\": {",
351
+ " \"options\": {",
352
+ " \"planMode\": true",
353
+ " }",
354
+ " }",
355
+ " }",
356
+ "",
357
+ "Plan Mode operates independently from tool permissions, preserving your underlying permission mode.",
358
+ ];
359
+ return { title: "Qoder Plan Mode", output: lines.join("\n") };
360
+ }
361
+ export async function executeQoderCommand(name, rawArguments, context) {
362
+ if (typeof rawArguments !== "string" || rawArguments.length > MAX_ARGUMENTS || CONTROL_CHAR_TEST.test(rawArguments)) {
363
+ return { title: "Qoder Command", output: "Command arguments are invalid or too long.", variant: "error" };
364
+ }
365
+ switch (name) {
366
+ case "qoder_usage":
367
+ return runQoderUsage(context);
368
+ case "qoder_models":
369
+ return runQoderModels(context);
370
+ case "qoder_sessions": {
371
+ const parsed = parseSessionsArguments(rawArguments);
372
+ return parsed.error ? parsed.error : runQoderSessions(context, parsed.value);
373
+ }
374
+ case "qoder_session_reset": {
375
+ const parsed = parseResetArguments(rawArguments);
376
+ return parsed.error ? parsed.error : runQoderSessionReset(context, parsed.value);
377
+ }
378
+ case "qoder_session_fork": {
379
+ const parsed = parseForkArguments(rawArguments);
380
+ return parsed.error ? parsed.error : runQoderSessionFork(context, parsed.value);
381
+ }
382
+ case "qoder_mcp_status":
383
+ return runQoderMcpStatus(context);
384
+ case "qoder_mcp_auth": {
385
+ const parsed = parseMcpAuthArguments(rawArguments);
386
+ return parsed.error ? parsed.error : runQoderMcpAuth(context, parsed.value);
387
+ }
388
+ case "qoder_plan_mode":
389
+ return runQoderPlanMode();
390
+ default:
391
+ return { title: "Qoder Command", output: `Unknown Qoder command: ${safeDisplay(name, "unknown")}`, variant: "error" };
392
+ }
393
+ }
394
+ function isRecord(value) {
395
+ return typeof value === "object" && value !== null && !Array.isArray(value);
396
+ }
397
+ function parseTokens(raw) {
398
+ const tokens = [];
399
+ let current = "";
400
+ let quote;
401
+ for (let index = 0; index < raw.length; index++) {
402
+ const char = raw[index];
403
+ if (quote) {
404
+ if (char === quote) {
405
+ quote = undefined;
406
+ }
407
+ else if (char === "\\" && quote === '"' && index + 1 < raw.length && raw[index + 1] === '"') {
408
+ current += '"';
409
+ index++;
410
+ }
411
+ else {
412
+ current += char;
413
+ }
414
+ continue;
415
+ }
416
+ if (char === "'" || char === '"') {
417
+ quote = char;
418
+ }
419
+ else if (/\s/.test(char)) {
420
+ if (current) {
421
+ tokens.push(current);
422
+ current = "";
423
+ }
424
+ }
425
+ else {
426
+ current += char;
427
+ }
428
+ }
429
+ if (quote)
430
+ return { error: { title: "Qoder Command", output: "Unclosed quote in command arguments.", variant: "error" } };
431
+ if (current)
432
+ tokens.push(current);
433
+ return { value: tokens };
434
+ }
435
+ function parseSessionsArguments(raw) {
436
+ const tokenResult = parseTokens(raw);
437
+ if (tokenResult.error)
438
+ return tokenResult;
439
+ const tokens = tokenResult.value;
440
+ let dir;
441
+ let limit;
442
+ for (let index = 0; index < tokens.length; index++) {
443
+ const token = tokens[index] ?? "";
444
+ const dirMatch = token.match(/^--dir=(.+)$/);
445
+ const limitMatch = token.match(/^--limit=(.+)$/);
446
+ if (dirMatch) {
447
+ if (dir)
448
+ return invalidArguments("Only one sessions directory may be supplied.");
449
+ dir = dirMatch[1];
450
+ continue;
451
+ }
452
+ if (limitMatch) {
453
+ if (limit !== undefined)
454
+ return invalidArguments("Only one sessions limit may be supplied.");
455
+ limit = parsePositiveInteger(limitMatch[1]);
456
+ if (limit === undefined)
457
+ return invalidArguments("Sessions limit must be a positive integer.");
458
+ continue;
459
+ }
460
+ if (token === "--dir") {
461
+ const value = tokens[++index];
462
+ if (!value || dir)
463
+ return invalidArguments("Provide one valid sessions directory.");
464
+ dir = value;
465
+ continue;
466
+ }
467
+ if (token === "--limit" || token === "-n") {
468
+ const value = tokens[++index];
469
+ if (!value || limit !== undefined)
470
+ return invalidArguments("Provide one positive sessions limit.");
471
+ limit = parsePositiveInteger(value);
472
+ if (limit === undefined)
473
+ return invalidArguments("Sessions limit must be a positive integer.");
474
+ continue;
475
+ }
476
+ if (/^\d+$/.test(token) && limit === undefined) {
477
+ limit = parsePositiveInteger(token);
478
+ if (limit === undefined)
479
+ return invalidArguments("Sessions limit must be a positive integer.");
480
+ continue;
481
+ }
482
+ if (!dir) {
483
+ dir = token;
484
+ continue;
485
+ }
486
+ return invalidArguments("Use qoder_sessions as [directory] [limit], or --dir and --limit.");
487
+ }
488
+ return { value: { ...(dir ? { dir } : {}), ...(limit !== undefined ? { limit } : {}) } };
489
+ }
490
+ function parseResetArguments(raw) {
491
+ const tokenResult = parseTokens(raw);
492
+ if (tokenResult.error)
493
+ return tokenResult;
494
+ if (tokenResult.value.length > 1)
495
+ return invalidArguments("Use qoder_session_reset with one session key or 'all'.");
496
+ return { value: tokenResult.value[0] };
497
+ }
498
+ function parseForkArguments(raw) {
499
+ if (raw.trim().startsWith("{")) {
500
+ try {
501
+ const parsed = JSON.parse(raw);
502
+ if (!isRecord(parsed))
503
+ return invalidArguments("Fork JSON arguments must be an object.");
504
+ return { value: normalizeForkObject(parsed) };
505
+ }
506
+ catch {
507
+ return invalidArguments("Fork JSON arguments are invalid.");
508
+ }
509
+ }
510
+ const tokenResult = parseTokens(raw);
511
+ if (tokenResult.error)
512
+ return tokenResult;
513
+ const tokens = tokenResult.value;
514
+ const positional = [];
515
+ const result = {};
516
+ for (let index = 0; index < tokens.length; index++) {
517
+ const token = tokens[index] ?? "";
518
+ const equals = token.indexOf("=");
519
+ if (equals > 0) {
520
+ const key = token.slice(0, equals);
521
+ const value = token.slice(equals + 1);
522
+ if (!setForkField(result, key, value))
523
+ return invalidArguments(`Unknown fork argument: ${key}`);
524
+ continue;
525
+ }
526
+ const option = forkOption(token);
527
+ if (option) {
528
+ const value = tokens[++index];
529
+ if (!value || !setForkField(result, option, value))
530
+ return invalidArguments(`Missing value for ${token}.`);
531
+ continue;
532
+ }
533
+ positional.push(token);
534
+ }
535
+ const fields = ["sessionId", "dir", "title", "upToMessageId"];
536
+ for (let index = 0; index < positional.length; index++) {
537
+ const field = fields[index];
538
+ if (!field || result[field] !== undefined)
539
+ return invalidArguments("Fork arguments are ambiguous; use key=value fields.");
540
+ result[field] = positional[index];
541
+ }
542
+ return { value: result };
543
+ }
544
+ function parseMcpAuthArguments(raw) {
545
+ const tokenResult = parseTokens(raw);
546
+ if (tokenResult.error)
547
+ return tokenResult;
548
+ const tokens = tokenResult.value;
549
+ let server = "";
550
+ let callbackUrl;
551
+ let redirectUri;
552
+ const positional = [];
553
+ for (let index = 0; index < tokens.length; index++) {
554
+ const token = tokens[index] ?? "";
555
+ const equals = token.indexOf("=");
556
+ if (equals > 0) {
557
+ const key = token.slice(0, equals);
558
+ const value = token.slice(equals + 1);
559
+ if (key === "server")
560
+ server = value;
561
+ else if (key === "callbackUrl" || key === "callback-url")
562
+ callbackUrl = value;
563
+ else if (key === "redirectUri" || key === "redirect-uri")
564
+ redirectUri = value;
565
+ else
566
+ return invalidArguments(`Unknown MCP OAuth argument: ${key}`);
567
+ continue;
568
+ }
569
+ const option = token === "--callback-url" ? "callbackUrl" : token === "--redirect-uri" ? "redirectUri" : undefined;
570
+ if (option) {
571
+ const value = tokens[++index];
572
+ if (!value)
573
+ return invalidArguments(`Missing value for ${token}.`);
574
+ if (option === "callbackUrl")
575
+ callbackUrl = value;
576
+ else
577
+ redirectUri = value;
578
+ continue;
579
+ }
580
+ positional.push(token);
581
+ }
582
+ if (!server)
583
+ server = positional.shift() ?? "";
584
+ if (callbackUrl === undefined)
585
+ callbackUrl = positional.shift();
586
+ if (redirectUri === undefined)
587
+ redirectUri = positional.shift();
588
+ if (positional.length > 0)
589
+ return invalidArguments("Use qoder_mcp_auth as server [callbackUrl] [redirectUri].");
590
+ return { value: { server, ...(callbackUrl ? { callbackUrl } : {}), ...(redirectUri ? { redirectUri } : {}) } };
591
+ }
592
+ function normalizeForkObject(value) {
593
+ return {
594
+ ...(stringValue(value.sessionId) ? { sessionId: stringValue(value.sessionId) } : {}),
595
+ ...(stringValue(value.dir) ? { dir: stringValue(value.dir) } : {}),
596
+ ...(stringValue(value.title) ? { title: stringValue(value.title) } : {}),
597
+ ...(stringValue(value.upToMessageId) ? { upToMessageId: stringValue(value.upToMessageId) } : {}),
598
+ };
599
+ }
600
+ function stringValue(value) {
601
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
602
+ }
603
+ function setForkField(target, key, value) {
604
+ const field = forkOption(key) ?? (key === "sessionId" || key === "dir" || key === "title" || key === "upToMessageId" ? key : undefined);
605
+ if (!field || !value)
606
+ return false;
607
+ target[field] = value;
608
+ return true;
609
+ }
610
+ function forkOption(value) {
611
+ switch (value) {
612
+ case "--session-id":
613
+ case "--sessionId":
614
+ case "id":
615
+ case "sessionId":
616
+ return "sessionId";
617
+ case "--dir":
618
+ case "dir":
619
+ return "dir";
620
+ case "--title":
621
+ case "title":
622
+ return "title";
623
+ case "--up-to-message-id":
624
+ case "--upToMessageId":
625
+ case "upToMessageId":
626
+ case "cutoff":
627
+ return "upToMessageId";
628
+ default:
629
+ return undefined;
630
+ }
631
+ }
632
+ function parsePositiveInteger(value) {
633
+ if (!value || !/^\d+$/.test(value))
634
+ return undefined;
635
+ const parsed = Number(value);
636
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
637
+ }
638
+ function invalidArguments(output) {
639
+ return { error: { title: "Qoder Command", output, variant: "error" } };
640
+ }
641
+ //# sourceMappingURL=command-actions.js.map