@docyrus/docyrus 0.0.24 → 0.0.26

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/server-loader.js CHANGED
@@ -692,7 +692,7 @@ var init_dist = __esm({
692
692
  // src/server/server-loader.ts
693
693
  var import_node_fs = require("node:fs");
694
694
  var import_node_url = require("node:url");
695
- var import_node_path3 = require("node:path");
695
+ var import_node_path4 = require("node:path");
696
696
  var import_picocolors = __toESM(require_picocolors());
697
697
 
698
698
  // src/agent/envStore.ts
@@ -795,8 +795,8 @@ var AgentEnvStore = class {
795
795
 
796
796
  // src/server/agentServer.ts
797
797
  var import_node_child_process = require("node:child_process");
798
- var import_promises2 = require("node:fs/promises");
799
- var import_node_path2 = require("node:path");
798
+ var import_promises3 = require("node:fs/promises");
799
+ var import_node_path3 = require("node:path");
800
800
 
801
801
  // ../../node_modules/.pnpm/hono@4.12.8/node_modules/hono/dist/compose.js
802
802
  var compose = (middleware, onError, onNotFound) => {
@@ -2936,10 +2936,212 @@ var cors = (options) => {
2936
2936
  };
2937
2937
  };
2938
2938
 
2939
+ // resources/pi-agent/shared/askUserProtocol.ts
2940
+ var ASK_USER_TAG = "ask_user";
2941
+ var ASK_USER_OPEN = `<${ASK_USER_TAG}>`;
2942
+ var ASK_USER_CLOSE = `</${ASK_USER_TAG}>`;
2943
+ var ASK_USER_RESPONSE_TAG = "ask_user_response";
2944
+ var ASK_USER_RESPONSE_OPEN = `<${ASK_USER_RESPONSE_TAG}>`;
2945
+ var ASK_USER_RESPONSE_CLOSE = `</${ASK_USER_RESPONSE_TAG}>`;
2946
+ function hashString(value) {
2947
+ let hash = 0;
2948
+ for (let index = 0; index < value.length; index += 1) {
2949
+ hash = (hash << 5) - hash + value.charCodeAt(index);
2950
+ hash |= 0;
2951
+ }
2952
+ return Math.abs(hash).toString(36);
2953
+ }
2954
+ function isRecord(value) {
2955
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2956
+ }
2957
+ function isNonEmptyString(value) {
2958
+ return typeof value === "string" && value.trim().length > 0;
2959
+ }
2960
+ function normalizeTrimmedString(value) {
2961
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
2962
+ }
2963
+ function normalizeQuestionType(value) {
2964
+ return value === "text" || value === "textarea" || value === "boolean" || value === "singleSelect" || value === "multiSelect" ? value : void 0;
2965
+ }
2966
+ function normalizeDefaultValue(value) {
2967
+ if (typeof value === "string" || typeof value === "boolean") {
2968
+ return value;
2969
+ }
2970
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
2971
+ return [...value];
2972
+ }
2973
+ return void 0;
2974
+ }
2975
+ function normalizeOptions(value) {
2976
+ if (!Array.isArray(value)) {
2977
+ return void 0;
2978
+ }
2979
+ const options = [];
2980
+ for (const item of value) {
2981
+ if (!isRecord(item) || !isNonEmptyString(item.label) || !isNonEmptyString(item.value)) {
2982
+ return void 0;
2983
+ }
2984
+ options.push({
2985
+ label: item.label.trim(),
2986
+ value: item.value.trim()
2987
+ });
2988
+ }
2989
+ return options;
2990
+ }
2991
+ function normalizeAskUserQuestion(value) {
2992
+ if (!isRecord(value)) {
2993
+ return void 0;
2994
+ }
2995
+ const id = normalizeTrimmedString(value.id);
2996
+ const label = normalizeTrimmedString(value.label);
2997
+ const type = normalizeQuestionType(value.type);
2998
+ if (!id || !label || !type) {
2999
+ return void 0;
3000
+ }
3001
+ const question = {
3002
+ id,
3003
+ label,
3004
+ type
3005
+ };
3006
+ const description = normalizeTrimmedString(value.description);
3007
+ if (description) {
3008
+ question.description = description;
3009
+ }
3010
+ if (typeof value.required === "boolean") {
3011
+ question.required = value.required;
3012
+ }
3013
+ const options = normalizeOptions(value.options);
3014
+ if (type === "singleSelect" || type === "multiSelect") {
3015
+ if (!options || options.length === 0) {
3016
+ return void 0;
3017
+ }
3018
+ question.options = options;
3019
+ }
3020
+ const placeholder = normalizeTrimmedString(value.placeholder);
3021
+ if (placeholder) {
3022
+ question.placeholder = placeholder;
3023
+ }
3024
+ const defaultValue = normalizeDefaultValue(value.defaultValue);
3025
+ if (defaultValue !== void 0) {
3026
+ question.defaultValue = defaultValue;
3027
+ }
3028
+ return question;
3029
+ }
3030
+ function normalizeAskUserRequest(value) {
3031
+ if (!isRecord(value)) {
3032
+ return void 0;
3033
+ }
3034
+ const title = normalizeTrimmedString(value.title);
3035
+ const message = normalizeTrimmedString(value.message);
3036
+ if (!title || !message || !Array.isArray(value.questions) || value.questions.length === 0) {
3037
+ return void 0;
3038
+ }
3039
+ const questions = [];
3040
+ for (const question of value.questions) {
3041
+ const normalized = normalizeAskUserQuestion(question);
3042
+ if (!normalized) {
3043
+ return void 0;
3044
+ }
3045
+ questions.push(normalized);
3046
+ }
3047
+ return {
3048
+ title,
3049
+ message,
3050
+ questions
3051
+ };
3052
+ }
3053
+ function normalizeAskUserResponse(value) {
3054
+ if (!isRecord(value)) {
3055
+ return void 0;
3056
+ }
3057
+ const action = value.action;
3058
+ if (action !== "submit" && action !== "cancel" && action !== "decline") {
3059
+ return void 0;
3060
+ }
3061
+ const answersRecord = isRecord(value.answers) ? value.answers : {};
3062
+ const answers = {};
3063
+ for (const [key, answerValue] of Object.entries(answersRecord)) {
3064
+ const normalized = normalizeDefaultValue(answerValue);
3065
+ if (normalized !== void 0) {
3066
+ answers[key] = normalized;
3067
+ }
3068
+ }
3069
+ return {
3070
+ action,
3071
+ answers
3072
+ };
3073
+ }
3074
+ function createAskUserToolCallId(request) {
3075
+ return `ask_user_${hashString(JSON.stringify(request))}`;
3076
+ }
3077
+ function parseAskUserRequestFromText(text) {
3078
+ const trimmed = text.trim();
3079
+ if (!trimmed.startsWith(ASK_USER_OPEN) || !trimmed.endsWith(ASK_USER_CLOSE)) {
3080
+ return void 0;
3081
+ }
3082
+ const body = trimmed.slice(ASK_USER_OPEN.length, trimmed.length - ASK_USER_CLOSE.length).trim();
3083
+ if (!body) {
3084
+ return void 0;
3085
+ }
3086
+ try {
3087
+ return normalizeAskUserRequest(JSON.parse(body));
3088
+ } catch {
3089
+ return void 0;
3090
+ }
3091
+ }
3092
+ function formatAskUserResponsePrompt(response) {
3093
+ return [
3094
+ "The user submitted structured clarification answers.",
3095
+ "",
3096
+ `${ASK_USER_RESPONSE_OPEN}`,
3097
+ JSON.stringify(response, null, 2),
3098
+ `${ASK_USER_RESPONSE_CLOSE}`,
3099
+ "",
3100
+ "Continue the planning workflow using these answers."
3101
+ ].join("\n");
3102
+ }
3103
+ function parseAskUserResponseFromToolOutput(output) {
3104
+ return normalizeAskUserResponse(output);
3105
+ }
3106
+
2939
3107
  // src/server/eventBridge.ts
2940
3108
  function createEventBridge(params) {
2941
- const { messageId, onChunk, onDone, onError } = params;
3109
+ const { messageId, onChunk, onDone, onError, onAskUser } = params;
2942
3110
  const activeToolCalls = /* @__PURE__ */ new Map();
3111
+ let activeTextBuffer = null;
3112
+ function flushBufferedTextToStream(close) {
3113
+ if (!activeTextBuffer) {
3114
+ return;
3115
+ }
3116
+ onChunk({ type: "text-start", id: messageId });
3117
+ if (activeTextBuffer.text.length > 0) {
3118
+ onChunk({ type: "text-delta", id: messageId, delta: activeTextBuffer.text });
3119
+ }
3120
+ if (close) {
3121
+ onChunk({ type: "text-end", id: messageId });
3122
+ activeTextBuffer = null;
3123
+ return;
3124
+ }
3125
+ activeTextBuffer = { text: "", mode: "plain" };
3126
+ }
3127
+ function maybeEmitAskUser(text) {
3128
+ const request = parseAskUserRequestFromText(text);
3129
+ if (!request) {
3130
+ return false;
3131
+ }
3132
+ const toolCallId = createAskUserToolCallId(request);
3133
+ onAskUser?.({ toolCallId, request });
3134
+ onChunk({ type: "tool-input-start", toolCallId, toolName: "ask_user", dynamic: true, title: request.title });
3135
+ onChunk({
3136
+ type: "tool-input-available",
3137
+ toolCallId,
3138
+ toolName: "ask_user",
3139
+ input: request,
3140
+ dynamic: true,
3141
+ title: request.title
3142
+ });
3143
+ return true;
3144
+ }
2943
3145
  function handleEvent(event) {
2944
3146
  switch (event.type) {
2945
3147
  case "message_update": {
@@ -2963,15 +3165,42 @@ function createEventBridge(params) {
2963
3165
  }
2964
3166
  switch (assistantEvent.type) {
2965
3167
  case "text_start": {
2966
- onChunk({ type: "text-start", id: messageId });
3168
+ activeTextBuffer = { text: "", mode: "pending" };
2967
3169
  break;
2968
3170
  }
2969
3171
  case "text_delta": {
2970
- onChunk({ type: "text-delta", id: messageId, delta: assistantEvent.delta ?? "" });
3172
+ if (!activeTextBuffer) {
3173
+ onChunk({ type: "text-start", id: messageId });
3174
+ onChunk({ type: "text-delta", id: messageId, delta: assistantEvent.delta ?? "" });
3175
+ activeTextBuffer = { text: "", mode: "plain" };
3176
+ break;
3177
+ }
3178
+ if (activeTextBuffer.mode === "plain") {
3179
+ onChunk({ type: "text-delta", id: messageId, delta: assistantEvent.delta ?? "" });
3180
+ break;
3181
+ }
3182
+ activeTextBuffer.text += assistantEvent.delta ?? "";
3183
+ if ("<ask_user>".startsWith(activeTextBuffer.text) || activeTextBuffer.text.startsWith("<ask_user>")) {
3184
+ break;
3185
+ }
3186
+ flushBufferedTextToStream(false);
2971
3187
  break;
2972
3188
  }
2973
3189
  case "text_end": {
2974
- onChunk({ type: "text-end", id: messageId });
3190
+ if (!activeTextBuffer) {
3191
+ onChunk({ type: "text-end", id: messageId });
3192
+ break;
3193
+ }
3194
+ if (activeTextBuffer.mode === "plain") {
3195
+ onChunk({ type: "text-end", id: messageId });
3196
+ activeTextBuffer = null;
3197
+ break;
3198
+ }
3199
+ if (!maybeEmitAskUser(activeTextBuffer.text)) {
3200
+ flushBufferedTextToStream(true);
3201
+ } else {
3202
+ activeTextBuffer = null;
3203
+ }
2975
3204
  break;
2976
3205
  }
2977
3206
  case "thinking_start": {
@@ -3050,6 +3279,854 @@ function createEventBridge(params) {
3050
3279
  return { handleEvent };
3051
3280
  }
3052
3281
 
3282
+ // src/agent/modelsConfig.ts
3283
+ var import_promises2 = require("node:fs/promises");
3284
+ var import_node_path2 = require("node:path");
3285
+ function createDefaultState2() {
3286
+ return {
3287
+ providers: {}
3288
+ };
3289
+ }
3290
+ async function readModelsConfig(filePath) {
3291
+ try {
3292
+ const raw2 = await (0, import_promises2.readFile)(filePath, "utf8");
3293
+ const parsed = JSON.parse(raw2);
3294
+ if (!parsed || typeof parsed !== "object" || typeof parsed.providers !== "object" || parsed.providers === null) {
3295
+ return createDefaultState2();
3296
+ }
3297
+ return {
3298
+ providers: { ...parsed.providers }
3299
+ };
3300
+ } catch (error) {
3301
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
3302
+ return createDefaultState2();
3303
+ }
3304
+ return createDefaultState2();
3305
+ }
3306
+ }
3307
+ async function writeModelsConfig(filePath, state) {
3308
+ const directory = (0, import_node_path2.dirname)(filePath);
3309
+ await (0, import_promises2.mkdir)(directory, {
3310
+ recursive: true,
3311
+ mode: 448
3312
+ });
3313
+ await (0, import_promises2.writeFile)(filePath, `${JSON.stringify(state, null, 2)}
3314
+ `, {
3315
+ encoding: "utf8",
3316
+ mode: 384
3317
+ });
3318
+ await (0, import_promises2.chmod)(filePath, 384);
3319
+ }
3320
+ async function upsertModelsProvider(filePath, providerId, providerConfig) {
3321
+ const state = await readModelsConfig(filePath);
3322
+ await writeModelsConfig(filePath, {
3323
+ providers: {
3324
+ ...state.providers,
3325
+ [providerId]: providerConfig
3326
+ }
3327
+ });
3328
+ }
3329
+ async function removeModelsProvider(filePath, providerId) {
3330
+ const state = await readModelsConfig(filePath);
3331
+ const nextProviders = { ...state.providers };
3332
+ delete nextProviders[providerId];
3333
+ await writeModelsConfig(filePath, {
3334
+ providers: nextProviders
3335
+ });
3336
+ }
3337
+ function createCustomOpenAiProviderConfig(params) {
3338
+ return {
3339
+ baseUrl: params.baseUrl,
3340
+ apiKey: "env:CUSTOM_OPENAI_API_KEY",
3341
+ api: "openai-completions",
3342
+ models: [
3343
+ {
3344
+ id: params.modelId,
3345
+ name: params.modelId,
3346
+ reasoning: false,
3347
+ input: ["text"],
3348
+ contextWindow: 128e3,
3349
+ maxTokens: 16384,
3350
+ cost: {
3351
+ input: 0,
3352
+ output: 0,
3353
+ cacheRead: 0,
3354
+ cacheWrite: 0
3355
+ }
3356
+ }
3357
+ ]
3358
+ };
3359
+ }
3360
+ function createAzureProviderConfig(params) {
3361
+ const providerConfig = {
3362
+ baseUrl: params.baseUrl,
3363
+ apiKey: "env:AZURE_OPENAI_API_KEY"
3364
+ };
3365
+ if (params.useCustomModel) {
3366
+ providerConfig.api = "azure-openai-responses";
3367
+ providerConfig.models = [
3368
+ {
3369
+ id: params.modelId,
3370
+ name: params.modelId,
3371
+ reasoning: false,
3372
+ input: ["text"],
3373
+ contextWindow: 128e3,
3374
+ maxTokens: 16384,
3375
+ cost: {
3376
+ input: 0,
3377
+ output: 0,
3378
+ cacheRead: 0,
3379
+ cacheWrite: 0
3380
+ }
3381
+ }
3382
+ ];
3383
+ }
3384
+ return providerConfig;
3385
+ }
3386
+
3387
+ // src/agent/authFlows.ts
3388
+ function trimOrUndefined(value) {
3389
+ const trimmed = value?.trim();
3390
+ return trimmed && trimmed.length > 0 ? trimmed : void 0;
3391
+ }
3392
+ async function saveGenericApiKey(params) {
3393
+ params.authStorage.set(params.providerId, {
3394
+ type: "api_key",
3395
+ key: params.apiKey.trim()
3396
+ });
3397
+ }
3398
+ async function saveCustomOpenAiConfig(params) {
3399
+ params.authStorage.set("custom-openai", {
3400
+ type: "api_key",
3401
+ key: params.apiKey.trim()
3402
+ });
3403
+ await params.envStore.removeMany([
3404
+ "CUSTOM_OPENAI_API_KEY"
3405
+ ]);
3406
+ await params.envStore.setMany({
3407
+ CUSTOM_OPENAI_API_KEY: params.apiKey.trim()
3408
+ });
3409
+ await upsertModelsProvider(params.modelsJsonPath, "custom-openai", createCustomOpenAiProviderConfig({
3410
+ baseUrl: params.baseUrl.trim(),
3411
+ modelId: params.modelId.trim()
3412
+ }));
3413
+ params.settingsManager.setDefaultModelAndProvider("custom-openai", params.modelId.trim());
3414
+ }
3415
+ async function saveAzureConfig(params) {
3416
+ const modelId = params.modelId.trim();
3417
+ const useCustomModel = params.useCustomModel ?? false;
3418
+ params.authStorage.set("azure-openai-responses", {
3419
+ type: "api_key",
3420
+ key: params.apiKey.trim()
3421
+ });
3422
+ await params.envStore.removeMany([
3423
+ "AZURE_OPENAI_API_KEY",
3424
+ "AZURE_OPENAI_API_VERSION",
3425
+ "AZURE_OPENAI_DEPLOYMENT_NAME_MAP"
3426
+ ]);
3427
+ const envValues = {
3428
+ AZURE_OPENAI_API_KEY: params.apiKey.trim()
3429
+ };
3430
+ const trimmedApiVersion = trimOrUndefined(params.apiVersion);
3431
+ if (trimmedApiVersion) {
3432
+ envValues.AZURE_OPENAI_API_VERSION = trimmedApiVersion;
3433
+ }
3434
+ const trimmedDeploymentName = trimOrUndefined(params.deploymentName);
3435
+ if (trimmedDeploymentName && trimmedDeploymentName !== modelId) {
3436
+ envValues.AZURE_OPENAI_DEPLOYMENT_NAME_MAP = JSON.stringify({
3437
+ [modelId]: trimmedDeploymentName
3438
+ });
3439
+ }
3440
+ await params.envStore.setMany(envValues);
3441
+ await upsertModelsProvider(params.modelsJsonPath, "azure-openai-responses", createAzureProviderConfig({
3442
+ baseUrl: params.baseUrl.trim(),
3443
+ modelId,
3444
+ useCustomModel
3445
+ }));
3446
+ params.settingsManager.setDefaultModelAndProvider("azure-openai-responses", modelId);
3447
+ }
3448
+ async function saveBedrockConfig(params) {
3449
+ params.authStorage.set("amazon-bedrock", {
3450
+ type: "api_key",
3451
+ key: "aws-configured"
3452
+ });
3453
+ await params.envStore.removeMany([
3454
+ "AWS_PROFILE",
3455
+ "AWS_ACCESS_KEY_ID",
3456
+ "AWS_SECRET_ACCESS_KEY",
3457
+ "AWS_SESSION_TOKEN",
3458
+ "AWS_REGION",
3459
+ "AWS_DEFAULT_REGION",
3460
+ "AWS_BEARER_TOKEN_BEDROCK"
3461
+ ]);
3462
+ const envValues = {
3463
+ AWS_REGION: params.region.trim(),
3464
+ AWS_DEFAULT_REGION: params.region.trim()
3465
+ };
3466
+ const profile = trimOrUndefined(params.profile);
3467
+ if (profile) {
3468
+ envValues.AWS_PROFILE = profile;
3469
+ }
3470
+ const accessKeyId = trimOrUndefined(params.accessKeyId);
3471
+ const secretAccessKey = trimOrUndefined(params.secretAccessKey);
3472
+ if (accessKeyId && secretAccessKey) {
3473
+ envValues.AWS_ACCESS_KEY_ID = accessKeyId;
3474
+ envValues.AWS_SECRET_ACCESS_KEY = secretAccessKey;
3475
+ }
3476
+ const sessionToken = trimOrUndefined(params.sessionToken);
3477
+ if (sessionToken) {
3478
+ envValues.AWS_SESSION_TOKEN = sessionToken;
3479
+ }
3480
+ await params.envStore.setMany(envValues);
3481
+ params.settingsManager.setDefaultModelAndProvider("amazon-bedrock", params.modelId.trim());
3482
+ }
3483
+ async function clearProviderConfig(params) {
3484
+ params.authStorage.remove(params.providerId);
3485
+ if (params.providerId === "custom-openai") {
3486
+ await params.envStore.removeMany([
3487
+ "CUSTOM_OPENAI_API_KEY"
3488
+ ]);
3489
+ await removeModelsProvider(params.modelsJsonPath, "custom-openai");
3490
+ }
3491
+ if (params.providerId === "azure-openai-responses") {
3492
+ await params.envStore.removeMany([
3493
+ "AZURE_OPENAI_API_KEY",
3494
+ "AZURE_OPENAI_API_VERSION",
3495
+ "AZURE_OPENAI_DEPLOYMENT_NAME_MAP"
3496
+ ]);
3497
+ await removeModelsProvider(params.modelsJsonPath, "azure-openai-responses");
3498
+ }
3499
+ if (params.providerId === "amazon-bedrock") {
3500
+ await params.envStore.removeMany([
3501
+ "AWS_PROFILE",
3502
+ "AWS_ACCESS_KEY_ID",
3503
+ "AWS_SECRET_ACCESS_KEY",
3504
+ "AWS_SESSION_TOKEN",
3505
+ "AWS_REGION",
3506
+ "AWS_DEFAULT_REGION",
3507
+ "AWS_BEARER_TOKEN_BEDROCK"
3508
+ ]);
3509
+ }
3510
+ }
3511
+
3512
+ // src/agent/providerCatalog.ts
3513
+ var OAUTH_ONLY_PROVIDER_IDS = /* @__PURE__ */ new Set([
3514
+ "github-copilot",
3515
+ "openai-codex",
3516
+ "google-gemini-cli",
3517
+ "google-antigravity"
3518
+ ]);
3519
+ var PROVIDER_LABELS = {
3520
+ anthropic: "Anthropic (Claude)",
3521
+ openai: "OpenAI",
3522
+ google: "Google (Gemini)",
3523
+ "google-vertex": "Google Vertex AI",
3524
+ "azure-openai-responses": "Azure OpenAI",
3525
+ "amazon-bedrock": "Amazon Bedrock",
3526
+ xai: "xAI (Grok)",
3527
+ groq: "Groq",
3528
+ cerebras: "Cerebras",
3529
+ openrouter: "OpenRouter",
3530
+ "vercel-ai-gateway": "Vercel AI Gateway",
3531
+ zai: "ZAI",
3532
+ mistral: "Mistral",
3533
+ minimax: "MiniMax",
3534
+ "minimax-cn": "MiniMax CN",
3535
+ huggingface: "Hugging Face",
3536
+ opencode: "OpenCode",
3537
+ "opencode-go": "OpenCode Go",
3538
+ "kimi-coding": "Kimi Coding",
3539
+ "custom-openai": "Custom OpenAI-Compatible",
3540
+ "google-gemini-cli": "Google Gemini CLI",
3541
+ "google-antigravity": "Google Antigravity",
3542
+ "openai-codex": "OpenAI Codex",
3543
+ "github-copilot": "GitHub Copilot"
3544
+ };
3545
+ var PROVIDER_HINTS = {
3546
+ anthropic: "recommended",
3547
+ "custom-openai": "custom base URL + API key",
3548
+ "azure-openai-responses": "API key + base URL/resource + deployment",
3549
+ "amazon-bedrock": "AWS profile or access key pair",
3550
+ "openai-codex": "browser auth",
3551
+ "github-copilot": "browser auth",
3552
+ "google-gemini-cli": "browser auth",
3553
+ "google-antigravity": "browser auth"
3554
+ };
3555
+ function humanizeProviderId(providerId) {
3556
+ return providerId.split("-").map((part) => {
3557
+ if (!part) {
3558
+ return part;
3559
+ }
3560
+ if (part.length <= 3) {
3561
+ return part.toUpperCase();
3562
+ }
3563
+ return part[0].toUpperCase() + part.slice(1);
3564
+ }).join(" ");
3565
+ }
3566
+ function toLabel(providerId) {
3567
+ return PROVIDER_LABELS[providerId] || humanizeProviderId(providerId);
3568
+ }
3569
+ function toHint(providerId) {
3570
+ return PROVIDER_HINTS[providerId];
3571
+ }
3572
+ function compareProviderOptions(a, b) {
3573
+ return a.label.localeCompare(b.label);
3574
+ }
3575
+ function getBrowserAuthProviderOptions(oauthProviders) {
3576
+ return oauthProviders.map((provider) => ({
3577
+ id: provider.id,
3578
+ label: provider.name || toLabel(provider.id),
3579
+ hint: toHint(provider.id),
3580
+ flow: "oauth"
3581
+ })).sort(compareProviderOptions);
3582
+ }
3583
+ function getApiKeyProviderOptions(providerIds) {
3584
+ const baseProviders = providerIds.filter((providerId) => !OAUTH_ONLY_PROVIDER_IDS.has(providerId)).map((providerId) => {
3585
+ let flow = "generic-api-key";
3586
+ if (providerId === "azure-openai-responses") {
3587
+ flow = "azure-openai-responses";
3588
+ } else if (providerId === "amazon-bedrock") {
3589
+ flow = "amazon-bedrock";
3590
+ }
3591
+ return {
3592
+ id: providerId,
3593
+ label: toLabel(providerId),
3594
+ hint: toHint(providerId),
3595
+ flow
3596
+ };
3597
+ });
3598
+ baseProviders.push({
3599
+ id: "custom-openai",
3600
+ label: toLabel("custom-openai"),
3601
+ hint: toHint("custom-openai"),
3602
+ flow: "custom-openai"
3603
+ });
3604
+ const deduped = /* @__PURE__ */ new Map();
3605
+ for (const provider of baseProviders) {
3606
+ deduped.set(provider.id, provider);
3607
+ }
3608
+ return Array.from(deduped.values()).sort(compareProviderOptions);
3609
+ }
3610
+ function getProviderLabel(providerId) {
3611
+ return toLabel(providerId);
3612
+ }
3613
+ function getProviderHint(providerId) {
3614
+ return toHint(providerId);
3615
+ }
3616
+
3617
+ // src/agent/authInteractivity.ts
3618
+ function buildFieldUiHint(field) {
3619
+ return {
3620
+ component: field.component,
3621
+ ...field.component === "password" ? { secret: true } : {},
3622
+ ...field.placeholder ? { placeholder: field.placeholder } : {},
3623
+ ...field.options ? { options: field.options } : {},
3624
+ ...field.dependsOn ? { dependsOn: field.dependsOn } : {}
3625
+ };
3626
+ }
3627
+ function buildProperty(field) {
3628
+ return {
3629
+ type: "string",
3630
+ title: field.title,
3631
+ ...field.description ? { description: field.description } : {},
3632
+ ...field.options ? { enum: field.options.map((option) => option.value) } : {},
3633
+ ...field.defaultValue ? { default: field.defaultValue } : {}
3634
+ };
3635
+ }
3636
+ function toSelectOptions(values) {
3637
+ return values.map((value) => ({ label: value, value }));
3638
+ }
3639
+ function getProviderFormFields(params) {
3640
+ const provider = params.provider;
3641
+ switch (provider.flow) {
3642
+ case "custom-openai":
3643
+ return [
3644
+ {
3645
+ name: "baseUrl",
3646
+ title: "Custom OpenAI base URL",
3647
+ required: true,
3648
+ component: "text",
3649
+ placeholder: "https://my-proxy.example.com/v1"
3650
+ },
3651
+ {
3652
+ name: "apiKey",
3653
+ title: `${provider.label} API key`,
3654
+ required: true,
3655
+ component: "password"
3656
+ },
3657
+ {
3658
+ name: "modelId",
3659
+ title: "Model ID",
3660
+ required: true,
3661
+ component: "text",
3662
+ placeholder: "gpt-4o"
3663
+ }
3664
+ ];
3665
+ case "azure-openai-responses":
3666
+ return [
3667
+ {
3668
+ name: "apiKey",
3669
+ title: "Azure OpenAI API key",
3670
+ required: true,
3671
+ component: "password"
3672
+ },
3673
+ {
3674
+ name: "configMode",
3675
+ title: "Azure OpenAI configuration",
3676
+ required: true,
3677
+ defaultValue: "base-url",
3678
+ component: "select",
3679
+ options: [
3680
+ { label: "Use a base URL", value: "base-url" },
3681
+ { label: "Use an Azure resource name", value: "resource-name" }
3682
+ ]
3683
+ },
3684
+ {
3685
+ name: "baseUrl",
3686
+ title: "Azure OpenAI base URL",
3687
+ required: true,
3688
+ component: "text",
3689
+ placeholder: "https://my-resource.openai.azure.com/openai/v1",
3690
+ dependsOn: { field: "configMode", equals: "base-url" }
3691
+ },
3692
+ {
3693
+ name: "resourceName",
3694
+ title: "Azure resource name",
3695
+ required: true,
3696
+ component: "text",
3697
+ placeholder: "my-resource",
3698
+ dependsOn: { field: "configMode", equals: "resource-name" }
3699
+ },
3700
+ {
3701
+ name: "modelId",
3702
+ title: "Model ID",
3703
+ required: true,
3704
+ component: params.modelIdsByProvider[provider.id]?.length ? "select" : "text",
3705
+ options: params.modelIdsByProvider[provider.id]?.length ? toSelectOptions(params.modelIdsByProvider[provider.id]) : void 0,
3706
+ defaultValue: params.modelIdsByProvider[provider.id]?.[0] || "gpt-4.1"
3707
+ },
3708
+ {
3709
+ name: "deploymentName",
3710
+ title: "Deployment name",
3711
+ component: "text",
3712
+ placeholder: "gpt-4.1"
3713
+ },
3714
+ {
3715
+ name: "apiVersion",
3716
+ title: "API version",
3717
+ component: "text",
3718
+ defaultValue: "2025-03-01-preview",
3719
+ placeholder: "2025-03-01-preview"
3720
+ }
3721
+ ];
3722
+ case "amazon-bedrock":
3723
+ return [
3724
+ {
3725
+ name: "authMode",
3726
+ title: "Amazon Bedrock authentication",
3727
+ required: true,
3728
+ defaultValue: "profile",
3729
+ component: "select",
3730
+ options: [
3731
+ { label: "Use an AWS profile", value: "profile" },
3732
+ { label: "Paste AWS access keys", value: "access-keys" }
3733
+ ]
3734
+ },
3735
+ {
3736
+ name: "profile",
3737
+ title: "AWS profile name",
3738
+ required: true,
3739
+ defaultValue: "default",
3740
+ component: "text",
3741
+ placeholder: "default",
3742
+ dependsOn: { field: "authMode", equals: "profile" }
3743
+ },
3744
+ {
3745
+ name: "accessKeyId",
3746
+ title: "AWS access key ID",
3747
+ required: true,
3748
+ component: "text",
3749
+ dependsOn: { field: "authMode", equals: "access-keys" }
3750
+ },
3751
+ {
3752
+ name: "secretAccessKey",
3753
+ title: "AWS secret access key",
3754
+ required: true,
3755
+ component: "password",
3756
+ dependsOn: { field: "authMode", equals: "access-keys" }
3757
+ },
3758
+ {
3759
+ name: "sessionToken",
3760
+ title: "AWS session token",
3761
+ component: "password",
3762
+ dependsOn: { field: "authMode", equals: "access-keys" }
3763
+ },
3764
+ {
3765
+ name: "region",
3766
+ title: "AWS region",
3767
+ required: true,
3768
+ defaultValue: "us-east-1",
3769
+ component: "text",
3770
+ placeholder: "us-east-1"
3771
+ },
3772
+ {
3773
+ name: "modelId",
3774
+ title: "Bedrock model ID",
3775
+ required: true,
3776
+ component: params.modelIdsByProvider[provider.id]?.length ? "select" : "text",
3777
+ options: params.modelIdsByProvider[provider.id]?.length ? toSelectOptions(params.modelIdsByProvider[provider.id]) : void 0,
3778
+ defaultValue: params.modelIdsByProvider[provider.id]?.[0] || "anthropic.claude-3-7-sonnet-20250219-v1:0"
3779
+ }
3780
+ ];
3781
+ default:
3782
+ return [
3783
+ {
3784
+ name: "apiKey",
3785
+ title: `${provider.label} API key`,
3786
+ required: true,
3787
+ component: "password"
3788
+ }
3789
+ ];
3790
+ }
3791
+ }
3792
+ function buildProviderFormContract(params) {
3793
+ if (params.provider.flow === "oauth") {
3794
+ return null;
3795
+ }
3796
+ const fields = getProviderFormFields(params);
3797
+ const properties = Object.fromEntries(fields.map((field) => [field.name, buildProperty(field)]));
3798
+ const required = fields.filter((field) => field.required).map((field) => field.name);
3799
+ const fieldHints = Object.fromEntries(fields.map((field) => [field.name, buildFieldUiHint(field)]));
3800
+ return {
3801
+ inputSchema: {
3802
+ type: "object",
3803
+ properties,
3804
+ required
3805
+ },
3806
+ uiHints: {
3807
+ submitLabel: "Save credentials",
3808
+ order: fields.map((field) => field.name),
3809
+ fields: fieldHints
3810
+ }
3811
+ };
3812
+ }
3813
+ function getOAuthDescriptor(params, provider) {
3814
+ return {
3815
+ id: provider.id,
3816
+ label: provider.name || getProviderLabel(provider.id),
3817
+ hint: getProviderHint(provider.id),
3818
+ flow: "oauth",
3819
+ authMode: "oauth",
3820
+ configured: params.authStorage.hasAuth(provider.id),
3821
+ canLogin: true,
3822
+ canLogout: params.authStorage.hasAuth(provider.id),
3823
+ inputSchema: null,
3824
+ uiHints: null,
3825
+ oauth: { usesCallbackServer: provider.usesCallbackServer }
3826
+ };
3827
+ }
3828
+ function getApiKeyDescriptor(params, provider) {
3829
+ const contract = buildProviderFormContract({
3830
+ provider,
3831
+ modelIdsByProvider: params.modelIdsByProvider
3832
+ });
3833
+ return {
3834
+ id: provider.id,
3835
+ label: provider.label,
3836
+ hint: provider.hint,
3837
+ flow: provider.flow,
3838
+ authMode: "api-key",
3839
+ configured: params.authStorage.hasAuth(provider.id),
3840
+ canLogin: true,
3841
+ canLogout: params.authStorage.hasAuth(provider.id),
3842
+ inputSchema: contract?.inputSchema ?? null,
3843
+ uiHints: contract?.uiHints ?? null,
3844
+ oauth: null
3845
+ };
3846
+ }
3847
+ function buildProviderCatalog(params) {
3848
+ const oauthProviders = getBrowserAuthProviderOptions(params.browserProviders).map(
3849
+ (provider) => getOAuthDescriptor(
3850
+ params,
3851
+ params.browserProviders.find((item) => item.id === provider.id) ?? { id: provider.id, name: provider.label }
3852
+ )
3853
+ );
3854
+ const apiKeyProviders = getApiKeyProviderOptions(params.apiKeyProviderIds).map((provider) => getApiKeyDescriptor(params, provider));
3855
+ return [...oauthProviders, ...apiKeyProviders].sort((left, right) => left.label.localeCompare(right.label));
3856
+ }
3857
+ function listProviderOptions(params) {
3858
+ return [
3859
+ ...getBrowserAuthProviderOptions(params.browserProviders),
3860
+ ...getApiKeyProviderOptions(params.apiKeyProviderIds)
3861
+ ].sort((left, right) => left.label.localeCompare(right.label));
3862
+ }
3863
+ function findProviderOption(params) {
3864
+ return listProviderOptions({
3865
+ browserProviders: params.browserProviders,
3866
+ apiKeyProviderIds: params.apiKeyProviderIds
3867
+ }).find((provider) => provider.id === params.providerId);
3868
+ }
3869
+ function trimOrUndefined2(value) {
3870
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
3871
+ }
3872
+ function validateProviderLoginInput(params) {
3873
+ const fields = getProviderFormFields({
3874
+ provider: params.provider,
3875
+ modelIdsByProvider: params.modelIdsByProvider
3876
+ });
3877
+ const values = {};
3878
+ for (const field of fields) {
3879
+ if (field.dependsOn) {
3880
+ const dependsOnValue = values[field.dependsOn.field] ?? trimOrUndefined2(params.value[field.dependsOn.field]);
3881
+ if (dependsOnValue !== field.dependsOn.equals) {
3882
+ continue;
3883
+ }
3884
+ }
3885
+ const rawValue = params.value[field.name];
3886
+ const normalized = trimOrUndefined2(rawValue);
3887
+ if (field.required && !normalized) {
3888
+ return { ok: false, error: `Missing required field: ${field.name}` };
3889
+ }
3890
+ if (normalized) {
3891
+ values[field.name] = normalized;
3892
+ } else if (field.defaultValue) {
3893
+ values[field.name] = field.defaultValue;
3894
+ }
3895
+ }
3896
+ return { ok: true, value: values };
3897
+ }
3898
+ async function applyProviderLogin(params) {
3899
+ const provider = params.provider;
3900
+ switch (provider.flow) {
3901
+ case "custom-openai": {
3902
+ await saveCustomOpenAiConfig({
3903
+ ...params,
3904
+ apiKey: params.input.apiKey,
3905
+ baseUrl: params.input.baseUrl,
3906
+ modelId: params.input.modelId
3907
+ });
3908
+ return { providerId: provider.id, preferredModelId: params.input.modelId };
3909
+ }
3910
+ case "azure-openai-responses": {
3911
+ const baseUrl = params.input.baseUrl ?? (params.input.resourceName ? `https://${params.input.resourceName}.openai.azure.com/openai/v1` : void 0);
3912
+ if (!baseUrl) {
3913
+ throw new Error("Azure login requires either baseUrl or resourceName.");
3914
+ }
3915
+ const builtInModelIds = new Set(params.modelIdsByProvider[provider.id] ?? []);
3916
+ await saveAzureConfig({
3917
+ ...params,
3918
+ apiKey: params.input.apiKey,
3919
+ baseUrl,
3920
+ modelId: params.input.modelId,
3921
+ deploymentName: params.input.deploymentName,
3922
+ apiVersion: params.input.apiVersion,
3923
+ useCustomModel: !builtInModelIds.has(params.input.modelId)
3924
+ });
3925
+ return { providerId: provider.id, preferredModelId: params.input.modelId };
3926
+ }
3927
+ case "amazon-bedrock": {
3928
+ const authMode = params.input.authMode ?? "profile";
3929
+ await saveBedrockConfig({
3930
+ ...params,
3931
+ region: params.input.region,
3932
+ modelId: params.input.modelId,
3933
+ profile: authMode === "profile" ? params.input.profile : void 0,
3934
+ accessKeyId: authMode === "access-keys" ? params.input.accessKeyId : void 0,
3935
+ secretAccessKey: authMode === "access-keys" ? params.input.secretAccessKey : void 0,
3936
+ sessionToken: authMode === "access-keys" ? params.input.sessionToken : void 0
3937
+ });
3938
+ return { providerId: provider.id, preferredModelId: params.input.modelId };
3939
+ }
3940
+ default: {
3941
+ await saveGenericApiKey({
3942
+ ...params,
3943
+ providerId: provider.id,
3944
+ apiKey: params.input.apiKey
3945
+ });
3946
+ return { providerId: provider.id };
3947
+ }
3948
+ }
3949
+ }
3950
+ async function refreshSessionAfterCredentialChange(params) {
3951
+ params.modelRegistry.refresh();
3952
+ const availableModels = params.modelRegistry.getAvailable();
3953
+ const preferredModel = params.preferredProviderId && params.preferredModelId ? availableModels.find((model) => model.provider === params.preferredProviderId && model.id === params.preferredModelId) : params.preferredProviderId ? availableModels.find((model) => model.provider === params.preferredProviderId) : void 0;
3954
+ const currentModel = params.session.model;
3955
+ const currentModelStillAvailable = currentModel ? availableModels.some((model) => model.provider === currentModel.provider && model.id === currentModel.id) : false;
3956
+ if (currentModel && !currentModelStillAvailable) {
3957
+ if (preferredModel) {
3958
+ await params.session.setModel(preferredModel);
3959
+ return;
3960
+ }
3961
+ if (availableModels[0]) {
3962
+ await params.session.setModel(availableModels[0]);
3963
+ }
3964
+ return;
3965
+ }
3966
+ if (!currentModel && preferredModel) {
3967
+ await params.session.setModel(preferredModel);
3968
+ return;
3969
+ }
3970
+ if (!currentModel && availableModels[0]) {
3971
+ await params.session.setModel(availableModels[0]);
3972
+ }
3973
+ }
3974
+ function serializeModels(params) {
3975
+ return params.models.map((model) => ({
3976
+ provider: model.provider,
3977
+ providerLabel: getProviderLabel(model.provider),
3978
+ id: model.id,
3979
+ name: typeof model.name === "string" && model.name.length > 0 ? model.name : model.id,
3980
+ reasoning: model.reasoning === true,
3981
+ contextWindow: typeof model.contextWindow === "number" ? model.contextWindow : null,
3982
+ maxTokens: typeof model.maxTokens === "number" ? model.maxTokens : null,
3983
+ configured: params.authStorage.hasAuth(model.provider)
3984
+ }));
3985
+ }
3986
+
3987
+ // src/server/oauthFlowManager.ts
3988
+ var import_node_crypto = require("node:crypto");
3989
+ function normalizeError(error) {
3990
+ return error instanceof Error ? error.message : String(error);
3991
+ }
3992
+ function buildPromptStep(params) {
3993
+ return {
3994
+ type: params.type,
3995
+ stepToken: params.state.stepToken,
3996
+ providerId: params.state.providerId,
3997
+ message: params.prompt.message,
3998
+ inputSchema: {
3999
+ type: "object",
4000
+ properties: {
4001
+ value: {
4002
+ type: "string",
4003
+ title: params.type === "manual-code" ? "Verification Code" : "Value",
4004
+ ...params.prompt.placeholder ? { default: params.prompt.placeholder } : {}
4005
+ }
4006
+ },
4007
+ required: params.prompt.allowEmpty ? [] : ["value"]
4008
+ },
4009
+ uiHints: {
4010
+ submitLabel: "Continue",
4011
+ order: ["value"],
4012
+ fields: {
4013
+ value: {
4014
+ component: "text",
4015
+ ...params.prompt.placeholder ? { placeholder: params.prompt.placeholder } : {}
4016
+ }
4017
+ }
4018
+ }
4019
+ };
4020
+ }
4021
+ function buildAuthStep(params) {
4022
+ return {
4023
+ type: "open-url",
4024
+ stepToken: params.state.stepToken,
4025
+ providerId: params.state.providerId,
4026
+ url: params.authInfo.url,
4027
+ instructions: params.authInfo.instructions,
4028
+ usesCallbackServer: params.state.usesCallbackServer,
4029
+ pending: true
4030
+ };
4031
+ }
4032
+ var OAuthFlowManager = class {
4033
+ flows = /* @__PURE__ */ new Map();
4034
+ emitStep(state, step) {
4035
+ state.currentStep = step;
4036
+ if (state.waitingForStep) {
4037
+ const waiting = state.waitingForStep;
4038
+ state.waitingForStep = void 0;
4039
+ waiting.resolve(step);
4040
+ return;
4041
+ }
4042
+ state.queuedSteps.push(step);
4043
+ }
4044
+ nextStep(state) {
4045
+ const queued = state.queuedSteps.shift();
4046
+ if (queued) {
4047
+ return Promise.resolve(queued);
4048
+ }
4049
+ if (state.currentStep) {
4050
+ return Promise.resolve(state.currentStep);
4051
+ }
4052
+ return new Promise((resolve2) => {
4053
+ state.waitingForStep = { resolve: resolve2 };
4054
+ });
4055
+ }
4056
+ async start(params) {
4057
+ const state = {
4058
+ stepToken: (0, import_node_crypto.randomUUID)(),
4059
+ providerId: params.providerId,
4060
+ usesCallbackServer: params.usesCallbackServer,
4061
+ pendingInput: void 0,
4062
+ currentStep: void 0,
4063
+ queuedSteps: [],
4064
+ completed: false
4065
+ };
4066
+ this.flows.set(state.stepToken, state);
4067
+ void params.authStorage.login(params.providerId, {
4068
+ onAuth: (info) => {
4069
+ this.emitStep(state, buildAuthStep({ state, authInfo: info }));
4070
+ },
4071
+ onPrompt: async (prompt) => {
4072
+ const step = buildPromptStep({ state, type: "prompt", prompt });
4073
+ this.emitStep(state, step);
4074
+ return await new Promise((resolve2) => {
4075
+ state.pendingInput = { type: "prompt", resolve: resolve2 };
4076
+ });
4077
+ },
4078
+ onManualCodeInput: async () => {
4079
+ const step = buildPromptStep({
4080
+ state,
4081
+ type: "manual-code",
4082
+ prompt: {
4083
+ message: "Enter the verification code from the provider."
4084
+ }
4085
+ });
4086
+ this.emitStep(state, step);
4087
+ return await new Promise((resolve2) => {
4088
+ state.pendingInput = { type: "manual-code", resolve: resolve2 };
4089
+ });
4090
+ },
4091
+ onProgress: (_message) => {
4092
+ }
4093
+ }).then(() => {
4094
+ state.completed = true;
4095
+ this.emitStep(state, {
4096
+ type: "complete",
4097
+ stepToken: state.stepToken,
4098
+ providerId: state.providerId
4099
+ });
4100
+ }).catch((error) => {
4101
+ state.completed = true;
4102
+ this.emitStep(state, {
4103
+ type: "error",
4104
+ stepToken: state.stepToken,
4105
+ providerId: state.providerId,
4106
+ error: normalizeError(error)
4107
+ });
4108
+ });
4109
+ return await this.nextStep(state);
4110
+ }
4111
+ async continue(params) {
4112
+ const state = this.flows.get(params.stepToken);
4113
+ if (!state) {
4114
+ throw new Error(`Unknown OAuth step token: ${params.stepToken}`);
4115
+ }
4116
+ if (state.pendingInput) {
4117
+ const nextValue = params.value?.trim();
4118
+ state.pendingInput.resolve(nextValue ?? "");
4119
+ state.pendingInput = void 0;
4120
+ state.currentStep = void 0;
4121
+ }
4122
+ const step = await this.nextStep(state);
4123
+ if (step.type === "complete" || step.type === "error") {
4124
+ this.flows.delete(state.stepToken);
4125
+ }
4126
+ return step;
4127
+ }
4128
+ };
4129
+
3053
4130
  // src/server/agentServer.ts
3054
4131
  function generateMessageId() {
3055
4132
  return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
@@ -3069,6 +4146,40 @@ function extractLastUserText(messages) {
3069
4146
  }
3070
4147
  return void 0;
3071
4148
  }
4149
+ function extractAskUserToolResponse(messages) {
4150
+ for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
4151
+ const message = messages[messageIndex];
4152
+ if (message.role !== "assistant") {
4153
+ continue;
4154
+ }
4155
+ for (let partIndex = message.parts.length - 1; partIndex >= 0; partIndex -= 1) {
4156
+ const part = message.parts[partIndex];
4157
+ if (part.type === "dynamic-tool" && part.toolName === "ask_user" && part.state === "output-available" && typeof part.toolCallId === "string") {
4158
+ const response = parseAskUserResponseFromToolOutput(part.output);
4159
+ if (response) {
4160
+ return {
4161
+ toolCallId: part.toolCallId,
4162
+ response
4163
+ };
4164
+ }
4165
+ }
4166
+ }
4167
+ }
4168
+ return void 0;
4169
+ }
4170
+ function normalizeSlashCommands(commands) {
4171
+ const seen = /* @__PURE__ */ new Set();
4172
+ return commands.filter((command) => typeof command.name === "string" && command.name.trim().length > 0).sort((left, right) => left.name.localeCompare(right.name)).filter((command) => {
4173
+ if (seen.has(command.name)) {
4174
+ return false;
4175
+ }
4176
+ seen.add(command.name);
4177
+ return true;
4178
+ }).map((command) => ({
4179
+ name: command.name,
4180
+ description: command.description?.trim() || null
4181
+ }));
4182
+ }
3072
4183
  async function waitForIdle(session, timeoutMs = 3e4) {
3073
4184
  if (!session.isStreaming) {
3074
4185
  return;
@@ -3092,6 +4203,96 @@ async function waitForIdle(session, timeoutMs = 3e4) {
3092
4203
  }
3093
4204
  });
3094
4205
  }
4206
+ function convertSessionEntriesToUIMessages(entries) {
4207
+ const messages = [];
4208
+ const toolResults = /* @__PURE__ */ new Map();
4209
+ for (const entry of entries) {
4210
+ const rec = entry;
4211
+ if (rec.type !== "message") {
4212
+ continue;
4213
+ }
4214
+ const msg = rec.message;
4215
+ if (!msg || msg.role !== "toolResult") {
4216
+ continue;
4217
+ }
4218
+ const toolCallId = msg.toolCallId;
4219
+ if (toolCallId) {
4220
+ const content = msg.content;
4221
+ const textParts = Array.isArray(content) ? content.filter((b) => b.type === "text").map((b) => b.text).join("\n") : typeof content === "string" ? content : "";
4222
+ toolResults.set(toolCallId, {
4223
+ output: textParts,
4224
+ isError: msg.isError ?? false
4225
+ });
4226
+ }
4227
+ }
4228
+ for (const entry of entries) {
4229
+ const rec = entry;
4230
+ if (rec.type !== "message") {
4231
+ continue;
4232
+ }
4233
+ const msg = rec.message;
4234
+ if (!msg) {
4235
+ continue;
4236
+ }
4237
+ const entryId = rec.id ?? `entry_${messages.length}`;
4238
+ if (msg.role === "user") {
4239
+ const content = msg.content;
4240
+ let text = "";
4241
+ if (typeof content === "string") {
4242
+ text = content;
4243
+ } else if (Array.isArray(content)) {
4244
+ text = content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
4245
+ }
4246
+ if (text) {
4247
+ messages.push({ id: entryId, role: "user", parts: [{ type: "text", text }] });
4248
+ }
4249
+ continue;
4250
+ }
4251
+ if (msg.role === "assistant") {
4252
+ const content = msg.content;
4253
+ if (!Array.isArray(content)) {
4254
+ continue;
4255
+ }
4256
+ const parts = [];
4257
+ for (const block of content) {
4258
+ if (block.type === "text" && typeof block.text === "string") {
4259
+ const askUserRequest = parseAskUserRequestFromText(block.text);
4260
+ if (askUserRequest) {
4261
+ parts.push({
4262
+ type: "dynamic-tool",
4263
+ toolCallId: createAskUserToolCallId(askUserRequest),
4264
+ toolName: "ask_user",
4265
+ state: "input-available",
4266
+ input: askUserRequest
4267
+ });
4268
+ continue;
4269
+ }
4270
+ parts.push({ type: "text", text: block.text });
4271
+ } else if (block.type === "thinking" && typeof block.thinking === "string") {
4272
+ parts.push({ type: "reasoning", text: block.thinking, state: "complete" });
4273
+ } else if (block.type === "toolCall") {
4274
+ const toolCallId = block.id;
4275
+ const toolName = block.name;
4276
+ const input = block.arguments ?? {};
4277
+ const result = toolResults.get(toolCallId);
4278
+ parts.push({
4279
+ type: "dynamic-tool",
4280
+ toolCallId,
4281
+ toolName,
4282
+ input,
4283
+ state: result ? result.isError ? "output-error" : "output-available" : "input-available",
4284
+ ...result && !result.isError ? { output: result.output } : {},
4285
+ ...result && result.isError ? { errorText: String(result.output) } : {}
4286
+ });
4287
+ }
4288
+ }
4289
+ if (parts.length > 0) {
4290
+ messages.push({ id: entryId, role: "assistant", parts });
4291
+ }
4292
+ }
4293
+ }
4294
+ return messages;
4295
+ }
3095
4296
  var FS_IGNORE = /* @__PURE__ */ new Set([
3096
4297
  "node_modules",
3097
4298
  ".git",
@@ -3110,8 +4311,8 @@ var FS_IGNORE = /* @__PURE__ */ new Set([
3110
4311
  ".svelte-kit"
3111
4312
  ]);
3112
4313
  function resolveSafePath(cwd, requestPath) {
3113
- const resolved = (0, import_node_path2.resolve)(cwd, requestPath);
3114
- const normalizedCwd = cwd.endsWith(import_node_path2.sep) ? cwd : cwd + import_node_path2.sep;
4314
+ const resolved = (0, import_node_path3.resolve)(cwd, requestPath);
4315
+ const normalizedCwd = cwd.endsWith(import_node_path3.sep) ? cwd : cwd + import_node_path3.sep;
3115
4316
  if (resolved !== cwd && !resolved.startsWith(normalizedCwd)) {
3116
4317
  throw new Error("Path escapes working directory");
3117
4318
  }
@@ -3128,7 +4329,7 @@ async function buildTree(params) {
3128
4329
  }
3129
4330
  let entries;
3130
4331
  try {
3131
- entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
4332
+ entries = await (0, import_promises3.readdir)(dir, { withFileTypes: true });
3132
4333
  } catch {
3133
4334
  return [];
3134
4335
  }
@@ -3148,8 +4349,8 @@ async function buildTree(params) {
3148
4349
  if (ignore.has(entry.name)) {
3149
4350
  continue;
3150
4351
  }
3151
- const fullPath = (0, import_node_path2.join)(dir, entry.name);
3152
- const relativePath = (0, import_node_path2.relative)(cwd, fullPath);
4352
+ const fullPath = (0, import_node_path3.join)(dir, entry.name);
4353
+ const relativePath = (0, import_node_path3.relative)(cwd, fullPath);
3153
4354
  if (entry.isDirectory()) {
3154
4355
  const children = await buildTree({
3155
4356
  dir: fullPath,
@@ -3175,7 +4376,7 @@ async function walkFiles(params) {
3175
4376
  }
3176
4377
  let entries;
3177
4378
  try {
3178
- entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
4379
+ entries = await (0, import_promises3.readdir)(dir, { withFileTypes: true });
3179
4380
  } catch {
3180
4381
  return;
3181
4382
  }
@@ -3186,8 +4387,8 @@ async function walkFiles(params) {
3186
4387
  if (ignore.has(entry.name)) {
3187
4388
  continue;
3188
4389
  }
3189
- const fullPath = (0, import_node_path2.join)(dir, entry.name);
3190
- const relativePath = (0, import_node_path2.relative)(cwd, fullPath);
4390
+ const fullPath = (0, import_node_path3.join)(dir, entry.name);
4391
+ const relativePath = (0, import_node_path3.relative)(cwd, fullPath);
3191
4392
  if (entry.isDirectory()) {
3192
4393
  await walkFiles({ dir: fullPath, cwd, pattern, ignore, maxResults, results });
3193
4394
  } else if (entry.isFile() && pattern.test(relativePath)) {
@@ -3196,9 +4397,32 @@ async function walkFiles(params) {
3196
4397
  }
3197
4398
  }
3198
4399
  async function createAgentServer(params) {
3199
- const { port, sessionManager, modelRegistry, context, onResumeSession } = params;
4400
+ const { port, sessionManager, modelRegistry, authRuntime, context, onCreateSession, onResumeSession } = params;
3200
4401
  let activeSession = params.session;
4402
+ const pendingAskUserRequests = /* @__PURE__ */ new Map();
4403
+ const oauthFlowManager = new OAuthFlowManager();
3201
4404
  const app = new Hono2();
4405
+ function getModelIdsByProvider() {
4406
+ const values = {};
4407
+ for (const model of modelRegistry.getAll()) {
4408
+ if (!values[model.provider]) {
4409
+ values[model.provider] = [];
4410
+ }
4411
+ values[model.provider].push(model.id);
4412
+ }
4413
+ for (const providerId of Object.keys(values)) {
4414
+ values[providerId] = [...new Set(values[providerId])].sort((left, right) => left.localeCompare(right));
4415
+ }
4416
+ return values;
4417
+ }
4418
+ function getProviderCatalog() {
4419
+ return buildProviderCatalog({
4420
+ authStorage: authRuntime.authStorage,
4421
+ browserProviders: authRuntime.authStorage.getOAuthProviders(),
4422
+ apiKeyProviderIds: [...new Set(modelRegistry.getAll().map((model) => model.provider))].sort((left, right) => left.localeCompare(right)),
4423
+ modelIdsByProvider: getModelIdsByProvider()
4424
+ });
4425
+ }
3202
4426
  app.use("/*", cors({ origin: "*" }));
3203
4427
  app.get("/api/health", (c) => {
3204
4428
  return c.json({ ok: true });
@@ -3212,9 +4436,23 @@ async function createAgentServer(params) {
3212
4436
  app.post("/api/chat", async (c) => {
3213
4437
  const body = await c.req.json();
3214
4438
  const messages = body.messages ?? [];
3215
- const userMessage = extractLastUserText(messages);
4439
+ if (body.sessionId) {
4440
+ try {
4441
+ activeSession = await onResumeSession(body.sessionId);
4442
+ } catch (error) {
4443
+ const msg = error instanceof Error ? error.message : String(error);
4444
+ return c.json({ error: `Failed to resume session: ${msg}` }, 400);
4445
+ }
4446
+ }
4447
+ const sessionId = body.sessionId?.trim() || activeSession.id?.trim() || "active";
4448
+ const askUserResponse = extractAskUserToolResponse(messages);
4449
+ const pendingAskUser = pendingAskUserRequests.get(sessionId);
4450
+ const userMessage = askUserResponse ? pendingAskUser && pendingAskUser.toolCallId === askUserResponse.toolCallId ? formatAskUserResponsePrompt(askUserResponse.response) : void 0 : extractLastUserText(messages);
3216
4451
  if (!userMessage) {
3217
- return c.json({ error: "No user message found" }, 400);
4452
+ return c.json({ error: askUserResponse ? "No matching pending ask_user request found" : "No user message found" }, 400);
4453
+ }
4454
+ if (askUserResponse) {
4455
+ pendingAskUserRequests.delete(sessionId);
3218
4456
  }
3219
4457
  if (activeSession.isStreaming) {
3220
4458
  await activeSession.abort();
@@ -3234,6 +4472,9 @@ async function createAgentServer(params) {
3234
4472
  const bridge = createEventBridge({
3235
4473
  messageId,
3236
4474
  onChunk: writeChunk,
4475
+ onAskUser: ({ toolCallId, request }) => {
4476
+ pendingAskUserRequests.set(sessionId, { toolCallId, request });
4477
+ },
3237
4478
  onDone: () => {
3238
4479
  writeChunk({ type: "finish-step" });
3239
4480
  writeChunk({ type: "finish" });
@@ -3252,7 +4493,14 @@ async function createAgentServer(params) {
3252
4493
  unsubscribe();
3253
4494
  }
3254
4495
  });
3255
- activeSession.prompt(userMessage).catch((error) => {
4496
+ activeSession.prompt(userMessage).then(() => {
4497
+ if (!activeSession.isStreaming) {
4498
+ unsubscribe();
4499
+ writeChunk({ type: "finish-step" });
4500
+ writeChunk({ type: "finish" });
4501
+ controller.close();
4502
+ }
4503
+ }).catch((error) => {
3256
4504
  const errorMessage = error instanceof Error ? error.message : String(error);
3257
4505
  writeChunk({ type: "error", errorText: errorMessage });
3258
4506
  writeChunk({ type: "finish-step" });
@@ -3291,6 +4539,31 @@ async function createAgentServer(params) {
3291
4539
  return c.json({ error: message }, 500);
3292
4540
  }
3293
4541
  });
4542
+ app.post("/api/sessions", async (c) => {
4543
+ try {
4544
+ if (activeSession.isStreaming) {
4545
+ await activeSession.abort();
4546
+ await waitForIdle(activeSession);
4547
+ }
4548
+ activeSession = await onCreateSession();
4549
+ const sessionId = activeSession.id?.trim();
4550
+ if (!sessionId) {
4551
+ return c.json({ error: "Created session is missing an id" }, 500);
4552
+ }
4553
+ const sessions = await sessionManager.list();
4554
+ const match2 = sessions.find((session) => session.id === sessionId);
4555
+ return c.json({
4556
+ ok: true,
4557
+ sessionId,
4558
+ sessionName: match2?.name ?? null,
4559
+ cwd: match2?.cwd ?? context.cwd,
4560
+ model: activeSession.model ? { provider: activeSession.model.provider, id: activeSession.model.id } : null
4561
+ });
4562
+ } catch (error) {
4563
+ const message = error instanceof Error ? error.message : String(error);
4564
+ return c.json({ error: message }, 500);
4565
+ }
4566
+ });
3294
4567
  app.get("/api/sessions/:sessionId/messages", async (c) => {
3295
4568
  const sessionId = c.req.param("sessionId");
3296
4569
  try {
@@ -3301,14 +4574,15 @@ async function createAgentServer(params) {
3301
4574
  }
3302
4575
  const opened = sessionManager.open(match2.path);
3303
4576
  const sessionContext = opened.buildSessionContext();
3304
- const entries = opened.getBranch();
4577
+ const rawEntries = opened.getBranch();
4578
+ const messages = convertSessionEntriesToUIMessages(rawEntries);
3305
4579
  return c.json({
3306
4580
  sessionId: opened.getSessionId(),
3307
4581
  sessionName: opened.getSessionName() ?? null,
3308
4582
  cwd: opened.getCwd(),
3309
4583
  model: sessionContext.model ?? null,
3310
4584
  thinkingLevel: sessionContext.thinkingLevel ?? null,
3311
- entries
4585
+ messages
3312
4586
  });
3313
4587
  } catch (error) {
3314
4588
  const message = error instanceof Error ? error.message : String(error);
@@ -3328,8 +4602,161 @@ async function createAgentServer(params) {
3328
4602
  app.get("/api/models", (c) => {
3329
4603
  return c.json({
3330
4604
  current: activeSession.model ? { provider: activeSession.model.provider, id: activeSession.model.id } : null,
3331
- available: modelRegistry.getAvailable()
4605
+ thinkingLevel: activeSession.thinkingLevel,
4606
+ availableThinkingLevels: activeSession.getAvailableThinkingLevels(),
4607
+ available: serializeModels({
4608
+ models: modelRegistry.getAvailable(),
4609
+ authStorage: authRuntime.authStorage
4610
+ })
4611
+ });
4612
+ });
4613
+ app.get("/api/slash-commands", (c) => {
4614
+ return c.json({
4615
+ commands: normalizeSlashCommands(activeSession.listCommands())
4616
+ });
4617
+ });
4618
+ app.get("/api/auth/providers", (c) => {
4619
+ return c.json({
4620
+ providers: getProviderCatalog()
4621
+ });
4622
+ });
4623
+ app.post("/api/auth/providers/:providerId/login", async (c) => {
4624
+ const providerId = c.req.param("providerId");
4625
+ const provider = findProviderOption({
4626
+ providerId,
4627
+ browserProviders: authRuntime.authStorage.getOAuthProviders(),
4628
+ apiKeyProviderIds: [...new Set(modelRegistry.getAll().map((model) => model.provider))]
4629
+ });
4630
+ if (!provider) {
4631
+ return c.json({ error: `Unknown provider: ${providerId}` }, 404);
4632
+ }
4633
+ if (provider.flow === "oauth") {
4634
+ const step = await oauthFlowManager.start({
4635
+ authStorage: authRuntime.authStorage,
4636
+ providerId,
4637
+ usesCallbackServer: authRuntime.authStorage.getOAuthProviders().find((item) => item.id === providerId)?.usesCallbackServer
4638
+ });
4639
+ return c.json({ step });
4640
+ }
4641
+ const body = await c.req.json();
4642
+ const validated = validateProviderLoginInput({
4643
+ provider,
4644
+ modelIdsByProvider: getModelIdsByProvider(),
4645
+ value: body
4646
+ });
4647
+ if (!validated.ok) {
4648
+ return c.json({ error: validated.error }, 400);
4649
+ }
4650
+ try {
4651
+ const result = await applyProviderLogin({
4652
+ ...authRuntime,
4653
+ provider,
4654
+ input: validated.value,
4655
+ modelIdsByProvider: getModelIdsByProvider()
4656
+ });
4657
+ await refreshSessionAfterCredentialChange({
4658
+ session: activeSession,
4659
+ modelRegistry,
4660
+ preferredProviderId: result.providerId,
4661
+ preferredModelId: result.preferredModelId
4662
+ });
4663
+ return c.json({
4664
+ ok: true,
4665
+ providerId,
4666
+ current: activeSession.model ? { provider: activeSession.model.provider, id: activeSession.model.id } : null,
4667
+ thinkingLevel: activeSession.thinkingLevel
4668
+ });
4669
+ } catch (error) {
4670
+ const message = error instanceof Error ? error.message : String(error);
4671
+ return c.json({ error: message }, 400);
4672
+ }
4673
+ });
4674
+ app.post("/api/auth/providers/:providerId/oauth/continue", async (c) => {
4675
+ const providerId = c.req.param("providerId");
4676
+ const body = await c.req.json();
4677
+ if (!body.stepToken) {
4678
+ return c.json({ error: "Missing required field: stepToken" }, 400);
4679
+ }
4680
+ try {
4681
+ const value = typeof body.values?.value === "string" ? body.values.value : void 0;
4682
+ const step = await oauthFlowManager.continue({
4683
+ stepToken: body.stepToken,
4684
+ value
4685
+ });
4686
+ if (step.type === "complete") {
4687
+ modelRegistry.refresh();
4688
+ await refreshSessionAfterCredentialChange({
4689
+ session: activeSession,
4690
+ modelRegistry,
4691
+ preferredProviderId: providerId
4692
+ });
4693
+ }
4694
+ return c.json({ step });
4695
+ } catch (error) {
4696
+ const message = error instanceof Error ? error.message : String(error);
4697
+ return c.json({ error: message }, 400);
4698
+ }
4699
+ });
4700
+ app.post("/api/auth/providers/:providerId/logout", async (c) => {
4701
+ const providerId = c.req.param("providerId");
4702
+ const provider = findProviderOption({
4703
+ providerId,
4704
+ browserProviders: authRuntime.authStorage.getOAuthProviders(),
4705
+ apiKeyProviderIds: [...new Set(modelRegistry.getAll().map((model) => model.provider))]
3332
4706
  });
4707
+ if (!provider) {
4708
+ return c.json({ error: `Unknown provider: ${providerId}` }, 404);
4709
+ }
4710
+ try {
4711
+ if (provider.flow === "oauth") {
4712
+ authRuntime.authStorage.logout(providerId);
4713
+ } else {
4714
+ await clearProviderConfig({
4715
+ ...authRuntime,
4716
+ providerId
4717
+ });
4718
+ }
4719
+ modelRegistry.refresh();
4720
+ await refreshSessionAfterCredentialChange({
4721
+ session: activeSession,
4722
+ modelRegistry
4723
+ });
4724
+ return c.json({
4725
+ ok: true,
4726
+ providerId,
4727
+ current: activeSession.model ? { provider: activeSession.model.provider, id: activeSession.model.id } : null,
4728
+ thinkingLevel: activeSession.thinkingLevel
4729
+ });
4730
+ } catch (error) {
4731
+ const message = error instanceof Error ? error.message : String(error);
4732
+ return c.json({ error: message }, 400);
4733
+ }
4734
+ });
4735
+ app.post("/api/models/select", async (c) => {
4736
+ const body = await c.req.json();
4737
+ const provider = body.provider?.trim();
4738
+ const modelId = body.modelId?.trim();
4739
+ if (!provider || !modelId) {
4740
+ return c.json({ error: "Missing required fields: provider, modelId" }, 400);
4741
+ }
4742
+ const model = modelRegistry.find(provider, modelId);
4743
+ if (!model) {
4744
+ return c.json({ error: `Model not found: ${provider}/${modelId}` }, 404);
4745
+ }
4746
+ try {
4747
+ await activeSession.setModel(model);
4748
+ if (body.thinkingLevel) {
4749
+ activeSession.setThinkingLevel(body.thinkingLevel);
4750
+ }
4751
+ return c.json({
4752
+ current: activeSession.model ? { provider: activeSession.model.provider, id: activeSession.model.id } : null,
4753
+ thinkingLevel: activeSession.thinkingLevel,
4754
+ availableThinkingLevels: activeSession.getAvailableThinkingLevels()
4755
+ });
4756
+ } catch (error) {
4757
+ const message = error instanceof Error ? error.message : String(error);
4758
+ return c.json({ error: message }, 400);
4759
+ }
3333
4760
  });
3334
4761
  app.post("/api/sessions/:sessionId/resume", async (c) => {
3335
4762
  const sessionId = c.req.param("sessionId");
@@ -3395,11 +4822,11 @@ async function createAgentServer(params) {
3395
4822
  }
3396
4823
  try {
3397
4824
  const resolved = resolveSafePath(context.cwd, filePath);
3398
- const fileStat = await (0, import_promises2.stat)(resolved);
4825
+ const fileStat = await (0, import_promises3.stat)(resolved);
3399
4826
  if (!fileStat.isFile()) {
3400
4827
  return c.json({ error: "Path is not a file" }, 400);
3401
4828
  }
3402
- const content = await (0, import_promises2.readFile)(resolved, "utf-8");
4829
+ const content = await (0, import_promises3.readFile)(resolved, "utf-8");
3403
4830
  return c.json({
3404
4831
  path: filePath,
3405
4832
  content,
@@ -3419,9 +4846,9 @@ async function createAgentServer(params) {
3419
4846
  }
3420
4847
  try {
3421
4848
  const resolved = resolveSafePath(context.cwd, body.path);
3422
- await (0, import_promises2.mkdir)((0, import_node_path2.join)(resolved, ".."), { recursive: true });
3423
- await (0, import_promises2.writeFile)(resolved, body.content, "utf-8");
3424
- const fileStat = await (0, import_promises2.stat)(resolved);
4849
+ await (0, import_promises3.mkdir)((0, import_node_path3.join)(resolved, ".."), { recursive: true });
4850
+ await (0, import_promises3.writeFile)(resolved, body.content, "utf-8");
4851
+ const fileStat = await (0, import_promises3.stat)(resolved);
3425
4852
  return c.json({ ok: true, path: body.path, size: fileStat.size });
3426
4853
  } catch (error) {
3427
4854
  const message = error instanceof Error ? error.message : String(error);
@@ -3435,7 +4862,7 @@ async function createAgentServer(params) {
3435
4862
  }
3436
4863
  try {
3437
4864
  const resolved = resolveSafePath(context.cwd, body.path);
3438
- await (0, import_promises2.mkdir)(resolved, { recursive: true });
4865
+ await (0, import_promises3.mkdir)(resolved, { recursive: true });
3439
4866
  return c.json({ ok: true, path: body.path });
3440
4867
  } catch (error) {
3441
4868
  const message = error instanceof Error ? error.message : String(error);
@@ -3450,8 +4877,8 @@ async function createAgentServer(params) {
3450
4877
  try {
3451
4878
  const resolvedFrom = resolveSafePath(context.cwd, body.from);
3452
4879
  const resolvedTo = resolveSafePath(context.cwd, body.to);
3453
- await (0, import_promises2.mkdir)((0, import_node_path2.join)(resolvedTo, ".."), { recursive: true });
3454
- await (0, import_promises2.rename)(resolvedFrom, resolvedTo);
4880
+ await (0, import_promises3.mkdir)((0, import_node_path3.join)(resolvedTo, ".."), { recursive: true });
4881
+ await (0, import_promises3.rename)(resolvedFrom, resolvedTo);
3455
4882
  return c.json({ ok: true, from: body.from, to: body.to });
3456
4883
  } catch (error) {
3457
4884
  const message = error instanceof Error ? error.message : String(error);
@@ -3469,8 +4896,8 @@ async function createAgentServer(params) {
3469
4896
  if (resolved === context.cwd) {
3470
4897
  return c.json({ error: "Cannot delete working directory" }, 400);
3471
4898
  }
3472
- const fileStat = await (0, import_promises2.stat)(resolved);
3473
- await (0, import_promises2.rm)(resolved, { recursive: fileStat.isDirectory() });
4899
+ const fileStat = await (0, import_promises3.stat)(resolved);
4900
+ await (0, import_promises3.rm)(resolved, { recursive: fileStat.isDirectory() });
3474
4901
  return c.json({ ok: true, path: filePath });
3475
4902
  } catch (error) {
3476
4903
  const message = error instanceof Error ? error.message : String(error);
@@ -3478,6 +4905,42 @@ async function createAgentServer(params) {
3478
4905
  return c.json({ error: message }, status);
3479
4906
  }
3480
4907
  });
4908
+ const CLI_EXEC = process.env.DOCYRUS_CLI_EXECUTABLE || process.execPath;
4909
+ const CLI_ENTRY = process.env.DOCYRUS_CLI_ENTRY || (0, import_node_path3.resolve)(process.argv[1] ? (0, import_node_path3.join)(process.argv[1], "..") : __dirname, "main.js");
4910
+ const CLI_SCOPE = process.env.DOCYRUS_CLI_SCOPE;
4911
+ const CLI_TIMEOUT_MS = 3e4;
4912
+ function runCliCommand(args) {
4913
+ return new Promise((resolveResult) => {
4914
+ const scopeArgs = CLI_SCOPE ? ["--scope", CLI_SCOPE] : [];
4915
+ const proc = (0, import_node_child_process.spawn)(CLI_EXEC, [CLI_ENTRY, ...scopeArgs, ...args, "--json"], {
4916
+ cwd: context.cwd,
4917
+ stdio: ["ignore", "pipe", "pipe"],
4918
+ timeout: CLI_TIMEOUT_MS
4919
+ });
4920
+ let stdout = "";
4921
+ let stderr = "";
4922
+ proc.stdout?.on("data", (chunk) => {
4923
+ stdout += chunk.toString();
4924
+ });
4925
+ proc.stderr?.on("data", (chunk) => {
4926
+ stderr += chunk.toString();
4927
+ });
4928
+ proc.on("close", (code) => {
4929
+ if (code !== 0) {
4930
+ resolveResult({ ok: false, error: stderr.trim() || `exit code ${code}` });
4931
+ return;
4932
+ }
4933
+ try {
4934
+ resolveResult({ ok: true, data: JSON.parse(stdout) });
4935
+ } catch {
4936
+ resolveResult({ ok: true, data: stdout.trim() });
4937
+ }
4938
+ });
4939
+ proc.on("error", (err) => {
4940
+ resolveResult({ ok: false, error: err.message });
4941
+ });
4942
+ });
4943
+ }
3481
4944
  let devProcess = null;
3482
4945
  let devUrl = null;
3483
4946
  const DEV_URL_PATTERN = /https?:\/\/(?:localhost|127\.0\.0\.1):\d+/;
@@ -3493,7 +4956,7 @@ async function createAgentServer(params) {
3493
4956
  }
3494
4957
  async function detectDevPort(cwd) {
3495
4958
  try {
3496
- const pkg = JSON.parse(await (0, import_promises2.readFile)((0, import_node_path2.join)(cwd, "package.json"), "utf-8"));
4959
+ const pkg = JSON.parse(await (0, import_promises3.readFile)((0, import_node_path3.join)(cwd, "package.json"), "utf-8"));
3497
4960
  const devScript = pkg.scripts?.dev;
3498
4961
  if (devScript) {
3499
4962
  const portFlag = devScript.match(/--port\s+(\d+)/);
@@ -3509,7 +4972,7 @@ async function createAgentServer(params) {
3509
4972
  }
3510
4973
  for (const name of ["vite.config.ts", "vite.config.mts", "vite.config.js"]) {
3511
4974
  try {
3512
- const content = await (0, import_promises2.readFile)((0, import_node_path2.join)(cwd, name), "utf-8");
4975
+ const content = await (0, import_promises3.readFile)((0, import_node_path3.join)(cwd, name), "utf-8");
3513
4976
  const portMatch = content.match(/port\s*:\s*(\d+)/);
3514
4977
  if (portMatch) {
3515
4978
  return Number(portMatch[1]);
@@ -3519,32 +4982,76 @@ async function createAgentServer(params) {
3519
4982
  }
3520
4983
  for (const name of ["next.config.ts", "next.config.mts", "next.config.js", "next.config.mjs"]) {
3521
4984
  try {
3522
- await (0, import_promises2.stat)((0, import_node_path2.join)(cwd, name));
4985
+ await (0, import_promises3.stat)((0, import_node_path3.join)(cwd, name));
3523
4986
  return 3e3;
3524
4987
  } catch {
3525
4988
  }
3526
4989
  }
3527
4990
  try {
3528
- await (0, import_promises2.stat)((0, import_node_path2.join)(cwd, "angular.json"));
4991
+ await (0, import_promises3.stat)((0, import_node_path3.join)(cwd, "angular.json"));
3529
4992
  return 4200;
3530
4993
  } catch {
3531
4994
  }
3532
4995
  for (const name of ["nuxt.config.ts", "nuxt.config.js"]) {
3533
4996
  try {
3534
- await (0, import_promises2.stat)((0, import_node_path2.join)(cwd, name));
4997
+ await (0, import_promises3.stat)((0, import_node_path3.join)(cwd, name));
3535
4998
  return 3e3;
3536
4999
  } catch {
3537
5000
  }
3538
5001
  }
3539
5002
  return null;
3540
5003
  }
5004
+ let cachedProjectInfo = null;
5005
+ async function getProjectInfo() {
5006
+ if (cachedProjectInfo) {
5007
+ return cachedProjectInfo;
5008
+ }
5009
+ const cwd = context.cwd;
5010
+ let repo = null;
5011
+ let packageName = null;
5012
+ let packageVersion = null;
5013
+ try {
5014
+ repo = await new Promise((res, rej) => {
5015
+ (0, import_node_child_process.execFile)("git", ["remote", "get-url", "origin"], { cwd }, (err, stdout) => {
5016
+ if (err) {
5017
+ return rej(err);
5018
+ }
5019
+ const url = stdout.trim();
5020
+ const match2 = url.match(/\/([^/]+?)(?:\.git)?$/);
5021
+ res(match2 ? match2[1] : url);
5022
+ });
5023
+ });
5024
+ } catch {
5025
+ }
5026
+ try {
5027
+ const pkg = JSON.parse(await (0, import_promises3.readFile)((0, import_node_path3.join)(cwd, "package.json"), "utf-8"));
5028
+ packageName = pkg.name ?? null;
5029
+ packageVersion = pkg.version ?? null;
5030
+ } catch {
5031
+ }
5032
+ cachedProjectInfo = {
5033
+ path: cwd,
5034
+ folder: (0, import_node_path3.basename)(cwd),
5035
+ repo,
5036
+ packageName,
5037
+ packageVersion
5038
+ };
5039
+ return cachedProjectInfo;
5040
+ }
3541
5041
  app.get("/api/env/status", async (c) => {
5042
+ const [authResult, project] = await Promise.all([
5043
+ runCliCommand(["auth", "who"]),
5044
+ getProjectInfo()
5045
+ ]);
5046
+ const env = authResult.ok ? authResult.data : null;
3542
5047
  if (devProcess && devProcess.exitCode === null && devUrl) {
3543
5048
  const httpStatus = await probeUrl(devUrl);
3544
5049
  return c.json({
3545
5050
  status: httpStatus !== null ? "running" : "starting",
3546
5051
  url: devUrl,
3547
5052
  managed: true,
5053
+ project,
5054
+ env,
3548
5055
  ...httpStatus !== null ? { httpStatus } : {}
3549
5056
  });
3550
5057
  }
@@ -3553,7 +5060,7 @@ async function createAgentServer(params) {
3553
5060
  devProcess = null;
3554
5061
  const stoppedUrl = devUrl;
3555
5062
  devUrl = null;
3556
- return c.json({ status: "stopped", url: stoppedUrl, exitCode, managed: true });
5063
+ return c.json({ status: "stopped", url: stoppedUrl, exitCode, managed: true, project, env });
3557
5064
  }
3558
5065
  const explicitUrl = c.req.query("url");
3559
5066
  const explicitPort = c.req.query("port");
@@ -3574,10 +5081,12 @@ async function createAgentServer(params) {
3574
5081
  status: httpStatus !== null ? "running" : "stopped",
3575
5082
  url: probeTarget,
3576
5083
  httpStatus: httpStatus ?? void 0,
3577
- managed: false
5084
+ managed: false,
5085
+ project,
5086
+ env
3578
5087
  });
3579
5088
  }
3580
- return c.json({ status: "stopped", url: null, managed: false });
5089
+ return c.json({ status: "stopped", url: null, managed: false, project, env });
3581
5090
  });
3582
5091
  app.post("/api/env/serve", (c) => {
3583
5092
  if (devProcess && devProcess.exitCode === null) {
@@ -3637,10 +5146,6 @@ async function createAgentServer(params) {
3637
5146
  devUrl = null;
3638
5147
  return c.json({ status: "stopped", url: stoppedUrl, pid });
3639
5148
  });
3640
- const CLI_EXEC = process.env.DOCYRUS_CLI_EXECUTABLE || process.execPath;
3641
- const CLI_ENTRY = process.env.DOCYRUS_CLI_ENTRY;
3642
- const CLI_SCOPE = process.env.DOCYRUS_CLI_SCOPE;
3643
- const CLI_TIMEOUT_MS = 3e4;
3644
5149
  const BOOLEAN_CLI_FLAGS = /* @__PURE__ */ new Set(["json", "verbose", "global", "noAuth", "expand", "i"]);
3645
5150
  function buildCliArgs(pathSegments, query, body) {
3646
5151
  const args = [...pathSegments, "--json"];
@@ -3679,9 +5184,6 @@ async function createAgentServer(params) {
3679
5184
  return args;
3680
5185
  }
3681
5186
  app.all("/api/cli/*", async (c) => {
3682
- if (!CLI_ENTRY) {
3683
- return c.json({ error: "CLI entry path not available (DOCYRUS_CLI_ENTRY not set)" }, 500);
3684
- }
3685
5187
  const pathSegments = c.req.path.replace(/^\/api\/cli\/?/, "").split("/").filter(Boolean);
3686
5188
  if (pathSegments.length === 0) {
3687
5189
  return c.json({ error: "No command specified. Usage: /api/cli/<command>/[subcommand]/[args...]" }, 400);
@@ -3737,12 +5239,10 @@ async function createAgentServer(params) {
3737
5239
  });
3738
5240
  });
3739
5241
  const { serve: serve2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
3740
- serve2({
3741
- fetch: app.fetch,
3742
- port
3743
- }, (info) => {
5242
+ const MAX_PORT_RETRIES = 10;
5243
+ function printBanner(actualPort) {
3744
5244
  process.stderr.write(`
3745
- Agent server listening on http://localhost:${info.port}
5245
+ Agent server listening on http://localhost:${actualPort}
3746
5246
 
3747
5247
  `);
3748
5248
  process.stderr.write(` POST /api/chat \u2014 send chat messages (SSE UIMessage stream)
@@ -3752,6 +5252,8 @@ async function createAgentServer(params) {
3752
5252
  process.stderr.write(` GET /api/status \u2014 session status
3753
5253
  `);
3754
5254
  process.stderr.write(` GET /api/sessions \u2014 list sessions
5255
+ `);
5256
+ process.stderr.write(` POST /api/sessions \u2014 create a new session
3755
5257
  `);
3756
5258
  process.stderr.write(` GET /api/sessions/:sessionId/messages \u2014 session messages
3757
5259
  `);
@@ -3760,6 +5262,18 @@ async function createAgentServer(params) {
3760
5262
  process.stderr.write(` GET /api/context \u2014 server context
3761
5263
  `);
3762
5264
  process.stderr.write(` GET /api/models \u2014 available models
5265
+ `);
5266
+ process.stderr.write(` GET /api/auth/providers \u2014 auth provider catalog
5267
+ `);
5268
+ process.stderr.write(` POST /api/auth/providers/:id/login \u2014 provider login/start login flow
5269
+ `);
5270
+ process.stderr.write(` POST /api/auth/providers/:id/oauth/continue \u2014 continue OAuth flow
5271
+ `);
5272
+ process.stderr.write(` POST /api/auth/providers/:id/logout \u2014 provider logout
5273
+ `);
5274
+ process.stderr.write(` POST /api/models/select \u2014 select model and optional thinking level
5275
+ `);
5276
+ process.stderr.write(` GET /api/slash-commands \u2014 slash command list
3763
5277
  `);
3764
5278
  process.stderr.write(` GET /api/fs/tree \u2014 directory tree
3765
5279
  `);
@@ -3784,7 +5298,33 @@ async function createAgentServer(params) {
3784
5298
  process.stderr.write(` * /api/cli/** \u2014 proxy any docyrus CLI command
3785
5299
 
3786
5300
  `);
3787
- });
5301
+ }
5302
+ for (let attempt = 0; attempt < MAX_PORT_RETRIES; attempt++) {
5303
+ const candidatePort = port + attempt;
5304
+ try {
5305
+ await new Promise((resolveStart, rejectStart) => {
5306
+ const server = serve2({ fetch: app.fetch, port: candidatePort }, () => {
5307
+ printBanner(candidatePort);
5308
+ resolveStart();
5309
+ });
5310
+ server.on("error", (err) => {
5311
+ if (err.code === "EADDRINUSE") {
5312
+ process.stderr.write(` Port ${candidatePort} in use, trying ${candidatePort + 1}...
5313
+ `);
5314
+ rejectStart(err);
5315
+ } else {
5316
+ throw err;
5317
+ }
5318
+ });
5319
+ });
5320
+ return;
5321
+ } catch (err) {
5322
+ const isAddrInUse = err instanceof Error && "code" in err && err.code === "EADDRINUSE";
5323
+ if (!isAddrInUse || attempt === MAX_PORT_RETRIES - 1) {
5324
+ throw new Error(`All ports ${port}\u2013${port + MAX_PORT_RETRIES - 1} are in use`);
5325
+ }
5326
+ }
5327
+ }
3788
5328
  }
3789
5329
 
3790
5330
  // src/server/server-loader.ts
@@ -3800,15 +5340,15 @@ function readLoaderRequest() {
3800
5340
  }
3801
5341
  async function loadPiExports() {
3802
5342
  const piPackageDir = readRequiredEnv("PI_PACKAGE_DIR");
3803
- const moduleUrl = (0, import_node_url.pathToFileURL)((0, import_node_path3.join)(piPackageDir, "dist", "index.js")).href;
5343
+ const moduleUrl = (0, import_node_url.pathToFileURL)((0, import_node_path4.join)(piPackageDir, "dist", "index.js")).href;
3804
5344
  return await import(moduleUrl);
3805
5345
  }
3806
5346
  function resolvePackagedPiResourceRoot() {
3807
5347
  const candidates = [
3808
- (0, import_node_path3.resolve)(process.cwd(), "apps/api-cli/resources/pi-agent"),
3809
- (0, import_node_path3.resolve)(__dirname, "../resources/pi-agent"),
3810
- (0, import_node_path3.resolve)(__dirname, "resources/pi-agent"),
3811
- (0, import_node_path3.resolve)(process.cwd(), "dist/apps/api-cli/resources/pi-agent")
5348
+ (0, import_node_path4.resolve)(process.cwd(), "apps/api-cli/resources/pi-agent"),
5349
+ (0, import_node_path4.resolve)(__dirname, "../resources/pi-agent"),
5350
+ (0, import_node_path4.resolve)(__dirname, "resources/pi-agent"),
5351
+ (0, import_node_path4.resolve)(process.cwd(), "dist/apps/api-cli/resources/pi-agent")
3812
5352
  ];
3813
5353
  const resolved = candidates.find((candidate) => (0, import_node_fs.existsSync)(candidate));
3814
5354
  if (!resolved) {
@@ -3817,13 +5357,13 @@ function resolvePackagedPiResourceRoot() {
3817
5357
  return resolved;
3818
5358
  }
3819
5359
  function resolvePackagedExtensionPaths(resourceRoot) {
3820
- const extensionsRoot = (0, import_node_path3.join)(resourceRoot, "extensions");
5360
+ const extensionsRoot = (0, import_node_path4.join)(resourceRoot, "extensions");
3821
5361
  if (!(0, import_node_fs.existsSync)(extensionsRoot)) {
3822
5362
  return [];
3823
5363
  }
3824
5364
  return (0, import_node_fs.readdirSync)(extensionsRoot, {
3825
5365
  withFileTypes: true
3826
- }).filter((entry) => entry.isDirectory() || entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js"))).map((entry) => (0, import_node_path3.join)(extensionsRoot, entry.name)).sort((left, right) => left.localeCompare(right));
5366
+ }).filter((entry) => entry.isDirectory() || entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js"))).map((entry) => (0, import_node_path4.join)(extensionsRoot, entry.name)).sort((left, right) => left.localeCompare(right));
3827
5367
  }
3828
5368
  function setProcessArgValue(flag, value) {
3829
5369
  const existingIndex = process.argv.indexOf(flag);
@@ -3890,6 +5430,45 @@ function renderStartupSplash(version) {
3890
5430
  `
3891
5431
  );
3892
5432
  }
5433
+ function createServerSessionAdapter(params) {
5434
+ return {
5435
+ id: params.session.id,
5436
+ get isStreaming() {
5437
+ return params.session.isStreaming;
5438
+ },
5439
+ get model() {
5440
+ return params.session.model;
5441
+ },
5442
+ get thinkingLevel() {
5443
+ return params.session.thinkingLevel;
5444
+ },
5445
+ subscribe(listener) {
5446
+ return params.session.subscribe(listener);
5447
+ },
5448
+ prompt(text) {
5449
+ return params.session.prompt(text);
5450
+ },
5451
+ abort() {
5452
+ return params.session.abort();
5453
+ },
5454
+ setModel(model) {
5455
+ return params.session.setModel(model);
5456
+ },
5457
+ setThinkingLevel(level) {
5458
+ return params.session.setThinkingLevel(level);
5459
+ },
5460
+ getAvailableThinkingLevels() {
5461
+ return params.session.getAvailableThinkingLevels();
5462
+ },
5463
+ supportsThinking() {
5464
+ return params.session.supportsThinking();
5465
+ },
5466
+ listCommands() {
5467
+ const getCommands = params.extensionsResult.runtime.getCommands;
5468
+ return typeof getCommands === "function" ? getCommands() : [];
5469
+ }
5470
+ };
5471
+ }
3893
5472
  async function main() {
3894
5473
  const request = readLoaderRequest();
3895
5474
  const pi = await loadPiExports();
@@ -3898,16 +5477,16 @@ async function main() {
3898
5477
  const version = process.env.DOCYRUS_PI_VERSION || "dev";
3899
5478
  const resourceRoot = resolvePackagedPiResourceRoot();
3900
5479
  const packagedExtensionPaths = resolvePackagedExtensionPaths(resourceRoot);
3901
- const mcpConfigPath = (0, import_node_path3.join)(agentDir, "mcp.json");
5480
+ const mcpConfigPath = (0, import_node_path4.join)(agentDir, "mcp.json");
3902
5481
  const hasPackagedMcpAdapter = packagedExtensionPaths.some((extensionPath) => extensionPath.includes("pi-mcp-adapter"));
3903
- const envStore = new AgentEnvStore((0, import_node_path3.join)(agentDir, "env.json"));
5482
+ const envStore = new AgentEnvStore((0, import_node_path4.join)(agentDir, "env.json"));
3904
5483
  await envStore.hydrateProcessEnv(process.env);
3905
5484
  if (hasPackagedMcpAdapter) {
3906
5485
  setProcessArgValue("--mcp-config", mcpConfigPath);
3907
5486
  }
3908
- const authStorage = pi.AuthStorage.create((0, import_node_path3.join)(agentDir, "auth.json"));
5487
+ const authStorage = pi.AuthStorage.create((0, import_node_path4.join)(agentDir, "auth.json"));
3909
5488
  const settingsManager = pi.SettingsManager.create(cwd, agentDir);
3910
- const modelsJsonPath = (0, import_node_path3.join)(agentDir, "models.json");
5489
+ const modelsJsonPath = (0, import_node_path4.join)(agentDir, "models.json");
3911
5490
  const modelRegistry = new pi.ModelRegistry(authStorage, modelsJsonPath);
3912
5491
  const quietStartup = !request.verbose;
3913
5492
  if (quietStartup) {
@@ -3934,7 +5513,7 @@ async function main() {
3934
5513
  agentDir,
3935
5514
  settingsManager,
3936
5515
  additionalExtensionPaths: packagedExtensionPaths,
3937
- systemPrompt: (0, import_node_path3.join)(
5516
+ systemPrompt: (0, import_node_path4.join)(
3938
5517
  resourceRoot,
3939
5518
  "prompts",
3940
5519
  request.profile === "agent" ? "agent-system.md" : "coder-system.md"
@@ -3967,14 +5546,18 @@ Or create ${modelsJsonPath}`
3967
5546
  );
3968
5547
  }
3969
5548
  await createAgentServer({
3970
- session,
5549
+ session: createServerSessionAdapter({ session, extensionsResult }),
3971
5550
  port: request.port,
3972
5551
  sessionManager: {
3973
5552
  list: () => pi.SessionManager.list(cwd, request.sessionDir),
3974
5553
  open: (path) => pi.SessionManager.open(path, request.sessionDir)
3975
5554
  },
3976
- modelRegistry: {
3977
- getAvailable: () => modelRegistry.getAvailable()
5555
+ modelRegistry,
5556
+ authRuntime: {
5557
+ authStorage,
5558
+ envStore,
5559
+ settingsManager,
5560
+ modelsJsonPath
3978
5561
  },
3979
5562
  context: {
3980
5563
  cwd,
@@ -3984,6 +5567,24 @@ Or create ${modelsJsonPath}`
3984
5567
  sessionDir: request.sessionDir ?? null,
3985
5568
  thinkingLevel: request.thinking ?? null
3986
5569
  },
5570
+ onCreateSession: async () => {
5571
+ const { session: freshSession, extensionsResult: freshExtensionsResult } = await pi.createAgentSession({
5572
+ cwd,
5573
+ agentDir,
5574
+ authStorage,
5575
+ modelRegistry,
5576
+ resourceLoader,
5577
+ settingsManager,
5578
+ sessionManager,
5579
+ tools: buildTools(request.profile, cwd, pi),
5580
+ model: requestedModel,
5581
+ thinkingLevel: request.thinking
5582
+ });
5583
+ return createServerSessionAdapter({
5584
+ session: freshSession,
5585
+ extensionsResult: freshExtensionsResult
5586
+ });
5587
+ },
3987
5588
  onResumeSession: async (sessionId) => {
3988
5589
  const sessions = await pi.SessionManager.list(cwd, request.sessionDir);
3989
5590
  const match2 = sessions.find((s) => s.id === sessionId);
@@ -3991,20 +5592,26 @@ Or create ${modelsJsonPath}`
3991
5592
  throw new Error(`Session not found: ${sessionId}`);
3992
5593
  }
3993
5594
  const resumeCwd = match2.cwd || cwd;
3994
- const { session: resumedSession } = await pi.createAgentSession({
5595
+ const resumedSessionManager = pi.SessionManager.open(
5596
+ match2.path,
5597
+ request.sessionDir
5598
+ );
5599
+ const { session: resumedSession, extensionsResult: resumedExtensionsResult } = await pi.createAgentSession({
3995
5600
  cwd: resumeCwd,
3996
5601
  agentDir,
3997
5602
  authStorage,
3998
5603
  modelRegistry,
3999
5604
  resourceLoader,
4000
5605
  settingsManager,
4001
- sessionManager,
5606
+ sessionManager: resumedSessionManager,
4002
5607
  tools: buildTools(request.profile, resumeCwd, pi),
4003
5608
  model: requestedModel,
4004
- thinkingLevel: request.thinking,
4005
- resumeSessionId: sessionId
5609
+ thinkingLevel: request.thinking
5610
+ });
5611
+ return createServerSessionAdapter({
5612
+ session: resumedSession,
5613
+ extensionsResult: resumedExtensionsResult
4006
5614
  });
4007
- return resumedSession;
4008
5615
  }
4009
5616
  });
4010
5617
  }