@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/README.md +41 -1
- package/agent-loader.js +249 -45
- package/agent-loader.js.map +3 -3
- package/main.js +499 -259
- package/main.js.map +4 -4
- package/package.json +2 -1
- package/resources/pi-agent/extensions/architect.ts +18 -34
- package/resources/pi-agent/extensions/plan.ts +1197 -0
- package/resources/pi-agent/shared/askUserProtocol.ts +248 -0
- package/resources/pi-agent/skills/docyrus-platform/SKILL.md +1 -1
- package/resources/pi-agent/skills/docyrus-platform/references/developer-tools.md +1 -1
- package/resources/pi-agent/skills/docyrus-platform/references/docyrus-cli-usage.md +46 -0
- package/resources/pi-agent/skills/docyrus-platform/references/querying-and-data-operations.md +7 -0
- package/server-loader.js +1647 -61
- package/server-loader.js.map +4 -4
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
|
|
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
|
|
799
|
-
var
|
|
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
|
-
|
|
3169
|
+
activeTextBuffer = { text: "", mode: "pending" };
|
|
2967
3170
|
break;
|
|
2968
3171
|
}
|
|
2969
3172
|
case "text_delta": {
|
|
2970
|
-
|
|
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
|
-
|
|
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,
|
|
3193
|
-
const normalizedCwd = cwd.endsWith(
|
|
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,
|
|
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,
|
|
3231
|
-
const relativePath = (0,
|
|
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,
|
|
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,
|
|
3269
|
-
const relativePath = (0,
|
|
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
|
-
|
|
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).
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
3528
|
-
await (0,
|
|
3529
|
-
const fileStat = await (0,
|
|
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,
|
|
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,
|
|
3559
|
-
await (0,
|
|
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,
|
|
3578
|
-
await (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
4015
|
-
(0,
|
|
4016
|
-
(0,
|
|
4017
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
}
|