@docyrus/docyrus 0.0.25 → 0.0.27

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,9 @@ 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_node_crypto2 = require("node:crypto");
799
+ var import_promises3 = require("node:fs/promises");
800
+ var import_node_path3 = require("node:path");
800
801
 
801
802
  // ../../node_modules/.pnpm/hono@4.12.8/node_modules/hono/dist/compose.js
802
803
  var compose = (middleware, onError, onNotFound) => {
@@ -2936,10 +2937,212 @@ var cors = (options) => {
2936
2937
  };
2937
2938
  };
2938
2939
 
2940
+ // resources/pi-agent/shared/askUserProtocol.ts
2941
+ var ASK_USER_TAG = "ask_user";
2942
+ var ASK_USER_OPEN = `<${ASK_USER_TAG}>`;
2943
+ var ASK_USER_CLOSE = `</${ASK_USER_TAG}>`;
2944
+ var ASK_USER_RESPONSE_TAG = "ask_user_response";
2945
+ var ASK_USER_RESPONSE_OPEN = `<${ASK_USER_RESPONSE_TAG}>`;
2946
+ var ASK_USER_RESPONSE_CLOSE = `</${ASK_USER_RESPONSE_TAG}>`;
2947
+ function hashString(value) {
2948
+ let hash = 0;
2949
+ for (let index = 0; index < value.length; index += 1) {
2950
+ hash = (hash << 5) - hash + value.charCodeAt(index);
2951
+ hash |= 0;
2952
+ }
2953
+ return Math.abs(hash).toString(36);
2954
+ }
2955
+ function isRecord(value) {
2956
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2957
+ }
2958
+ function isNonEmptyString(value) {
2959
+ return typeof value === "string" && value.trim().length > 0;
2960
+ }
2961
+ function normalizeTrimmedString(value) {
2962
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
2963
+ }
2964
+ function normalizeQuestionType(value) {
2965
+ return value === "text" || value === "textarea" || value === "boolean" || value === "singleSelect" || value === "multiSelect" ? value : void 0;
2966
+ }
2967
+ function normalizeDefaultValue(value) {
2968
+ if (typeof value === "string" || typeof value === "boolean") {
2969
+ return value;
2970
+ }
2971
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
2972
+ return [...value];
2973
+ }
2974
+ return void 0;
2975
+ }
2976
+ function normalizeOptions(value) {
2977
+ if (!Array.isArray(value)) {
2978
+ return void 0;
2979
+ }
2980
+ const options = [];
2981
+ for (const item of value) {
2982
+ if (!isRecord(item) || !isNonEmptyString(item.label) || !isNonEmptyString(item.value)) {
2983
+ return void 0;
2984
+ }
2985
+ options.push({
2986
+ label: item.label.trim(),
2987
+ value: item.value.trim()
2988
+ });
2989
+ }
2990
+ return options;
2991
+ }
2992
+ function normalizeAskUserQuestion(value) {
2993
+ if (!isRecord(value)) {
2994
+ return void 0;
2995
+ }
2996
+ const id = normalizeTrimmedString(value.id);
2997
+ const label = normalizeTrimmedString(value.label);
2998
+ const type = normalizeQuestionType(value.type);
2999
+ if (!id || !label || !type) {
3000
+ return void 0;
3001
+ }
3002
+ const question = {
3003
+ id,
3004
+ label,
3005
+ type
3006
+ };
3007
+ const description = normalizeTrimmedString(value.description);
3008
+ if (description) {
3009
+ question.description = description;
3010
+ }
3011
+ if (typeof value.required === "boolean") {
3012
+ question.required = value.required;
3013
+ }
3014
+ const options = normalizeOptions(value.options);
3015
+ if (type === "singleSelect" || type === "multiSelect") {
3016
+ if (!options || options.length === 0) {
3017
+ return void 0;
3018
+ }
3019
+ question.options = options;
3020
+ }
3021
+ const placeholder = normalizeTrimmedString(value.placeholder);
3022
+ if (placeholder) {
3023
+ question.placeholder = placeholder;
3024
+ }
3025
+ const defaultValue = normalizeDefaultValue(value.defaultValue);
3026
+ if (defaultValue !== void 0) {
3027
+ question.defaultValue = defaultValue;
3028
+ }
3029
+ return question;
3030
+ }
3031
+ function normalizeAskUserRequest(value) {
3032
+ if (!isRecord(value)) {
3033
+ return void 0;
3034
+ }
3035
+ const title = normalizeTrimmedString(value.title);
3036
+ const message = normalizeTrimmedString(value.message);
3037
+ if (!title || !message || !Array.isArray(value.questions) || value.questions.length === 0) {
3038
+ return void 0;
3039
+ }
3040
+ const questions = [];
3041
+ for (const question of value.questions) {
3042
+ const normalized = normalizeAskUserQuestion(question);
3043
+ if (!normalized) {
3044
+ return void 0;
3045
+ }
3046
+ questions.push(normalized);
3047
+ }
3048
+ return {
3049
+ title,
3050
+ message,
3051
+ questions
3052
+ };
3053
+ }
3054
+ function normalizeAskUserResponse(value) {
3055
+ if (!isRecord(value)) {
3056
+ return void 0;
3057
+ }
3058
+ const action = value.action;
3059
+ if (action !== "submit" && action !== "cancel" && action !== "decline") {
3060
+ return void 0;
3061
+ }
3062
+ const answersRecord = isRecord(value.answers) ? value.answers : {};
3063
+ const answers = {};
3064
+ for (const [key, answerValue] of Object.entries(answersRecord)) {
3065
+ const normalized = normalizeDefaultValue(answerValue);
3066
+ if (normalized !== void 0) {
3067
+ answers[key] = normalized;
3068
+ }
3069
+ }
3070
+ return {
3071
+ action,
3072
+ answers
3073
+ };
3074
+ }
3075
+ function createAskUserToolCallId(request) {
3076
+ return `ask_user_${hashString(JSON.stringify(request))}`;
3077
+ }
3078
+ function parseAskUserRequestFromText(text) {
3079
+ const trimmed = text.trim();
3080
+ if (!trimmed.startsWith(ASK_USER_OPEN) || !trimmed.endsWith(ASK_USER_CLOSE)) {
3081
+ return void 0;
3082
+ }
3083
+ const body = trimmed.slice(ASK_USER_OPEN.length, trimmed.length - ASK_USER_CLOSE.length).trim();
3084
+ if (!body) {
3085
+ return void 0;
3086
+ }
3087
+ try {
3088
+ return normalizeAskUserRequest(JSON.parse(body));
3089
+ } catch {
3090
+ return void 0;
3091
+ }
3092
+ }
3093
+ function formatAskUserResponsePrompt(response) {
3094
+ return [
3095
+ "The user submitted structured clarification answers.",
3096
+ "",
3097
+ `${ASK_USER_RESPONSE_OPEN}`,
3098
+ JSON.stringify(response, null, 2),
3099
+ `${ASK_USER_RESPONSE_CLOSE}`,
3100
+ "",
3101
+ "Continue the planning workflow using these answers."
3102
+ ].join("\n");
3103
+ }
3104
+ function parseAskUserResponseFromToolOutput(output) {
3105
+ return normalizeAskUserResponse(output);
3106
+ }
3107
+
2939
3108
  // src/server/eventBridge.ts
2940
3109
  function createEventBridge(params) {
2941
- const { messageId, onChunk, onDone, onError } = params;
3110
+ const { messageId, onChunk, onDone, onError, onAskUser } = params;
2942
3111
  const activeToolCalls = /* @__PURE__ */ new Map();
3112
+ let activeTextBuffer = null;
3113
+ function flushBufferedTextToStream(close) {
3114
+ if (!activeTextBuffer) {
3115
+ return;
3116
+ }
3117
+ onChunk({ type: "text-start", id: messageId });
3118
+ if (activeTextBuffer.text.length > 0) {
3119
+ onChunk({ type: "text-delta", id: messageId, delta: activeTextBuffer.text });
3120
+ }
3121
+ if (close) {
3122
+ onChunk({ type: "text-end", id: messageId });
3123
+ activeTextBuffer = null;
3124
+ return;
3125
+ }
3126
+ activeTextBuffer = { text: "", mode: "plain" };
3127
+ }
3128
+ function maybeEmitAskUser(text) {
3129
+ const request = parseAskUserRequestFromText(text);
3130
+ if (!request) {
3131
+ return false;
3132
+ }
3133
+ const toolCallId = createAskUserToolCallId(request);
3134
+ onAskUser?.({ toolCallId, request });
3135
+ onChunk({ type: "tool-input-start", toolCallId, toolName: "ask_user", dynamic: true, title: request.title });
3136
+ onChunk({
3137
+ type: "tool-input-available",
3138
+ toolCallId,
3139
+ toolName: "ask_user",
3140
+ input: request,
3141
+ dynamic: true,
3142
+ title: request.title
3143
+ });
3144
+ return true;
3145
+ }
2943
3146
  function handleEvent(event) {
2944
3147
  switch (event.type) {
2945
3148
  case "message_update": {
@@ -2963,15 +3166,42 @@ function createEventBridge(params) {
2963
3166
  }
2964
3167
  switch (assistantEvent.type) {
2965
3168
  case "text_start": {
2966
- onChunk({ type: "text-start", id: messageId });
3169
+ activeTextBuffer = { text: "", mode: "pending" };
2967
3170
  break;
2968
3171
  }
2969
3172
  case "text_delta": {
2970
- onChunk({ type: "text-delta", id: messageId, delta: assistantEvent.delta ?? "" });
3173
+ if (!activeTextBuffer) {
3174
+ onChunk({ type: "text-start", id: messageId });
3175
+ onChunk({ type: "text-delta", id: messageId, delta: assistantEvent.delta ?? "" });
3176
+ activeTextBuffer = { text: "", mode: "plain" };
3177
+ break;
3178
+ }
3179
+ if (activeTextBuffer.mode === "plain") {
3180
+ onChunk({ type: "text-delta", id: messageId, delta: assistantEvent.delta ?? "" });
3181
+ break;
3182
+ }
3183
+ activeTextBuffer.text += assistantEvent.delta ?? "";
3184
+ if ("<ask_user>".startsWith(activeTextBuffer.text) || activeTextBuffer.text.startsWith("<ask_user>")) {
3185
+ break;
3186
+ }
3187
+ flushBufferedTextToStream(false);
2971
3188
  break;
2972
3189
  }
2973
3190
  case "text_end": {
2974
- onChunk({ type: "text-end", id: messageId });
3191
+ if (!activeTextBuffer) {
3192
+ onChunk({ type: "text-end", id: messageId });
3193
+ break;
3194
+ }
3195
+ if (activeTextBuffer.mode === "plain") {
3196
+ onChunk({ type: "text-end", id: messageId });
3197
+ activeTextBuffer = null;
3198
+ break;
3199
+ }
3200
+ if (!maybeEmitAskUser(activeTextBuffer.text)) {
3201
+ flushBufferedTextToStream(true);
3202
+ } else {
3203
+ activeTextBuffer = null;
3204
+ }
2975
3205
  break;
2976
3206
  }
2977
3207
  case "thinking_start": {
@@ -3050,6 +3280,854 @@ function createEventBridge(params) {
3050
3280
  return { handleEvent };
3051
3281
  }
3052
3282
 
3283
+ // src/agent/modelsConfig.ts
3284
+ var import_promises2 = require("node:fs/promises");
3285
+ var import_node_path2 = require("node:path");
3286
+ function createDefaultState2() {
3287
+ return {
3288
+ providers: {}
3289
+ };
3290
+ }
3291
+ async function readModelsConfig(filePath) {
3292
+ try {
3293
+ const raw2 = await (0, import_promises2.readFile)(filePath, "utf8");
3294
+ const parsed = JSON.parse(raw2);
3295
+ if (!parsed || typeof parsed !== "object" || typeof parsed.providers !== "object" || parsed.providers === null) {
3296
+ return createDefaultState2();
3297
+ }
3298
+ return {
3299
+ providers: { ...parsed.providers }
3300
+ };
3301
+ } catch (error) {
3302
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
3303
+ return createDefaultState2();
3304
+ }
3305
+ return createDefaultState2();
3306
+ }
3307
+ }
3308
+ async function writeModelsConfig(filePath, state) {
3309
+ const directory = (0, import_node_path2.dirname)(filePath);
3310
+ await (0, import_promises2.mkdir)(directory, {
3311
+ recursive: true,
3312
+ mode: 448
3313
+ });
3314
+ await (0, import_promises2.writeFile)(filePath, `${JSON.stringify(state, null, 2)}
3315
+ `, {
3316
+ encoding: "utf8",
3317
+ mode: 384
3318
+ });
3319
+ await (0, import_promises2.chmod)(filePath, 384);
3320
+ }
3321
+ async function upsertModelsProvider(filePath, providerId, providerConfig) {
3322
+ const state = await readModelsConfig(filePath);
3323
+ await writeModelsConfig(filePath, {
3324
+ providers: {
3325
+ ...state.providers,
3326
+ [providerId]: providerConfig
3327
+ }
3328
+ });
3329
+ }
3330
+ async function removeModelsProvider(filePath, providerId) {
3331
+ const state = await readModelsConfig(filePath);
3332
+ const nextProviders = { ...state.providers };
3333
+ delete nextProviders[providerId];
3334
+ await writeModelsConfig(filePath, {
3335
+ providers: nextProviders
3336
+ });
3337
+ }
3338
+ function createCustomOpenAiProviderConfig(params) {
3339
+ return {
3340
+ baseUrl: params.baseUrl,
3341
+ apiKey: "env:CUSTOM_OPENAI_API_KEY",
3342
+ api: "openai-completions",
3343
+ models: [
3344
+ {
3345
+ id: params.modelId,
3346
+ name: params.modelId,
3347
+ reasoning: false,
3348
+ input: ["text"],
3349
+ contextWindow: 128e3,
3350
+ maxTokens: 16384,
3351
+ cost: {
3352
+ input: 0,
3353
+ output: 0,
3354
+ cacheRead: 0,
3355
+ cacheWrite: 0
3356
+ }
3357
+ }
3358
+ ]
3359
+ };
3360
+ }
3361
+ function createAzureProviderConfig(params) {
3362
+ const providerConfig = {
3363
+ baseUrl: params.baseUrl,
3364
+ apiKey: "env:AZURE_OPENAI_API_KEY"
3365
+ };
3366
+ if (params.useCustomModel) {
3367
+ providerConfig.api = "azure-openai-responses";
3368
+ providerConfig.models = [
3369
+ {
3370
+ id: params.modelId,
3371
+ name: params.modelId,
3372
+ reasoning: false,
3373
+ input: ["text"],
3374
+ contextWindow: 128e3,
3375
+ maxTokens: 16384,
3376
+ cost: {
3377
+ input: 0,
3378
+ output: 0,
3379
+ cacheRead: 0,
3380
+ cacheWrite: 0
3381
+ }
3382
+ }
3383
+ ];
3384
+ }
3385
+ return providerConfig;
3386
+ }
3387
+
3388
+ // src/agent/authFlows.ts
3389
+ function trimOrUndefined(value) {
3390
+ const trimmed = value?.trim();
3391
+ return trimmed && trimmed.length > 0 ? trimmed : void 0;
3392
+ }
3393
+ async function saveGenericApiKey(params) {
3394
+ params.authStorage.set(params.providerId, {
3395
+ type: "api_key",
3396
+ key: params.apiKey.trim()
3397
+ });
3398
+ }
3399
+ async function saveCustomOpenAiConfig(params) {
3400
+ params.authStorage.set("custom-openai", {
3401
+ type: "api_key",
3402
+ key: params.apiKey.trim()
3403
+ });
3404
+ await params.envStore.removeMany([
3405
+ "CUSTOM_OPENAI_API_KEY"
3406
+ ]);
3407
+ await params.envStore.setMany({
3408
+ CUSTOM_OPENAI_API_KEY: params.apiKey.trim()
3409
+ });
3410
+ await upsertModelsProvider(params.modelsJsonPath, "custom-openai", createCustomOpenAiProviderConfig({
3411
+ baseUrl: params.baseUrl.trim(),
3412
+ modelId: params.modelId.trim()
3413
+ }));
3414
+ params.settingsManager.setDefaultModelAndProvider("custom-openai", params.modelId.trim());
3415
+ }
3416
+ async function saveAzureConfig(params) {
3417
+ const modelId = params.modelId.trim();
3418
+ const useCustomModel = params.useCustomModel ?? false;
3419
+ params.authStorage.set("azure-openai-responses", {
3420
+ type: "api_key",
3421
+ key: params.apiKey.trim()
3422
+ });
3423
+ await params.envStore.removeMany([
3424
+ "AZURE_OPENAI_API_KEY",
3425
+ "AZURE_OPENAI_API_VERSION",
3426
+ "AZURE_OPENAI_DEPLOYMENT_NAME_MAP"
3427
+ ]);
3428
+ const envValues = {
3429
+ AZURE_OPENAI_API_KEY: params.apiKey.trim()
3430
+ };
3431
+ const trimmedApiVersion = trimOrUndefined(params.apiVersion);
3432
+ if (trimmedApiVersion) {
3433
+ envValues.AZURE_OPENAI_API_VERSION = trimmedApiVersion;
3434
+ }
3435
+ const trimmedDeploymentName = trimOrUndefined(params.deploymentName);
3436
+ if (trimmedDeploymentName && trimmedDeploymentName !== modelId) {
3437
+ envValues.AZURE_OPENAI_DEPLOYMENT_NAME_MAP = JSON.stringify({
3438
+ [modelId]: trimmedDeploymentName
3439
+ });
3440
+ }
3441
+ await params.envStore.setMany(envValues);
3442
+ await upsertModelsProvider(params.modelsJsonPath, "azure-openai-responses", createAzureProviderConfig({
3443
+ baseUrl: params.baseUrl.trim(),
3444
+ modelId,
3445
+ useCustomModel
3446
+ }));
3447
+ params.settingsManager.setDefaultModelAndProvider("azure-openai-responses", modelId);
3448
+ }
3449
+ async function saveBedrockConfig(params) {
3450
+ params.authStorage.set("amazon-bedrock", {
3451
+ type: "api_key",
3452
+ key: "aws-configured"
3453
+ });
3454
+ await params.envStore.removeMany([
3455
+ "AWS_PROFILE",
3456
+ "AWS_ACCESS_KEY_ID",
3457
+ "AWS_SECRET_ACCESS_KEY",
3458
+ "AWS_SESSION_TOKEN",
3459
+ "AWS_REGION",
3460
+ "AWS_DEFAULT_REGION",
3461
+ "AWS_BEARER_TOKEN_BEDROCK"
3462
+ ]);
3463
+ const envValues = {
3464
+ AWS_REGION: params.region.trim(),
3465
+ AWS_DEFAULT_REGION: params.region.trim()
3466
+ };
3467
+ const profile = trimOrUndefined(params.profile);
3468
+ if (profile) {
3469
+ envValues.AWS_PROFILE = profile;
3470
+ }
3471
+ const accessKeyId = trimOrUndefined(params.accessKeyId);
3472
+ const secretAccessKey = trimOrUndefined(params.secretAccessKey);
3473
+ if (accessKeyId && secretAccessKey) {
3474
+ envValues.AWS_ACCESS_KEY_ID = accessKeyId;
3475
+ envValues.AWS_SECRET_ACCESS_KEY = secretAccessKey;
3476
+ }
3477
+ const sessionToken = trimOrUndefined(params.sessionToken);
3478
+ if (sessionToken) {
3479
+ envValues.AWS_SESSION_TOKEN = sessionToken;
3480
+ }
3481
+ await params.envStore.setMany(envValues);
3482
+ params.settingsManager.setDefaultModelAndProvider("amazon-bedrock", params.modelId.trim());
3483
+ }
3484
+ async function clearProviderConfig(params) {
3485
+ params.authStorage.remove(params.providerId);
3486
+ if (params.providerId === "custom-openai") {
3487
+ await params.envStore.removeMany([
3488
+ "CUSTOM_OPENAI_API_KEY"
3489
+ ]);
3490
+ await removeModelsProvider(params.modelsJsonPath, "custom-openai");
3491
+ }
3492
+ if (params.providerId === "azure-openai-responses") {
3493
+ await params.envStore.removeMany([
3494
+ "AZURE_OPENAI_API_KEY",
3495
+ "AZURE_OPENAI_API_VERSION",
3496
+ "AZURE_OPENAI_DEPLOYMENT_NAME_MAP"
3497
+ ]);
3498
+ await removeModelsProvider(params.modelsJsonPath, "azure-openai-responses");
3499
+ }
3500
+ if (params.providerId === "amazon-bedrock") {
3501
+ await params.envStore.removeMany([
3502
+ "AWS_PROFILE",
3503
+ "AWS_ACCESS_KEY_ID",
3504
+ "AWS_SECRET_ACCESS_KEY",
3505
+ "AWS_SESSION_TOKEN",
3506
+ "AWS_REGION",
3507
+ "AWS_DEFAULT_REGION",
3508
+ "AWS_BEARER_TOKEN_BEDROCK"
3509
+ ]);
3510
+ }
3511
+ }
3512
+
3513
+ // src/agent/providerCatalog.ts
3514
+ var OAUTH_ONLY_PROVIDER_IDS = /* @__PURE__ */ new Set([
3515
+ "github-copilot",
3516
+ "openai-codex",
3517
+ "google-gemini-cli",
3518
+ "google-antigravity"
3519
+ ]);
3520
+ var PROVIDER_LABELS = {
3521
+ anthropic: "Anthropic (Claude)",
3522
+ openai: "OpenAI",
3523
+ google: "Google (Gemini)",
3524
+ "google-vertex": "Google Vertex AI",
3525
+ "azure-openai-responses": "Azure OpenAI",
3526
+ "amazon-bedrock": "Amazon Bedrock",
3527
+ xai: "xAI (Grok)",
3528
+ groq: "Groq",
3529
+ cerebras: "Cerebras",
3530
+ openrouter: "OpenRouter",
3531
+ "vercel-ai-gateway": "Vercel AI Gateway",
3532
+ zai: "ZAI",
3533
+ mistral: "Mistral",
3534
+ minimax: "MiniMax",
3535
+ "minimax-cn": "MiniMax CN",
3536
+ huggingface: "Hugging Face",
3537
+ opencode: "OpenCode",
3538
+ "opencode-go": "OpenCode Go",
3539
+ "kimi-coding": "Kimi Coding",
3540
+ "custom-openai": "Custom OpenAI-Compatible",
3541
+ "google-gemini-cli": "Google Gemini CLI",
3542
+ "google-antigravity": "Google Antigravity",
3543
+ "openai-codex": "OpenAI Codex",
3544
+ "github-copilot": "GitHub Copilot"
3545
+ };
3546
+ var PROVIDER_HINTS = {
3547
+ anthropic: "recommended",
3548
+ "custom-openai": "custom base URL + API key",
3549
+ "azure-openai-responses": "API key + base URL/resource + deployment",
3550
+ "amazon-bedrock": "AWS profile or access key pair",
3551
+ "openai-codex": "browser auth",
3552
+ "github-copilot": "browser auth",
3553
+ "google-gemini-cli": "browser auth",
3554
+ "google-antigravity": "browser auth"
3555
+ };
3556
+ function humanizeProviderId(providerId) {
3557
+ return providerId.split("-").map((part) => {
3558
+ if (!part) {
3559
+ return part;
3560
+ }
3561
+ if (part.length <= 3) {
3562
+ return part.toUpperCase();
3563
+ }
3564
+ return part[0].toUpperCase() + part.slice(1);
3565
+ }).join(" ");
3566
+ }
3567
+ function toLabel(providerId) {
3568
+ return PROVIDER_LABELS[providerId] || humanizeProviderId(providerId);
3569
+ }
3570
+ function toHint(providerId) {
3571
+ return PROVIDER_HINTS[providerId];
3572
+ }
3573
+ function compareProviderOptions(a, b) {
3574
+ return a.label.localeCompare(b.label);
3575
+ }
3576
+ function getBrowserAuthProviderOptions(oauthProviders) {
3577
+ return oauthProviders.map((provider) => ({
3578
+ id: provider.id,
3579
+ label: provider.name || toLabel(provider.id),
3580
+ hint: toHint(provider.id),
3581
+ flow: "oauth"
3582
+ })).sort(compareProviderOptions);
3583
+ }
3584
+ function getApiKeyProviderOptions(providerIds) {
3585
+ const baseProviders = providerIds.filter((providerId) => !OAUTH_ONLY_PROVIDER_IDS.has(providerId)).map((providerId) => {
3586
+ let flow = "generic-api-key";
3587
+ if (providerId === "azure-openai-responses") {
3588
+ flow = "azure-openai-responses";
3589
+ } else if (providerId === "amazon-bedrock") {
3590
+ flow = "amazon-bedrock";
3591
+ }
3592
+ return {
3593
+ id: providerId,
3594
+ label: toLabel(providerId),
3595
+ hint: toHint(providerId),
3596
+ flow
3597
+ };
3598
+ });
3599
+ baseProviders.push({
3600
+ id: "custom-openai",
3601
+ label: toLabel("custom-openai"),
3602
+ hint: toHint("custom-openai"),
3603
+ flow: "custom-openai"
3604
+ });
3605
+ const deduped = /* @__PURE__ */ new Map();
3606
+ for (const provider of baseProviders) {
3607
+ deduped.set(provider.id, provider);
3608
+ }
3609
+ return Array.from(deduped.values()).sort(compareProviderOptions);
3610
+ }
3611
+ function getProviderLabel(providerId) {
3612
+ return toLabel(providerId);
3613
+ }
3614
+ function getProviderHint(providerId) {
3615
+ return toHint(providerId);
3616
+ }
3617
+
3618
+ // src/agent/authInteractivity.ts
3619
+ function buildFieldUiHint(field) {
3620
+ return {
3621
+ component: field.component,
3622
+ ...field.component === "password" ? { secret: true } : {},
3623
+ ...field.placeholder ? { placeholder: field.placeholder } : {},
3624
+ ...field.options ? { options: field.options } : {},
3625
+ ...field.dependsOn ? { dependsOn: field.dependsOn } : {}
3626
+ };
3627
+ }
3628
+ function buildProperty(field) {
3629
+ return {
3630
+ type: "string",
3631
+ title: field.title,
3632
+ ...field.description ? { description: field.description } : {},
3633
+ ...field.options ? { enum: field.options.map((option) => option.value) } : {},
3634
+ ...field.defaultValue ? { default: field.defaultValue } : {}
3635
+ };
3636
+ }
3637
+ function toSelectOptions(values) {
3638
+ return values.map((value) => ({ label: value, value }));
3639
+ }
3640
+ function getProviderFormFields(params) {
3641
+ const provider = params.provider;
3642
+ switch (provider.flow) {
3643
+ case "custom-openai":
3644
+ return [
3645
+ {
3646
+ name: "baseUrl",
3647
+ title: "Custom OpenAI base URL",
3648
+ required: true,
3649
+ component: "text",
3650
+ placeholder: "https://my-proxy.example.com/v1"
3651
+ },
3652
+ {
3653
+ name: "apiKey",
3654
+ title: `${provider.label} API key`,
3655
+ required: true,
3656
+ component: "password"
3657
+ },
3658
+ {
3659
+ name: "modelId",
3660
+ title: "Model ID",
3661
+ required: true,
3662
+ component: "text",
3663
+ placeholder: "gpt-4o"
3664
+ }
3665
+ ];
3666
+ case "azure-openai-responses":
3667
+ return [
3668
+ {
3669
+ name: "apiKey",
3670
+ title: "Azure OpenAI API key",
3671
+ required: true,
3672
+ component: "password"
3673
+ },
3674
+ {
3675
+ name: "configMode",
3676
+ title: "Azure OpenAI configuration",
3677
+ required: true,
3678
+ defaultValue: "base-url",
3679
+ component: "select",
3680
+ options: [
3681
+ { label: "Use a base URL", value: "base-url" },
3682
+ { label: "Use an Azure resource name", value: "resource-name" }
3683
+ ]
3684
+ },
3685
+ {
3686
+ name: "baseUrl",
3687
+ title: "Azure OpenAI base URL",
3688
+ required: true,
3689
+ component: "text",
3690
+ placeholder: "https://my-resource.openai.azure.com/openai/v1",
3691
+ dependsOn: { field: "configMode", equals: "base-url" }
3692
+ },
3693
+ {
3694
+ name: "resourceName",
3695
+ title: "Azure resource name",
3696
+ required: true,
3697
+ component: "text",
3698
+ placeholder: "my-resource",
3699
+ dependsOn: { field: "configMode", equals: "resource-name" }
3700
+ },
3701
+ {
3702
+ name: "modelId",
3703
+ title: "Model ID",
3704
+ required: true,
3705
+ component: params.modelIdsByProvider[provider.id]?.length ? "select" : "text",
3706
+ options: params.modelIdsByProvider[provider.id]?.length ? toSelectOptions(params.modelIdsByProvider[provider.id]) : void 0,
3707
+ defaultValue: params.modelIdsByProvider[provider.id]?.[0] || "gpt-4.1"
3708
+ },
3709
+ {
3710
+ name: "deploymentName",
3711
+ title: "Deployment name",
3712
+ component: "text",
3713
+ placeholder: "gpt-4.1"
3714
+ },
3715
+ {
3716
+ name: "apiVersion",
3717
+ title: "API version",
3718
+ component: "text",
3719
+ defaultValue: "2025-03-01-preview",
3720
+ placeholder: "2025-03-01-preview"
3721
+ }
3722
+ ];
3723
+ case "amazon-bedrock":
3724
+ return [
3725
+ {
3726
+ name: "authMode",
3727
+ title: "Amazon Bedrock authentication",
3728
+ required: true,
3729
+ defaultValue: "profile",
3730
+ component: "select",
3731
+ options: [
3732
+ { label: "Use an AWS profile", value: "profile" },
3733
+ { label: "Paste AWS access keys", value: "access-keys" }
3734
+ ]
3735
+ },
3736
+ {
3737
+ name: "profile",
3738
+ title: "AWS profile name",
3739
+ required: true,
3740
+ defaultValue: "default",
3741
+ component: "text",
3742
+ placeholder: "default",
3743
+ dependsOn: { field: "authMode", equals: "profile" }
3744
+ },
3745
+ {
3746
+ name: "accessKeyId",
3747
+ title: "AWS access key ID",
3748
+ required: true,
3749
+ component: "text",
3750
+ dependsOn: { field: "authMode", equals: "access-keys" }
3751
+ },
3752
+ {
3753
+ name: "secretAccessKey",
3754
+ title: "AWS secret access key",
3755
+ required: true,
3756
+ component: "password",
3757
+ dependsOn: { field: "authMode", equals: "access-keys" }
3758
+ },
3759
+ {
3760
+ name: "sessionToken",
3761
+ title: "AWS session token",
3762
+ component: "password",
3763
+ dependsOn: { field: "authMode", equals: "access-keys" }
3764
+ },
3765
+ {
3766
+ name: "region",
3767
+ title: "AWS region",
3768
+ required: true,
3769
+ defaultValue: "us-east-1",
3770
+ component: "text",
3771
+ placeholder: "us-east-1"
3772
+ },
3773
+ {
3774
+ name: "modelId",
3775
+ title: "Bedrock model ID",
3776
+ required: true,
3777
+ component: params.modelIdsByProvider[provider.id]?.length ? "select" : "text",
3778
+ options: params.modelIdsByProvider[provider.id]?.length ? toSelectOptions(params.modelIdsByProvider[provider.id]) : void 0,
3779
+ defaultValue: params.modelIdsByProvider[provider.id]?.[0] || "anthropic.claude-3-7-sonnet-20250219-v1:0"
3780
+ }
3781
+ ];
3782
+ default:
3783
+ return [
3784
+ {
3785
+ name: "apiKey",
3786
+ title: `${provider.label} API key`,
3787
+ required: true,
3788
+ component: "password"
3789
+ }
3790
+ ];
3791
+ }
3792
+ }
3793
+ function buildProviderFormContract(params) {
3794
+ if (params.provider.flow === "oauth") {
3795
+ return null;
3796
+ }
3797
+ const fields = getProviderFormFields(params);
3798
+ const properties = Object.fromEntries(fields.map((field) => [field.name, buildProperty(field)]));
3799
+ const required = fields.filter((field) => field.required).map((field) => field.name);
3800
+ const fieldHints = Object.fromEntries(fields.map((field) => [field.name, buildFieldUiHint(field)]));
3801
+ return {
3802
+ inputSchema: {
3803
+ type: "object",
3804
+ properties,
3805
+ required
3806
+ },
3807
+ uiHints: {
3808
+ submitLabel: "Save credentials",
3809
+ order: fields.map((field) => field.name),
3810
+ fields: fieldHints
3811
+ }
3812
+ };
3813
+ }
3814
+ function getOAuthDescriptor(params, provider) {
3815
+ return {
3816
+ id: provider.id,
3817
+ label: provider.name || getProviderLabel(provider.id),
3818
+ hint: getProviderHint(provider.id),
3819
+ flow: "oauth",
3820
+ authMode: "oauth",
3821
+ configured: params.authStorage.hasAuth(provider.id),
3822
+ canLogin: true,
3823
+ canLogout: params.authStorage.hasAuth(provider.id),
3824
+ inputSchema: null,
3825
+ uiHints: null,
3826
+ oauth: { usesCallbackServer: provider.usesCallbackServer }
3827
+ };
3828
+ }
3829
+ function getApiKeyDescriptor(params, provider) {
3830
+ const contract = buildProviderFormContract({
3831
+ provider,
3832
+ modelIdsByProvider: params.modelIdsByProvider
3833
+ });
3834
+ return {
3835
+ id: provider.id,
3836
+ label: provider.label,
3837
+ hint: provider.hint,
3838
+ flow: provider.flow,
3839
+ authMode: "api-key",
3840
+ configured: params.authStorage.hasAuth(provider.id),
3841
+ canLogin: true,
3842
+ canLogout: params.authStorage.hasAuth(provider.id),
3843
+ inputSchema: contract?.inputSchema ?? null,
3844
+ uiHints: contract?.uiHints ?? null,
3845
+ oauth: null
3846
+ };
3847
+ }
3848
+ function buildProviderCatalog(params) {
3849
+ const oauthProviders = getBrowserAuthProviderOptions(params.browserProviders).map(
3850
+ (provider) => getOAuthDescriptor(
3851
+ params,
3852
+ params.browserProviders.find((item) => item.id === provider.id) ?? { id: provider.id, name: provider.label }
3853
+ )
3854
+ );
3855
+ const apiKeyProviders = getApiKeyProviderOptions(params.apiKeyProviderIds).map((provider) => getApiKeyDescriptor(params, provider));
3856
+ return [...oauthProviders, ...apiKeyProviders].sort((left, right) => left.label.localeCompare(right.label));
3857
+ }
3858
+ function listProviderOptions(params) {
3859
+ return [
3860
+ ...getBrowserAuthProviderOptions(params.browserProviders),
3861
+ ...getApiKeyProviderOptions(params.apiKeyProviderIds)
3862
+ ].sort((left, right) => left.label.localeCompare(right.label));
3863
+ }
3864
+ function findProviderOption(params) {
3865
+ return listProviderOptions({
3866
+ browserProviders: params.browserProviders,
3867
+ apiKeyProviderIds: params.apiKeyProviderIds
3868
+ }).find((provider) => provider.id === params.providerId);
3869
+ }
3870
+ function trimOrUndefined2(value) {
3871
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
3872
+ }
3873
+ function validateProviderLoginInput(params) {
3874
+ const fields = getProviderFormFields({
3875
+ provider: params.provider,
3876
+ modelIdsByProvider: params.modelIdsByProvider
3877
+ });
3878
+ const values = {};
3879
+ for (const field of fields) {
3880
+ if (field.dependsOn) {
3881
+ const dependsOnValue = values[field.dependsOn.field] ?? trimOrUndefined2(params.value[field.dependsOn.field]);
3882
+ if (dependsOnValue !== field.dependsOn.equals) {
3883
+ continue;
3884
+ }
3885
+ }
3886
+ const rawValue = params.value[field.name];
3887
+ const normalized = trimOrUndefined2(rawValue);
3888
+ if (field.required && !normalized) {
3889
+ return { ok: false, error: `Missing required field: ${field.name}` };
3890
+ }
3891
+ if (normalized) {
3892
+ values[field.name] = normalized;
3893
+ } else if (field.defaultValue) {
3894
+ values[field.name] = field.defaultValue;
3895
+ }
3896
+ }
3897
+ return { ok: true, value: values };
3898
+ }
3899
+ async function applyProviderLogin(params) {
3900
+ const provider = params.provider;
3901
+ switch (provider.flow) {
3902
+ case "custom-openai": {
3903
+ await saveCustomOpenAiConfig({
3904
+ ...params,
3905
+ apiKey: params.input.apiKey,
3906
+ baseUrl: params.input.baseUrl,
3907
+ modelId: params.input.modelId
3908
+ });
3909
+ return { providerId: provider.id, preferredModelId: params.input.modelId };
3910
+ }
3911
+ case "azure-openai-responses": {
3912
+ const baseUrl = params.input.baseUrl ?? (params.input.resourceName ? `https://${params.input.resourceName}.openai.azure.com/openai/v1` : void 0);
3913
+ if (!baseUrl) {
3914
+ throw new Error("Azure login requires either baseUrl or resourceName.");
3915
+ }
3916
+ const builtInModelIds = new Set(params.modelIdsByProvider[provider.id] ?? []);
3917
+ await saveAzureConfig({
3918
+ ...params,
3919
+ apiKey: params.input.apiKey,
3920
+ baseUrl,
3921
+ modelId: params.input.modelId,
3922
+ deploymentName: params.input.deploymentName,
3923
+ apiVersion: params.input.apiVersion,
3924
+ useCustomModel: !builtInModelIds.has(params.input.modelId)
3925
+ });
3926
+ return { providerId: provider.id, preferredModelId: params.input.modelId };
3927
+ }
3928
+ case "amazon-bedrock": {
3929
+ const authMode = params.input.authMode ?? "profile";
3930
+ await saveBedrockConfig({
3931
+ ...params,
3932
+ region: params.input.region,
3933
+ modelId: params.input.modelId,
3934
+ profile: authMode === "profile" ? params.input.profile : void 0,
3935
+ accessKeyId: authMode === "access-keys" ? params.input.accessKeyId : void 0,
3936
+ secretAccessKey: authMode === "access-keys" ? params.input.secretAccessKey : void 0,
3937
+ sessionToken: authMode === "access-keys" ? params.input.sessionToken : void 0
3938
+ });
3939
+ return { providerId: provider.id, preferredModelId: params.input.modelId };
3940
+ }
3941
+ default: {
3942
+ await saveGenericApiKey({
3943
+ ...params,
3944
+ providerId: provider.id,
3945
+ apiKey: params.input.apiKey
3946
+ });
3947
+ return { providerId: provider.id };
3948
+ }
3949
+ }
3950
+ }
3951
+ async function refreshSessionAfterCredentialChange(params) {
3952
+ params.modelRegistry.refresh();
3953
+ const availableModels = params.modelRegistry.getAvailable();
3954
+ 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;
3955
+ const currentModel = params.session.model;
3956
+ const currentModelStillAvailable = currentModel ? availableModels.some((model) => model.provider === currentModel.provider && model.id === currentModel.id) : false;
3957
+ if (currentModel && !currentModelStillAvailable) {
3958
+ if (preferredModel) {
3959
+ await params.session.setModel(preferredModel);
3960
+ return;
3961
+ }
3962
+ if (availableModels[0]) {
3963
+ await params.session.setModel(availableModels[0]);
3964
+ }
3965
+ return;
3966
+ }
3967
+ if (!currentModel && preferredModel) {
3968
+ await params.session.setModel(preferredModel);
3969
+ return;
3970
+ }
3971
+ if (!currentModel && availableModels[0]) {
3972
+ await params.session.setModel(availableModels[0]);
3973
+ }
3974
+ }
3975
+ function serializeModels(params) {
3976
+ return params.models.map((model) => ({
3977
+ provider: model.provider,
3978
+ providerLabel: getProviderLabel(model.provider),
3979
+ id: model.id,
3980
+ name: typeof model.name === "string" && model.name.length > 0 ? model.name : model.id,
3981
+ reasoning: model.reasoning === true,
3982
+ contextWindow: typeof model.contextWindow === "number" ? model.contextWindow : null,
3983
+ maxTokens: typeof model.maxTokens === "number" ? model.maxTokens : null,
3984
+ configured: params.authStorage.hasAuth(model.provider)
3985
+ }));
3986
+ }
3987
+
3988
+ // src/server/oauthFlowManager.ts
3989
+ var import_node_crypto = require("node:crypto");
3990
+ function normalizeError(error) {
3991
+ return error instanceof Error ? error.message : String(error);
3992
+ }
3993
+ function buildPromptStep(params) {
3994
+ return {
3995
+ type: params.type,
3996
+ stepToken: params.state.stepToken,
3997
+ providerId: params.state.providerId,
3998
+ message: params.prompt.message,
3999
+ inputSchema: {
4000
+ type: "object",
4001
+ properties: {
4002
+ value: {
4003
+ type: "string",
4004
+ title: params.type === "manual-code" ? "Verification Code" : "Value",
4005
+ ...params.prompt.placeholder ? { default: params.prompt.placeholder } : {}
4006
+ }
4007
+ },
4008
+ required: params.prompt.allowEmpty ? [] : ["value"]
4009
+ },
4010
+ uiHints: {
4011
+ submitLabel: "Continue",
4012
+ order: ["value"],
4013
+ fields: {
4014
+ value: {
4015
+ component: "text",
4016
+ ...params.prompt.placeholder ? { placeholder: params.prompt.placeholder } : {}
4017
+ }
4018
+ }
4019
+ }
4020
+ };
4021
+ }
4022
+ function buildAuthStep(params) {
4023
+ return {
4024
+ type: "open-url",
4025
+ stepToken: params.state.stepToken,
4026
+ providerId: params.state.providerId,
4027
+ url: params.authInfo.url,
4028
+ instructions: params.authInfo.instructions,
4029
+ usesCallbackServer: params.state.usesCallbackServer,
4030
+ pending: true
4031
+ };
4032
+ }
4033
+ var OAuthFlowManager = class {
4034
+ flows = /* @__PURE__ */ new Map();
4035
+ emitStep(state, step) {
4036
+ state.currentStep = step;
4037
+ if (state.waitingForStep) {
4038
+ const waiting = state.waitingForStep;
4039
+ state.waitingForStep = void 0;
4040
+ waiting.resolve(step);
4041
+ return;
4042
+ }
4043
+ state.queuedSteps.push(step);
4044
+ }
4045
+ nextStep(state) {
4046
+ const queued = state.queuedSteps.shift();
4047
+ if (queued) {
4048
+ return Promise.resolve(queued);
4049
+ }
4050
+ if (state.currentStep) {
4051
+ return Promise.resolve(state.currentStep);
4052
+ }
4053
+ return new Promise((resolve2) => {
4054
+ state.waitingForStep = { resolve: resolve2 };
4055
+ });
4056
+ }
4057
+ async start(params) {
4058
+ const state = {
4059
+ stepToken: (0, import_node_crypto.randomUUID)(),
4060
+ providerId: params.providerId,
4061
+ usesCallbackServer: params.usesCallbackServer,
4062
+ pendingInput: void 0,
4063
+ currentStep: void 0,
4064
+ queuedSteps: [],
4065
+ completed: false
4066
+ };
4067
+ this.flows.set(state.stepToken, state);
4068
+ void params.authStorage.login(params.providerId, {
4069
+ onAuth: (info) => {
4070
+ this.emitStep(state, buildAuthStep({ state, authInfo: info }));
4071
+ },
4072
+ onPrompt: async (prompt) => {
4073
+ const step = buildPromptStep({ state, type: "prompt", prompt });
4074
+ this.emitStep(state, step);
4075
+ return await new Promise((resolve2) => {
4076
+ state.pendingInput = { type: "prompt", resolve: resolve2 };
4077
+ });
4078
+ },
4079
+ onManualCodeInput: async () => {
4080
+ const step = buildPromptStep({
4081
+ state,
4082
+ type: "manual-code",
4083
+ prompt: {
4084
+ message: "Enter the verification code from the provider."
4085
+ }
4086
+ });
4087
+ this.emitStep(state, step);
4088
+ return await new Promise((resolve2) => {
4089
+ state.pendingInput = { type: "manual-code", resolve: resolve2 };
4090
+ });
4091
+ },
4092
+ onProgress: (_message) => {
4093
+ }
4094
+ }).then(() => {
4095
+ state.completed = true;
4096
+ this.emitStep(state, {
4097
+ type: "complete",
4098
+ stepToken: state.stepToken,
4099
+ providerId: state.providerId
4100
+ });
4101
+ }).catch((error) => {
4102
+ state.completed = true;
4103
+ this.emitStep(state, {
4104
+ type: "error",
4105
+ stepToken: state.stepToken,
4106
+ providerId: state.providerId,
4107
+ error: normalizeError(error)
4108
+ });
4109
+ });
4110
+ return await this.nextStep(state);
4111
+ }
4112
+ async continue(params) {
4113
+ const state = this.flows.get(params.stepToken);
4114
+ if (!state) {
4115
+ throw new Error(`Unknown OAuth step token: ${params.stepToken}`);
4116
+ }
4117
+ if (state.pendingInput) {
4118
+ const nextValue = params.value?.trim();
4119
+ state.pendingInput.resolve(nextValue ?? "");
4120
+ state.pendingInput = void 0;
4121
+ state.currentStep = void 0;
4122
+ }
4123
+ const step = await this.nextStep(state);
4124
+ if (step.type === "complete" || step.type === "error") {
4125
+ this.flows.delete(state.stepToken);
4126
+ }
4127
+ return step;
4128
+ }
4129
+ };
4130
+
3053
4131
  // src/server/agentServer.ts
3054
4132
  function generateMessageId() {
3055
4133
  return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
@@ -3069,6 +4147,40 @@ function extractLastUserText(messages) {
3069
4147
  }
3070
4148
  return void 0;
3071
4149
  }
4150
+ function extractAskUserToolResponse(messages) {
4151
+ for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
4152
+ const message = messages[messageIndex];
4153
+ if (message.role !== "assistant") {
4154
+ continue;
4155
+ }
4156
+ for (let partIndex = message.parts.length - 1; partIndex >= 0; partIndex -= 1) {
4157
+ const part = message.parts[partIndex];
4158
+ if (part.type === "dynamic-tool" && part.toolName === "ask_user" && part.state === "output-available" && typeof part.toolCallId === "string") {
4159
+ const response = parseAskUserResponseFromToolOutput(part.output);
4160
+ if (response) {
4161
+ return {
4162
+ toolCallId: part.toolCallId,
4163
+ response
4164
+ };
4165
+ }
4166
+ }
4167
+ }
4168
+ }
4169
+ return void 0;
4170
+ }
4171
+ function normalizeSlashCommands(commands) {
4172
+ const seen = /* @__PURE__ */ new Set();
4173
+ return commands.filter((command) => typeof command.name === "string" && command.name.trim().length > 0).sort((left, right) => left.name.localeCompare(right.name)).filter((command) => {
4174
+ if (seen.has(command.name)) {
4175
+ return false;
4176
+ }
4177
+ seen.add(command.name);
4178
+ return true;
4179
+ }).map((command) => ({
4180
+ name: command.name,
4181
+ description: command.description?.trim() || null
4182
+ }));
4183
+ }
3072
4184
  async function waitForIdle(session, timeoutMs = 3e4) {
3073
4185
  if (!session.isStreaming) {
3074
4186
  return;
@@ -3145,6 +4257,17 @@ function convertSessionEntriesToUIMessages(entries) {
3145
4257
  const parts = [];
3146
4258
  for (const block of content) {
3147
4259
  if (block.type === "text" && typeof block.text === "string") {
4260
+ const askUserRequest = parseAskUserRequestFromText(block.text);
4261
+ if (askUserRequest) {
4262
+ parts.push({
4263
+ type: "dynamic-tool",
4264
+ toolCallId: createAskUserToolCallId(askUserRequest),
4265
+ toolName: "ask_user",
4266
+ state: "input-available",
4267
+ input: askUserRequest
4268
+ });
4269
+ continue;
4270
+ }
3148
4271
  parts.push({ type: "text", text: block.text });
3149
4272
  } else if (block.type === "thinking" && typeof block.thinking === "string") {
3150
4273
  parts.push({ type: "reasoning", text: block.thinking, state: "complete" });
@@ -3189,8 +4312,8 @@ var FS_IGNORE = /* @__PURE__ */ new Set([
3189
4312
  ".svelte-kit"
3190
4313
  ]);
3191
4314
  function resolveSafePath(cwd, requestPath) {
3192
- const resolved = (0, import_node_path2.resolve)(cwd, requestPath);
3193
- const normalizedCwd = cwd.endsWith(import_node_path2.sep) ? cwd : cwd + import_node_path2.sep;
4315
+ const resolved = (0, import_node_path3.resolve)(cwd, requestPath);
4316
+ const normalizedCwd = cwd.endsWith(import_node_path3.sep) ? cwd : cwd + import_node_path3.sep;
3194
4317
  if (resolved !== cwd && !resolved.startsWith(normalizedCwd)) {
3195
4318
  throw new Error("Path escapes working directory");
3196
4319
  }
@@ -3207,7 +4330,7 @@ async function buildTree(params) {
3207
4330
  }
3208
4331
  let entries;
3209
4332
  try {
3210
- entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
4333
+ entries = await (0, import_promises3.readdir)(dir, { withFileTypes: true });
3211
4334
  } catch {
3212
4335
  return [];
3213
4336
  }
@@ -3227,8 +4350,8 @@ async function buildTree(params) {
3227
4350
  if (ignore.has(entry.name)) {
3228
4351
  continue;
3229
4352
  }
3230
- const fullPath = (0, import_node_path2.join)(dir, entry.name);
3231
- const relativePath = (0, import_node_path2.relative)(cwd, fullPath);
4353
+ const fullPath = (0, import_node_path3.join)(dir, entry.name);
4354
+ const relativePath = (0, import_node_path3.relative)(cwd, fullPath);
3232
4355
  if (entry.isDirectory()) {
3233
4356
  const children = await buildTree({
3234
4357
  dir: fullPath,
@@ -3254,7 +4377,7 @@ async function walkFiles(params) {
3254
4377
  }
3255
4378
  let entries;
3256
4379
  try {
3257
- entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
4380
+ entries = await (0, import_promises3.readdir)(dir, { withFileTypes: true });
3258
4381
  } catch {
3259
4382
  return;
3260
4383
  }
@@ -3265,8 +4388,8 @@ async function walkFiles(params) {
3265
4388
  if (ignore.has(entry.name)) {
3266
4389
  continue;
3267
4390
  }
3268
- const fullPath = (0, import_node_path2.join)(dir, entry.name);
3269
- const relativePath = (0, import_node_path2.relative)(cwd, fullPath);
4391
+ const fullPath = (0, import_node_path3.join)(dir, entry.name);
4392
+ const relativePath = (0, import_node_path3.relative)(cwd, fullPath);
3270
4393
  if (entry.isDirectory()) {
3271
4394
  await walkFiles({ dir: fullPath, cwd, pattern, ignore, maxResults, results });
3272
4395
  } else if (entry.isFile() && pattern.test(relativePath)) {
@@ -3275,10 +4398,40 @@ async function walkFiles(params) {
3275
4398
  }
3276
4399
  }
3277
4400
  async function createAgentServer(params) {
3278
- const { port, sessionManager, modelRegistry, context, onCreateSession, onResumeSession } = params;
4401
+ const { port, sessionManager, modelRegistry, authRuntime, context, onCreateSession, onResumeSession, authToken } = params;
3279
4402
  let activeSession = params.session;
4403
+ const pendingAskUserRequests = /* @__PURE__ */ new Map();
4404
+ const oauthFlowManager = new OAuthFlowManager();
3280
4405
  const app = new Hono2();
4406
+ function getModelIdsByProvider() {
4407
+ const values = {};
4408
+ for (const model of modelRegistry.getAll()) {
4409
+ if (!values[model.provider]) {
4410
+ values[model.provider] = [];
4411
+ }
4412
+ values[model.provider].push(model.id);
4413
+ }
4414
+ for (const providerId of Object.keys(values)) {
4415
+ values[providerId] = [...new Set(values[providerId])].sort((left, right) => left.localeCompare(right));
4416
+ }
4417
+ return values;
4418
+ }
4419
+ function getProviderCatalog() {
4420
+ return buildProviderCatalog({
4421
+ authStorage: authRuntime.authStorage,
4422
+ browserProviders: authRuntime.authStorage.getOAuthProviders(),
4423
+ apiKeyProviderIds: [...new Set(modelRegistry.getAll().map((model) => model.provider))].sort((left, right) => left.localeCompare(right)),
4424
+ modelIdsByProvider: getModelIdsByProvider()
4425
+ });
4426
+ }
3281
4427
  app.use("/*", cors({ origin: "*" }));
4428
+ app.use("/*", async (c, next) => {
4429
+ const unauthorizedResponse = authorizeServerRequest(c.req.raw, authToken);
4430
+ if (unauthorizedResponse) {
4431
+ return unauthorizedResponse;
4432
+ }
4433
+ await next();
4434
+ });
3282
4435
  app.get("/api/health", (c) => {
3283
4436
  return c.json({ ok: true });
3284
4437
  });
@@ -3291,9 +4444,23 @@ async function createAgentServer(params) {
3291
4444
  app.post("/api/chat", async (c) => {
3292
4445
  const body = await c.req.json();
3293
4446
  const messages = body.messages ?? [];
3294
- const userMessage = extractLastUserText(messages);
4447
+ if (body.sessionId) {
4448
+ try {
4449
+ activeSession = await onResumeSession(body.sessionId);
4450
+ } catch (error) {
4451
+ const msg = error instanceof Error ? error.message : String(error);
4452
+ return c.json({ error: `Failed to resume session: ${msg}` }, 400);
4453
+ }
4454
+ }
4455
+ const sessionId = body.sessionId?.trim() || activeSession.id?.trim() || "active";
4456
+ const askUserResponse = extractAskUserToolResponse(messages);
4457
+ const pendingAskUser = pendingAskUserRequests.get(sessionId);
4458
+ const userMessage = askUserResponse ? pendingAskUser && pendingAskUser.toolCallId === askUserResponse.toolCallId ? formatAskUserResponsePrompt(askUserResponse.response) : void 0 : extractLastUserText(messages);
3295
4459
  if (!userMessage) {
3296
- return c.json({ error: "No user message found" }, 400);
4460
+ return c.json({ error: askUserResponse ? "No matching pending ask_user request found" : "No user message found" }, 400);
4461
+ }
4462
+ if (askUserResponse) {
4463
+ pendingAskUserRequests.delete(sessionId);
3297
4464
  }
3298
4465
  if (activeSession.isStreaming) {
3299
4466
  await activeSession.abort();
@@ -3313,6 +4480,9 @@ async function createAgentServer(params) {
3313
4480
  const bridge = createEventBridge({
3314
4481
  messageId,
3315
4482
  onChunk: writeChunk,
4483
+ onAskUser: ({ toolCallId, request }) => {
4484
+ pendingAskUserRequests.set(sessionId, { toolCallId, request });
4485
+ },
3316
4486
  onDone: () => {
3317
4487
  writeChunk({ type: "finish-step" });
3318
4488
  writeChunk({ type: "finish" });
@@ -3331,7 +4501,14 @@ async function createAgentServer(params) {
3331
4501
  unsubscribe();
3332
4502
  }
3333
4503
  });
3334
- activeSession.prompt(userMessage).catch((error) => {
4504
+ activeSession.prompt(userMessage).then(() => {
4505
+ if (!activeSession.isStreaming) {
4506
+ unsubscribe();
4507
+ writeChunk({ type: "finish-step" });
4508
+ writeChunk({ type: "finish" });
4509
+ controller.close();
4510
+ }
4511
+ }).catch((error) => {
3335
4512
  const errorMessage = error instanceof Error ? error.message : String(error);
3336
4513
  writeChunk({ type: "error", errorText: errorMessage });
3337
4514
  writeChunk({ type: "finish-step" });
@@ -3433,8 +4610,161 @@ async function createAgentServer(params) {
3433
4610
  app.get("/api/models", (c) => {
3434
4611
  return c.json({
3435
4612
  current: activeSession.model ? { provider: activeSession.model.provider, id: activeSession.model.id } : null,
3436
- available: modelRegistry.getAvailable()
4613
+ thinkingLevel: activeSession.thinkingLevel,
4614
+ availableThinkingLevels: activeSession.getAvailableThinkingLevels(),
4615
+ available: serializeModels({
4616
+ models: modelRegistry.getAvailable(),
4617
+ authStorage: authRuntime.authStorage
4618
+ })
4619
+ });
4620
+ });
4621
+ app.get("/api/slash-commands", (c) => {
4622
+ return c.json({
4623
+ commands: normalizeSlashCommands(activeSession.listCommands())
4624
+ });
4625
+ });
4626
+ app.get("/api/auth/providers", (c) => {
4627
+ return c.json({
4628
+ providers: getProviderCatalog()
4629
+ });
4630
+ });
4631
+ app.post("/api/auth/providers/:providerId/login", async (c) => {
4632
+ const providerId = c.req.param("providerId");
4633
+ const provider = findProviderOption({
4634
+ providerId,
4635
+ browserProviders: authRuntime.authStorage.getOAuthProviders(),
4636
+ apiKeyProviderIds: [...new Set(modelRegistry.getAll().map((model) => model.provider))]
4637
+ });
4638
+ if (!provider) {
4639
+ return c.json({ error: `Unknown provider: ${providerId}` }, 404);
4640
+ }
4641
+ if (provider.flow === "oauth") {
4642
+ const step = await oauthFlowManager.start({
4643
+ authStorage: authRuntime.authStorage,
4644
+ providerId,
4645
+ usesCallbackServer: authRuntime.authStorage.getOAuthProviders().find((item) => item.id === providerId)?.usesCallbackServer
4646
+ });
4647
+ return c.json({ step });
4648
+ }
4649
+ const body = await c.req.json();
4650
+ const validated = validateProviderLoginInput({
4651
+ provider,
4652
+ modelIdsByProvider: getModelIdsByProvider(),
4653
+ value: body
3437
4654
  });
4655
+ if (!validated.ok) {
4656
+ return c.json({ error: validated.error }, 400);
4657
+ }
4658
+ try {
4659
+ const result = await applyProviderLogin({
4660
+ ...authRuntime,
4661
+ provider,
4662
+ input: validated.value,
4663
+ modelIdsByProvider: getModelIdsByProvider()
4664
+ });
4665
+ await refreshSessionAfterCredentialChange({
4666
+ session: activeSession,
4667
+ modelRegistry,
4668
+ preferredProviderId: result.providerId,
4669
+ preferredModelId: result.preferredModelId
4670
+ });
4671
+ return c.json({
4672
+ ok: true,
4673
+ providerId,
4674
+ current: activeSession.model ? { provider: activeSession.model.provider, id: activeSession.model.id } : null,
4675
+ thinkingLevel: activeSession.thinkingLevel
4676
+ });
4677
+ } catch (error) {
4678
+ const message = error instanceof Error ? error.message : String(error);
4679
+ return c.json({ error: message }, 400);
4680
+ }
4681
+ });
4682
+ app.post("/api/auth/providers/:providerId/oauth/continue", async (c) => {
4683
+ const providerId = c.req.param("providerId");
4684
+ const body = await c.req.json();
4685
+ if (!body.stepToken) {
4686
+ return c.json({ error: "Missing required field: stepToken" }, 400);
4687
+ }
4688
+ try {
4689
+ const value = typeof body.values?.value === "string" ? body.values.value : void 0;
4690
+ const step = await oauthFlowManager.continue({
4691
+ stepToken: body.stepToken,
4692
+ value
4693
+ });
4694
+ if (step.type === "complete") {
4695
+ modelRegistry.refresh();
4696
+ await refreshSessionAfterCredentialChange({
4697
+ session: activeSession,
4698
+ modelRegistry,
4699
+ preferredProviderId: providerId
4700
+ });
4701
+ }
4702
+ return c.json({ step });
4703
+ } catch (error) {
4704
+ const message = error instanceof Error ? error.message : String(error);
4705
+ return c.json({ error: message }, 400);
4706
+ }
4707
+ });
4708
+ app.post("/api/auth/providers/:providerId/logout", async (c) => {
4709
+ const providerId = c.req.param("providerId");
4710
+ const provider = findProviderOption({
4711
+ providerId,
4712
+ browserProviders: authRuntime.authStorage.getOAuthProviders(),
4713
+ apiKeyProviderIds: [...new Set(modelRegistry.getAll().map((model) => model.provider))]
4714
+ });
4715
+ if (!provider) {
4716
+ return c.json({ error: `Unknown provider: ${providerId}` }, 404);
4717
+ }
4718
+ try {
4719
+ if (provider.flow === "oauth") {
4720
+ authRuntime.authStorage.logout(providerId);
4721
+ } else {
4722
+ await clearProviderConfig({
4723
+ ...authRuntime,
4724
+ providerId
4725
+ });
4726
+ }
4727
+ modelRegistry.refresh();
4728
+ await refreshSessionAfterCredentialChange({
4729
+ session: activeSession,
4730
+ modelRegistry
4731
+ });
4732
+ return c.json({
4733
+ ok: true,
4734
+ providerId,
4735
+ current: activeSession.model ? { provider: activeSession.model.provider, id: activeSession.model.id } : null,
4736
+ thinkingLevel: activeSession.thinkingLevel
4737
+ });
4738
+ } catch (error) {
4739
+ const message = error instanceof Error ? error.message : String(error);
4740
+ return c.json({ error: message }, 400);
4741
+ }
4742
+ });
4743
+ app.post("/api/models/select", async (c) => {
4744
+ const body = await c.req.json();
4745
+ const provider = body.provider?.trim();
4746
+ const modelId = body.modelId?.trim();
4747
+ if (!provider || !modelId) {
4748
+ return c.json({ error: "Missing required fields: provider, modelId" }, 400);
4749
+ }
4750
+ const model = modelRegistry.find(provider, modelId);
4751
+ if (!model) {
4752
+ return c.json({ error: `Model not found: ${provider}/${modelId}` }, 404);
4753
+ }
4754
+ try {
4755
+ await activeSession.setModel(model);
4756
+ if (body.thinkingLevel) {
4757
+ activeSession.setThinkingLevel(body.thinkingLevel);
4758
+ }
4759
+ return c.json({
4760
+ current: activeSession.model ? { provider: activeSession.model.provider, id: activeSession.model.id } : null,
4761
+ thinkingLevel: activeSession.thinkingLevel,
4762
+ availableThinkingLevels: activeSession.getAvailableThinkingLevels()
4763
+ });
4764
+ } catch (error) {
4765
+ const message = error instanceof Error ? error.message : String(error);
4766
+ return c.json({ error: message }, 400);
4767
+ }
3438
4768
  });
3439
4769
  app.post("/api/sessions/:sessionId/resume", async (c) => {
3440
4770
  const sessionId = c.req.param("sessionId");
@@ -3500,11 +4830,11 @@ async function createAgentServer(params) {
3500
4830
  }
3501
4831
  try {
3502
4832
  const resolved = resolveSafePath(context.cwd, filePath);
3503
- const fileStat = await (0, import_promises2.stat)(resolved);
4833
+ const fileStat = await (0, import_promises3.stat)(resolved);
3504
4834
  if (!fileStat.isFile()) {
3505
4835
  return c.json({ error: "Path is not a file" }, 400);
3506
4836
  }
3507
- const content = await (0, import_promises2.readFile)(resolved, "utf-8");
4837
+ const content = await (0, import_promises3.readFile)(resolved, "utf-8");
3508
4838
  return c.json({
3509
4839
  path: filePath,
3510
4840
  content,
@@ -3524,9 +4854,9 @@ async function createAgentServer(params) {
3524
4854
  }
3525
4855
  try {
3526
4856
  const resolved = resolveSafePath(context.cwd, body.path);
3527
- await (0, import_promises2.mkdir)((0, import_node_path2.join)(resolved, ".."), { recursive: true });
3528
- await (0, import_promises2.writeFile)(resolved, body.content, "utf-8");
3529
- const fileStat = await (0, import_promises2.stat)(resolved);
4857
+ await (0, import_promises3.mkdir)((0, import_node_path3.join)(resolved, ".."), { recursive: true });
4858
+ await (0, import_promises3.writeFile)(resolved, body.content, "utf-8");
4859
+ const fileStat = await (0, import_promises3.stat)(resolved);
3530
4860
  return c.json({ ok: true, path: body.path, size: fileStat.size });
3531
4861
  } catch (error) {
3532
4862
  const message = error instanceof Error ? error.message : String(error);
@@ -3540,7 +4870,7 @@ async function createAgentServer(params) {
3540
4870
  }
3541
4871
  try {
3542
4872
  const resolved = resolveSafePath(context.cwd, body.path);
3543
- await (0, import_promises2.mkdir)(resolved, { recursive: true });
4873
+ await (0, import_promises3.mkdir)(resolved, { recursive: true });
3544
4874
  return c.json({ ok: true, path: body.path });
3545
4875
  } catch (error) {
3546
4876
  const message = error instanceof Error ? error.message : String(error);
@@ -3555,8 +4885,8 @@ async function createAgentServer(params) {
3555
4885
  try {
3556
4886
  const resolvedFrom = resolveSafePath(context.cwd, body.from);
3557
4887
  const resolvedTo = resolveSafePath(context.cwd, body.to);
3558
- await (0, import_promises2.mkdir)((0, import_node_path2.join)(resolvedTo, ".."), { recursive: true });
3559
- await (0, import_promises2.rename)(resolvedFrom, resolvedTo);
4888
+ await (0, import_promises3.mkdir)((0, import_node_path3.join)(resolvedTo, ".."), { recursive: true });
4889
+ await (0, import_promises3.rename)(resolvedFrom, resolvedTo);
3560
4890
  return c.json({ ok: true, from: body.from, to: body.to });
3561
4891
  } catch (error) {
3562
4892
  const message = error instanceof Error ? error.message : String(error);
@@ -3574,8 +4904,8 @@ async function createAgentServer(params) {
3574
4904
  if (resolved === context.cwd) {
3575
4905
  return c.json({ error: "Cannot delete working directory" }, 400);
3576
4906
  }
3577
- const fileStat = await (0, import_promises2.stat)(resolved);
3578
- await (0, import_promises2.rm)(resolved, { recursive: fileStat.isDirectory() });
4907
+ const fileStat = await (0, import_promises3.stat)(resolved);
4908
+ await (0, import_promises3.rm)(resolved, { recursive: fileStat.isDirectory() });
3579
4909
  return c.json({ ok: true, path: filePath });
3580
4910
  } catch (error) {
3581
4911
  const message = error instanceof Error ? error.message : String(error);
@@ -3584,7 +4914,7 @@ async function createAgentServer(params) {
3584
4914
  }
3585
4915
  });
3586
4916
  const CLI_EXEC = process.env.DOCYRUS_CLI_EXECUTABLE || process.execPath;
3587
- const CLI_ENTRY = process.env.DOCYRUS_CLI_ENTRY || (0, import_node_path2.resolve)(process.argv[1] ? (0, import_node_path2.join)(process.argv[1], "..") : __dirname, "main.js");
4917
+ 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");
3588
4918
  const CLI_SCOPE = process.env.DOCYRUS_CLI_SCOPE;
3589
4919
  const CLI_TIMEOUT_MS = 3e4;
3590
4920
  function runCliCommand(args) {
@@ -3634,7 +4964,7 @@ async function createAgentServer(params) {
3634
4964
  }
3635
4965
  async function detectDevPort(cwd) {
3636
4966
  try {
3637
- const pkg = JSON.parse(await (0, import_promises2.readFile)((0, import_node_path2.join)(cwd, "package.json"), "utf-8"));
4967
+ const pkg = JSON.parse(await (0, import_promises3.readFile)((0, import_node_path3.join)(cwd, "package.json"), "utf-8"));
3638
4968
  const devScript = pkg.scripts?.dev;
3639
4969
  if (devScript) {
3640
4970
  const portFlag = devScript.match(/--port\s+(\d+)/);
@@ -3650,7 +4980,7 @@ async function createAgentServer(params) {
3650
4980
  }
3651
4981
  for (const name of ["vite.config.ts", "vite.config.mts", "vite.config.js"]) {
3652
4982
  try {
3653
- const content = await (0, import_promises2.readFile)((0, import_node_path2.join)(cwd, name), "utf-8");
4983
+ const content = await (0, import_promises3.readFile)((0, import_node_path3.join)(cwd, name), "utf-8");
3654
4984
  const portMatch = content.match(/port\s*:\s*(\d+)/);
3655
4985
  if (portMatch) {
3656
4986
  return Number(portMatch[1]);
@@ -3660,19 +4990,19 @@ async function createAgentServer(params) {
3660
4990
  }
3661
4991
  for (const name of ["next.config.ts", "next.config.mts", "next.config.js", "next.config.mjs"]) {
3662
4992
  try {
3663
- await (0, import_promises2.stat)((0, import_node_path2.join)(cwd, name));
4993
+ await (0, import_promises3.stat)((0, import_node_path3.join)(cwd, name));
3664
4994
  return 3e3;
3665
4995
  } catch {
3666
4996
  }
3667
4997
  }
3668
4998
  try {
3669
- await (0, import_promises2.stat)((0, import_node_path2.join)(cwd, "angular.json"));
4999
+ await (0, import_promises3.stat)((0, import_node_path3.join)(cwd, "angular.json"));
3670
5000
  return 4200;
3671
5001
  } catch {
3672
5002
  }
3673
5003
  for (const name of ["nuxt.config.ts", "nuxt.config.js"]) {
3674
5004
  try {
3675
- await (0, import_promises2.stat)((0, import_node_path2.join)(cwd, name));
5005
+ await (0, import_promises3.stat)((0, import_node_path3.join)(cwd, name));
3676
5006
  return 3e3;
3677
5007
  } catch {
3678
5008
  }
@@ -3702,14 +5032,14 @@ async function createAgentServer(params) {
3702
5032
  } catch {
3703
5033
  }
3704
5034
  try {
3705
- const pkg = JSON.parse(await (0, import_promises2.readFile)((0, import_node_path2.join)(cwd, "package.json"), "utf-8"));
5035
+ const pkg = JSON.parse(await (0, import_promises3.readFile)((0, import_node_path3.join)(cwd, "package.json"), "utf-8"));
3706
5036
  packageName = pkg.name ?? null;
3707
5037
  packageVersion = pkg.version ?? null;
3708
5038
  } catch {
3709
5039
  }
3710
5040
  cachedProjectInfo = {
3711
5041
  path: cwd,
3712
- folder: (0, import_node_path2.basename)(cwd),
5042
+ folder: (0, import_node_path3.basename)(cwd),
3713
5043
  repo,
3714
5044
  packageName,
3715
5045
  packageVersion
@@ -3824,6 +5154,140 @@ async function createAgentServer(params) {
3824
5154
  devUrl = null;
3825
5155
  return c.json({ status: "stopped", url: stoppedUrl, pid });
3826
5156
  });
5157
+ function gitExec(args, gitCwd) {
5158
+ return new Promise((res, rej) => {
5159
+ (0, import_node_child_process.execFile)("git", args, { cwd: gitCwd, maxBuffer: 50 * 1024 * 1024 }, (err, stdout) => {
5160
+ if (err) {
5161
+ return rej(err);
5162
+ }
5163
+ res(stdout);
5164
+ });
5165
+ });
5166
+ }
5167
+ app.get("/api/git/diff", async (c) => {
5168
+ const filterPath = c.req.query("path");
5169
+ const cwd = context.cwd;
5170
+ try {
5171
+ let branch;
5172
+ let hasHead = true;
5173
+ try {
5174
+ branch = (await gitExec(["rev-parse", "--abbrev-ref", "HEAD"], cwd)).trim() || void 0;
5175
+ } catch {
5176
+ hasHead = false;
5177
+ }
5178
+ const diffRef = hasHead ? ["diff", "HEAD"] : ["diff", "--cached"];
5179
+ const [nameStatusRaw, untrackedRaw, numstatRaw] = await Promise.all([
5180
+ gitExec([...diffRef, "--name-status"], cwd).catch(() => ""),
5181
+ gitExec(["ls-files", "--others", "--exclude-standard"], cwd).catch(() => ""),
5182
+ gitExec([...diffRef, "--numstat"], cwd).catch(() => "")
5183
+ ]);
5184
+ const entries = [];
5185
+ for (const line of nameStatusRaw.split("\n").filter(Boolean)) {
5186
+ const parts = line.split(" ");
5187
+ const code = parts[0];
5188
+ if (code.startsWith("R")) {
5189
+ entries.push({ path: parts[2], status: "renamed", oldPath: parts[1] });
5190
+ } else if (code.startsWith("C")) {
5191
+ entries.push({ path: parts[2], status: "copied", oldPath: parts[1] });
5192
+ } else if (code.startsWith("M")) {
5193
+ entries.push({ path: parts[1], status: "modified" });
5194
+ } else if (code.startsWith("A")) {
5195
+ entries.push({ path: parts[1], status: "added" });
5196
+ } else if (code.startsWith("D")) {
5197
+ entries.push({ path: parts[1], status: "deleted" });
5198
+ }
5199
+ }
5200
+ for (const filePath of untrackedRaw.split("\n").filter(Boolean)) {
5201
+ entries.push({ path: filePath, status: "untracked" });
5202
+ }
5203
+ const numstatMap = /* @__PURE__ */ new Map();
5204
+ for (const line of numstatRaw.split("\n").filter(Boolean)) {
5205
+ const match2 = line.match(/^(-|\d+)\t(-|\d+)\t(.+)$/);
5206
+ if (!match2) {
5207
+ continue;
5208
+ }
5209
+ const isBinary = match2[1] === "-" && match2[2] === "-";
5210
+ const additions = isBinary ? 0 : Number(match2[1]);
5211
+ const deletions = isBinary ? 0 : Number(match2[2]);
5212
+ let filePath = match2[3];
5213
+ const arrowIdx = filePath.indexOf(" => ");
5214
+ if (arrowIdx !== -1) {
5215
+ const braceStart = filePath.indexOf("{");
5216
+ if (braceStart !== -1 && braceStart < arrowIdx) {
5217
+ const braceEnd = filePath.indexOf("}", arrowIdx);
5218
+ if (braceEnd !== -1) {
5219
+ const prefix = filePath.slice(0, braceStart);
5220
+ const suffix = filePath.slice(braceEnd + 1);
5221
+ const newPart = filePath.slice(braceStart + 1, braceEnd).split(" => ")[1];
5222
+ filePath = prefix + newPart + suffix;
5223
+ }
5224
+ } else {
5225
+ filePath = filePath.slice(arrowIdx + 4);
5226
+ }
5227
+ }
5228
+ numstatMap.set(filePath, { additions, deletions, isBinary });
5229
+ }
5230
+ const filtered = filterPath ? entries.filter((e) => e.path === filterPath) : entries;
5231
+ const MAX_CONTENT_SIZE = 1e6;
5232
+ const fileResults = await Promise.all(filtered.map(async (entry) => {
5233
+ const stats = numstatMap.get(entry.path);
5234
+ if (stats?.isBinary) {
5235
+ return null;
5236
+ }
5237
+ let oldContent = "";
5238
+ let newContent = "";
5239
+ let truncated = false;
5240
+ if (hasHead && (entry.status === "modified" || entry.status === "deleted" || entry.status === "renamed" || entry.status === "copied")) {
5241
+ const showPath = entry.oldPath ?? entry.path;
5242
+ try {
5243
+ oldContent = await gitExec(["show", `HEAD:${showPath}`], cwd);
5244
+ if (oldContent.length > MAX_CONTENT_SIZE) {
5245
+ oldContent = oldContent.slice(0, MAX_CONTENT_SIZE);
5246
+ truncated = true;
5247
+ }
5248
+ } catch {
5249
+ }
5250
+ }
5251
+ if (entry.status !== "deleted") {
5252
+ try {
5253
+ const resolved = (0, import_node_path3.resolve)(cwd, entry.path);
5254
+ const buf = await (0, import_promises3.readFile)(resolved);
5255
+ if (buf.subarray(0, 8192).includes(0)) {
5256
+ return null;
5257
+ }
5258
+ newContent = buf.toString("utf-8");
5259
+ if (newContent.length > MAX_CONTENT_SIZE) {
5260
+ newContent = newContent.slice(0, MAX_CONTENT_SIZE);
5261
+ truncated = true;
5262
+ }
5263
+ } catch {
5264
+ }
5265
+ }
5266
+ const additions = stats?.additions ?? (entry.status === "untracked" ? newContent.split("\n").length : 0);
5267
+ const deletions = stats?.deletions ?? 0;
5268
+ const file = {
5269
+ path: entry.path,
5270
+ status: entry.status,
5271
+ oldContent,
5272
+ newContent,
5273
+ additions,
5274
+ deletions
5275
+ };
5276
+ if (entry.oldPath) {
5277
+ file.oldPath = entry.oldPath;
5278
+ }
5279
+ if (truncated) {
5280
+ file.truncated = true;
5281
+ }
5282
+ return file;
5283
+ }));
5284
+ const files = fileResults.filter((f) => f !== null);
5285
+ return c.json({ files, ...branch ? { branch } : {} });
5286
+ } catch (error) {
5287
+ const message = error instanceof Error ? error.message : String(error);
5288
+ return c.json({ error: message }, 500);
5289
+ }
5290
+ });
3827
5291
  const BOOLEAN_CLI_FLAGS = /* @__PURE__ */ new Set(["json", "verbose", "global", "noAuth", "expand", "i"]);
3828
5292
  function buildCliArgs(pathSegments, query, body) {
3829
5293
  const args = [...pathSegments, "--json"];
@@ -3940,6 +5404,18 @@ async function createAgentServer(params) {
3940
5404
  process.stderr.write(` GET /api/context \u2014 server context
3941
5405
  `);
3942
5406
  process.stderr.write(` GET /api/models \u2014 available models
5407
+ `);
5408
+ process.stderr.write(` GET /api/auth/providers \u2014 auth provider catalog
5409
+ `);
5410
+ process.stderr.write(` POST /api/auth/providers/:id/login \u2014 provider login/start login flow
5411
+ `);
5412
+ process.stderr.write(` POST /api/auth/providers/:id/oauth/continue \u2014 continue OAuth flow
5413
+ `);
5414
+ process.stderr.write(` POST /api/auth/providers/:id/logout \u2014 provider logout
5415
+ `);
5416
+ process.stderr.write(` POST /api/models/select \u2014 select model and optional thinking level
5417
+ `);
5418
+ process.stderr.write(` GET /api/slash-commands \u2014 slash command list
3943
5419
  `);
3944
5420
  process.stderr.write(` GET /api/fs/tree \u2014 directory tree
3945
5421
  `);
@@ -3960,6 +5436,8 @@ async function createAgentServer(params) {
3960
5436
  process.stderr.write(` POST /api/env/serve \u2014 start dev server (pnpm dev)
3961
5437
  `);
3962
5438
  process.stderr.write(` POST /api/env/stop \u2014 stop dev server
5439
+ `);
5440
+ process.stderr.write(` GET /api/git/diff \u2014 uncommitted file diffs
3963
5441
  `);
3964
5442
  process.stderr.write(` * /api/cli/** \u2014 proxy any docyrus CLI command
3965
5443
 
@@ -3992,6 +5470,61 @@ async function createAgentServer(params) {
3992
5470
  }
3993
5471
  }
3994
5472
  }
5473
+ function extractBearerToken(authorizationHeader) {
5474
+ if (!authorizationHeader) {
5475
+ return null;
5476
+ }
5477
+ const trimmed = authorizationHeader.trim();
5478
+ const firstSpaceIndex = trimmed.indexOf(" ");
5479
+ if (firstSpaceIndex <= 0) {
5480
+ return null;
5481
+ }
5482
+ const scheme = trimmed.slice(0, firstSpaceIndex);
5483
+ const token = trimmed.slice(firstSpaceIndex + 1).trim();
5484
+ if (scheme.toLowerCase() !== "bearer" || token.length === 0) {
5485
+ return null;
5486
+ }
5487
+ return token;
5488
+ }
5489
+ function hashToken(token) {
5490
+ return (0, import_node_crypto2.createHash)("sha256").update(token).digest();
5491
+ }
5492
+ function isBearerTokenAuthorized(actualToken, expectedToken) {
5493
+ if (!expectedToken) {
5494
+ return true;
5495
+ }
5496
+ if (!actualToken) {
5497
+ return false;
5498
+ }
5499
+ const actualHash = hashToken(actualToken);
5500
+ const expectedHash = hashToken(expectedToken);
5501
+ return (0, import_node_crypto2.timingSafeEqual)(actualHash, expectedHash);
5502
+ }
5503
+ function createUnauthorizedResponse() {
5504
+ return new Response(JSON.stringify({ error: "Unauthorized" }), {
5505
+ status: 401,
5506
+ headers: {
5507
+ "Content-Type": "application/json",
5508
+ "WWW-Authenticate": "Bearer"
5509
+ }
5510
+ });
5511
+ }
5512
+ function authorizeServerRequest(request, expectedToken) {
5513
+ if (!expectedToken || request.method === "OPTIONS") {
5514
+ return null;
5515
+ }
5516
+ const authorizationHeader = request.headers.get("Authorization");
5517
+ const token = extractBearerToken(authorizationHeader);
5518
+ if (!isBearerTokenAuthorized(token, expectedToken)) {
5519
+ return createUnauthorizedResponse();
5520
+ }
5521
+ return null;
5522
+ }
5523
+
5524
+ // src/server/loaderRequest.ts
5525
+ function parseServerLoaderRequest(payload) {
5526
+ return JSON.parse(payload);
5527
+ }
3995
5528
 
3996
5529
  // src/server/server-loader.ts
3997
5530
  function readRequiredEnv(name) {
@@ -4002,19 +5535,19 @@ function readRequiredEnv(name) {
4002
5535
  return value;
4003
5536
  }
4004
5537
  function readLoaderRequest() {
4005
- return JSON.parse(readRequiredEnv("DOCYRUS_PI_REQUEST"));
5538
+ return parseServerLoaderRequest(readRequiredEnv("DOCYRUS_PI_REQUEST"));
4006
5539
  }
4007
5540
  async function loadPiExports() {
4008
5541
  const piPackageDir = readRequiredEnv("PI_PACKAGE_DIR");
4009
- const moduleUrl = (0, import_node_url.pathToFileURL)((0, import_node_path3.join)(piPackageDir, "dist", "index.js")).href;
5542
+ const moduleUrl = (0, import_node_url.pathToFileURL)((0, import_node_path4.join)(piPackageDir, "dist", "index.js")).href;
4010
5543
  return await import(moduleUrl);
4011
5544
  }
4012
5545
  function resolvePackagedPiResourceRoot() {
4013
5546
  const candidates = [
4014
- (0, import_node_path3.resolve)(process.cwd(), "apps/api-cli/resources/pi-agent"),
4015
- (0, import_node_path3.resolve)(__dirname, "../resources/pi-agent"),
4016
- (0, import_node_path3.resolve)(__dirname, "resources/pi-agent"),
4017
- (0, import_node_path3.resolve)(process.cwd(), "dist/apps/api-cli/resources/pi-agent")
5547
+ (0, import_node_path4.resolve)(process.cwd(), "apps/api-cli/resources/pi-agent"),
5548
+ (0, import_node_path4.resolve)(__dirname, "../resources/pi-agent"),
5549
+ (0, import_node_path4.resolve)(__dirname, "resources/pi-agent"),
5550
+ (0, import_node_path4.resolve)(process.cwd(), "dist/apps/api-cli/resources/pi-agent")
4018
5551
  ];
4019
5552
  const resolved = candidates.find((candidate) => (0, import_node_fs.existsSync)(candidate));
4020
5553
  if (!resolved) {
@@ -4023,13 +5556,13 @@ function resolvePackagedPiResourceRoot() {
4023
5556
  return resolved;
4024
5557
  }
4025
5558
  function resolvePackagedExtensionPaths(resourceRoot) {
4026
- const extensionsRoot = (0, import_node_path3.join)(resourceRoot, "extensions");
5559
+ const extensionsRoot = (0, import_node_path4.join)(resourceRoot, "extensions");
4027
5560
  if (!(0, import_node_fs.existsSync)(extensionsRoot)) {
4028
5561
  return [];
4029
5562
  }
4030
5563
  return (0, import_node_fs.readdirSync)(extensionsRoot, {
4031
5564
  withFileTypes: true
4032
- }).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));
5565
+ }).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));
4033
5566
  }
4034
5567
  function setProcessArgValue(flag, value) {
4035
5568
  const existingIndex = process.argv.indexOf(flag);
@@ -4096,6 +5629,45 @@ function renderStartupSplash(version) {
4096
5629
  `
4097
5630
  );
4098
5631
  }
5632
+ function createServerSessionAdapter(params) {
5633
+ return {
5634
+ id: params.session.id,
5635
+ get isStreaming() {
5636
+ return params.session.isStreaming;
5637
+ },
5638
+ get model() {
5639
+ return params.session.model;
5640
+ },
5641
+ get thinkingLevel() {
5642
+ return params.session.thinkingLevel;
5643
+ },
5644
+ subscribe(listener) {
5645
+ return params.session.subscribe(listener);
5646
+ },
5647
+ prompt(text) {
5648
+ return params.session.prompt(text);
5649
+ },
5650
+ abort() {
5651
+ return params.session.abort();
5652
+ },
5653
+ setModel(model) {
5654
+ return params.session.setModel(model);
5655
+ },
5656
+ setThinkingLevel(level) {
5657
+ return params.session.setThinkingLevel(level);
5658
+ },
5659
+ getAvailableThinkingLevels() {
5660
+ return params.session.getAvailableThinkingLevels();
5661
+ },
5662
+ supportsThinking() {
5663
+ return params.session.supportsThinking();
5664
+ },
5665
+ listCommands() {
5666
+ const getCommands = params.extensionsResult.runtime.getCommands;
5667
+ return typeof getCommands === "function" ? getCommands() : [];
5668
+ }
5669
+ };
5670
+ }
4099
5671
  async function main() {
4100
5672
  const request = readLoaderRequest();
4101
5673
  const pi = await loadPiExports();
@@ -4104,16 +5676,16 @@ async function main() {
4104
5676
  const version = process.env.DOCYRUS_PI_VERSION || "dev";
4105
5677
  const resourceRoot = resolvePackagedPiResourceRoot();
4106
5678
  const packagedExtensionPaths = resolvePackagedExtensionPaths(resourceRoot);
4107
- const mcpConfigPath = (0, import_node_path3.join)(agentDir, "mcp.json");
5679
+ const mcpConfigPath = (0, import_node_path4.join)(agentDir, "mcp.json");
4108
5680
  const hasPackagedMcpAdapter = packagedExtensionPaths.some((extensionPath) => extensionPath.includes("pi-mcp-adapter"));
4109
- const envStore = new AgentEnvStore((0, import_node_path3.join)(agentDir, "env.json"));
5681
+ const envStore = new AgentEnvStore((0, import_node_path4.join)(agentDir, "env.json"));
4110
5682
  await envStore.hydrateProcessEnv(process.env);
4111
5683
  if (hasPackagedMcpAdapter) {
4112
5684
  setProcessArgValue("--mcp-config", mcpConfigPath);
4113
5685
  }
4114
- const authStorage = pi.AuthStorage.create((0, import_node_path3.join)(agentDir, "auth.json"));
5686
+ const authStorage = pi.AuthStorage.create((0, import_node_path4.join)(agentDir, "auth.json"));
4115
5687
  const settingsManager = pi.SettingsManager.create(cwd, agentDir);
4116
- const modelsJsonPath = (0, import_node_path3.join)(agentDir, "models.json");
5688
+ const modelsJsonPath = (0, import_node_path4.join)(agentDir, "models.json");
4117
5689
  const modelRegistry = new pi.ModelRegistry(authStorage, modelsJsonPath);
4118
5690
  const quietStartup = !request.verbose;
4119
5691
  if (quietStartup) {
@@ -4140,7 +5712,7 @@ async function main() {
4140
5712
  agentDir,
4141
5713
  settingsManager,
4142
5714
  additionalExtensionPaths: packagedExtensionPaths,
4143
- systemPrompt: (0, import_node_path3.join)(
5715
+ systemPrompt: (0, import_node_path4.join)(
4144
5716
  resourceRoot,
4145
5717
  "prompts",
4146
5718
  request.profile === "agent" ? "agent-system.md" : "coder-system.md"
@@ -4173,14 +5745,19 @@ Or create ${modelsJsonPath}`
4173
5745
  );
4174
5746
  }
4175
5747
  await createAgentServer({
4176
- session,
5748
+ session: createServerSessionAdapter({ session, extensionsResult }),
5749
+ authToken: request.auth,
4177
5750
  port: request.port,
4178
5751
  sessionManager: {
4179
5752
  list: () => pi.SessionManager.list(cwd, request.sessionDir),
4180
5753
  open: (path) => pi.SessionManager.open(path, request.sessionDir)
4181
5754
  },
4182
- modelRegistry: {
4183
- getAvailable: () => modelRegistry.getAvailable()
5755
+ modelRegistry,
5756
+ authRuntime: {
5757
+ authStorage,
5758
+ envStore,
5759
+ settingsManager,
5760
+ modelsJsonPath
4184
5761
  },
4185
5762
  context: {
4186
5763
  cwd,
@@ -4191,7 +5768,7 @@ Or create ${modelsJsonPath}`
4191
5768
  thinkingLevel: request.thinking ?? null
4192
5769
  },
4193
5770
  onCreateSession: async () => {
4194
- const { session: freshSession } = await pi.createAgentSession({
5771
+ const { session: freshSession, extensionsResult: freshExtensionsResult } = await pi.createAgentSession({
4195
5772
  cwd,
4196
5773
  agentDir,
4197
5774
  authStorage,
@@ -4203,7 +5780,10 @@ Or create ${modelsJsonPath}`
4203
5780
  model: requestedModel,
4204
5781
  thinkingLevel: request.thinking
4205
5782
  });
4206
- return freshSession;
5783
+ return createServerSessionAdapter({
5784
+ session: freshSession,
5785
+ extensionsResult: freshExtensionsResult
5786
+ });
4207
5787
  },
4208
5788
  onResumeSession: async (sessionId) => {
4209
5789
  const sessions = await pi.SessionManager.list(cwd, request.sessionDir);
@@ -4212,20 +5792,26 @@ Or create ${modelsJsonPath}`
4212
5792
  throw new Error(`Session not found: ${sessionId}`);
4213
5793
  }
4214
5794
  const resumeCwd = match2.cwd || cwd;
4215
- const { session: resumedSession } = await pi.createAgentSession({
5795
+ const resumedSessionManager = pi.SessionManager.open(
5796
+ match2.path,
5797
+ request.sessionDir
5798
+ );
5799
+ const { session: resumedSession, extensionsResult: resumedExtensionsResult } = await pi.createAgentSession({
4216
5800
  cwd: resumeCwd,
4217
5801
  agentDir,
4218
5802
  authStorage,
4219
5803
  modelRegistry,
4220
5804
  resourceLoader,
4221
5805
  settingsManager,
4222
- sessionManager,
5806
+ sessionManager: resumedSessionManager,
4223
5807
  tools: buildTools(request.profile, resumeCwd, pi),
4224
5808
  model: requestedModel,
4225
- thinkingLevel: request.thinking,
4226
- resumeSessionId: sessionId
5809
+ thinkingLevel: request.thinking
5810
+ });
5811
+ return createServerSessionAdapter({
5812
+ session: resumedSession,
5813
+ extensionsResult: resumedExtensionsResult
4227
5814
  });
4228
- return resumedSession;
4229
5815
  }
4230
5816
  });
4231
5817
  }