@bike4mind/cli 0.2.1-quest-file-uploads.17007 → 0.2.3-feat-questmaster-gpt5-function-calling.17055

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.
@@ -6,7 +6,7 @@ import {
6
6
  getSettingsByNames,
7
7
  obfuscateApiKey,
8
8
  secureParameters
9
- } from "./chunk-U3GPYBTD.js";
9
+ } from "./chunk-NH74I6GX.js";
10
10
  import {
11
11
  ApiKeyType,
12
12
  MementoTier,
@@ -7,7 +7,7 @@ import {
7
7
  getSettingsMap,
8
8
  getSettingsValue,
9
9
  secureParameters
10
- } from "./chunk-U3GPYBTD.js";
10
+ } from "./chunk-NH74I6GX.js";
11
11
  import {
12
12
  KnowledgeType,
13
13
  SupportedFabFileMimeTypes
@@ -610,6 +610,10 @@ var AnthropicBackend = class {
610
610
  }
611
611
  if (collectedContent[event.index] && collectedContent[event.index].type === "tool_use") {
612
612
  }
613
+ } else if ("delta" in event && event.delta.type === "signature_delta") {
614
+ if (collectedContent[event.index] && collectedContent[event.index].type === "thinking") {
615
+ collectedContent[event.index].signature = event.delta.signature;
616
+ }
613
617
  }
614
618
  } else if (event.type === "content_block_stop") {
615
619
  if (isInThinkingBlock) {
@@ -792,14 +796,11 @@ var AnthropicBackend = class {
792
796
  }
793
797
  pushToolMessages(messages, tool, result, previousMessage) {
794
798
  if (this.isThinkingEnabled && this.lastAssistantContent.length > 0) {
795
- const contentWithoutThinking = this.lastAssistantContent.filter((block) => {
796
- return block.type !== "thinking";
797
- });
798
799
  messages.push({
799
800
  role: "assistant",
800
- content: contentWithoutThinking
801
+ content: this.lastAssistantContent
801
802
  });
802
- this.logger.debug(`[AnthropicBackend] Filtered thinking blocks from assistant content. Original: ${this.lastAssistantContent.length} blocks, Filtered: ${contentWithoutThinking.length} blocks`);
803
+ this.logger.debug(`[AnthropicBackend] Including ${this.lastAssistantContent.length} content blocks (thinking + tool_use) in assistant message`);
803
804
  } else {
804
805
  messages.push({
805
806
  role: "assistant",
@@ -1118,8 +1119,12 @@ var BaseBedrockBackend = class {
1118
1119
  await callback(streamedText, { inputTokens, outputTokens, toolsUsed });
1119
1120
  }
1120
1121
  } catch (error) {
1121
- console.error(`[BaseBedrockBackend] Error invoking model ${model}:`, error);
1122
1122
  if (error instanceof Error) {
1123
+ if (error.message.includes("aborted")) {
1124
+ console.log("[BaseBedrockBackend] Request aborted, skipping error logging");
1125
+ throw error;
1126
+ }
1127
+ console.error(`[BaseBedrockBackend] Error invoking model ${model}:`, error);
1123
1128
  console.error(`[BaseBedrockBackend] Error details:`, {
1124
1129
  name: error.name,
1125
1130
  message: error.message,
@@ -4107,6 +4112,12 @@ var OpenAIBackend = class {
4107
4112
  }
4108
4113
  if (options.tools?.length) {
4109
4114
  parameters.tools = this.formatTools(options.tools);
4115
+ if (options.tool_choice) {
4116
+ parameters.tool_choice = options.tool_choice;
4117
+ }
4118
+ if (options.parallel_tool_calls !== void 0) {
4119
+ parameters.parallel_tool_calls = options.parallel_tool_calls;
4120
+ }
4110
4121
  }
4111
4122
  const response = await this._api.chat.completions.create(parameters, {
4112
4123
  signal: options.abortSignal
@@ -7973,6 +7984,147 @@ function extractQuestMasterData(reply, options = {}) {
7973
7984
  return questMasterData;
7974
7985
  }
7975
7986
 
7987
+ // ../../b4m-core/packages/utils/dist/src/questMasterToolSchema.js
7988
+ var GPT5_MODELS_WITH_TOOL_SUPPORT = [
7989
+ ChatModels.GPT5,
7990
+ ChatModels.GPT5_MINI,
7991
+ ChatModels.GPT5_NANO,
7992
+ ChatModels.GPT5_1,
7993
+ ChatModels.GPT5_2
7994
+ ];
7995
+ function isGPT5ModelWithToolSupport(model) {
7996
+ return GPT5_MODELS_WITH_TOOL_SUPPORT.includes(model);
7997
+ }
7998
+ var createQuestPlanToolSchema = {
7999
+ name: "create_quest_plan",
8000
+ description: `Break down a user request into a structured quest plan with actionable tasks.
8001
+
8002
+ GUIDELINES:
8003
+ - Analyze the user's request and create 3-7 main quests
8004
+ - Each quest must have 2-5 specific, actionable subquests
8005
+ - Quest titles should be clear and action-oriented (e.g., "Set up database schema" not "Step 1")
8006
+ - Provide detailed descriptions for each quest
8007
+ - Assess complexity accurately: Easy (< 1 hour), Medium (1-4 hours), Hard (> 4 hours)
8008
+ - Generate 2-5 relevant tags categorizing the work (e.g., "web-development", "database", "api")
8009
+
8010
+ ALWAYS call this function to respond. Do NOT respond with plain text.`,
8011
+ parameters: {
8012
+ type: "object",
8013
+ properties: {
8014
+ goal: {
8015
+ type: "string",
8016
+ description: "The overall objective the user wants to achieve"
8017
+ },
8018
+ tags: {
8019
+ type: "array",
8020
+ items: { type: "string" },
8021
+ description: 'Categories for this quest (e.g., "web-development", "api", "database")'
8022
+ },
8023
+ quests: {
8024
+ type: "array",
8025
+ description: "The main quests (3-7) that break down the goal",
8026
+ items: {
8027
+ type: "object",
8028
+ properties: {
8029
+ id: {
8030
+ type: "string",
8031
+ description: 'Unique identifier (e.g., "quest-1", "setup-database")'
8032
+ },
8033
+ title: {
8034
+ type: "string",
8035
+ description: 'Clear, action-oriented title (e.g., "Configure authentication middleware")'
8036
+ },
8037
+ description: {
8038
+ type: "string",
8039
+ description: "Detailed explanation of what needs to be done and why"
8040
+ },
8041
+ complexity: {
8042
+ type: "string",
8043
+ enum: ["Easy", "Medium", "Hard"],
8044
+ description: "Difficulty assessment based on effort required"
8045
+ },
8046
+ subQuests: {
8047
+ type: "array",
8048
+ description: "Concrete subtasks (2-5) within this quest",
8049
+ items: {
8050
+ type: "object",
8051
+ properties: {
8052
+ id: {
8053
+ type: "string",
8054
+ description: "Unique identifier for the subquest"
8055
+ },
8056
+ title: {
8057
+ type: "string",
8058
+ description: "Specific, actionable subtask title"
8059
+ }
8060
+ },
8061
+ required: ["id", "title"]
8062
+ }
8063
+ }
8064
+ },
8065
+ required: ["id", "title", "description", "complexity", "subQuests"]
8066
+ }
8067
+ }
8068
+ },
8069
+ required: ["goal", "quests"]
8070
+ }
8071
+ };
8072
+ function parseQuestPlanFunctionCall(args) {
8073
+ let parsed;
8074
+ try {
8075
+ parsed = JSON.parse(args);
8076
+ } catch (e) {
8077
+ throw new Error(`Failed to parse function call arguments as JSON: ${e}`);
8078
+ }
8079
+ if (!parsed || typeof parsed !== "object") {
8080
+ throw new Error("Function call arguments must be an object");
8081
+ }
8082
+ const result = parsed;
8083
+ if (!result.goal || typeof result.goal !== "string") {
8084
+ throw new Error("Function call missing required field: goal");
8085
+ }
8086
+ if (!Array.isArray(result.quests)) {
8087
+ throw new Error("Function call missing required field: quests (must be an array)");
8088
+ }
8089
+ if (result.quests.length === 0) {
8090
+ throw new Error("Function call returned empty quests array");
8091
+ }
8092
+ for (let i = 0; i < result.quests.length; i++) {
8093
+ const quest = result.quests[i];
8094
+ if (!quest.id || typeof quest.id !== "string") {
8095
+ throw new Error(`Quest ${i} missing required field: id`);
8096
+ }
8097
+ if (!quest.title || typeof quest.title !== "string") {
8098
+ throw new Error(`Quest ${i} missing required field: title`);
8099
+ }
8100
+ if (!quest.description || typeof quest.description !== "string") {
8101
+ throw new Error(`Quest ${i} missing required field: description`);
8102
+ }
8103
+ if (!quest.complexity || !["Easy", "Medium", "Hard"].includes(quest.complexity)) {
8104
+ throw new Error(`Quest ${i} has invalid complexity: ${quest.complexity} (must be Easy, Medium, or Hard)`);
8105
+ }
8106
+ if (!Array.isArray(quest.subQuests)) {
8107
+ throw new Error(`Quest "${quest.id}" missing required field: subQuests (must be an array)`);
8108
+ }
8109
+ if (quest.subQuests.length === 0) {
8110
+ throw new Error(`Quest "${quest.id}" has no subQuests (minimum 2 required)`);
8111
+ }
8112
+ for (let j = 0; j < quest.subQuests.length; j++) {
8113
+ const subQuest = quest.subQuests[j];
8114
+ if (!subQuest.id || typeof subQuest.id !== "string") {
8115
+ throw new Error(`Quest "${quest.id}" subQuest ${j} missing required field: id`);
8116
+ }
8117
+ if (!subQuest.title || typeof subQuest.title !== "string") {
8118
+ throw new Error(`Quest "${quest.id}" subQuest ${j} missing required field: title`);
8119
+ }
8120
+ }
8121
+ }
8122
+ if (result.tags !== void 0 && !Array.isArray(result.tags)) {
8123
+ throw new Error("Function call field tags must be an array if provided");
8124
+ }
8125
+ return result;
8126
+ }
8127
+
7976
8128
  // ../../b4m-core/packages/utils/dist/src/questMaster.js
7977
8129
  var sanitizeId = (id) => {
7978
8130
  return id.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9_.-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
@@ -8003,10 +8155,9 @@ var QuestMaster = class {
8003
8155
  }
8004
8156
  return "Easy";
8005
8157
  }
8006
- async processQuestPlan(questPlanText) {
8158
+ async processQuestPlan(input) {
8007
8159
  try {
8008
8160
  await this.onStatusUpdate(this.quest, "Processing quest plan...");
8009
- this.logger.log("Processing quest plan:", questPlanText);
8010
8161
  const defaultResponse = {
8011
8162
  type: "narrative",
8012
8163
  meta: {
@@ -8023,135 +8174,161 @@ var QuestMaster = class {
8023
8174
  }
8024
8175
  ]
8025
8176
  };
8026
- if (!questPlanText || typeof questPlanText !== "string") {
8027
- this.logger.warn("Invalid questPlanText received:", questPlanText);
8028
- if (this.quest) {
8029
- this.quest.questMasterReply = JSON.stringify(defaultResponse);
8030
- await this.db.quests.update({
8031
- id: this.quest.id,
8032
- questMasterReply: this.quest.questMasterReply,
8033
- reply: null,
8034
- replies: [],
8035
- status: "done",
8036
- type: "message"
8037
- });
8038
- }
8039
- return;
8040
- }
8041
8177
  if (!this.quest) {
8042
8178
  throw new Error("No quest context available");
8043
8179
  }
8044
- const extracted = extractQuestMasterData(questPlanText, { logger: this.logger });
8045
- const metaStartTag = "<!--QuestMasterMeta";
8046
- const metaEndTag = "-->";
8047
- const metaStartIndex = questPlanText.indexOf(metaStartTag);
8048
- let goal = "Quest Plan";
8049
- let tags = [];
8050
8180
  let questResponse;
8051
- if (metaStartIndex !== -1) {
8052
- const metaEndIndex = questPlanText.indexOf(metaEndTag, metaStartIndex);
8053
- if (metaEndIndex !== -1) {
8054
- const metaContent = questPlanText.slice(metaStartIndex + metaStartTag.length, metaEndIndex).trim();
8055
- try {
8056
- const jsonStartIndex = metaContent.indexOf("{");
8057
- if (jsonStartIndex !== -1) {
8058
- const metaData = JSON.parse(metaContent.slice(jsonStartIndex));
8059
- goal = metaData.goal || goal;
8060
- tags = Array.isArray(metaData.tags) ? metaData.tags : [];
8061
- }
8062
- } catch (err) {
8063
- this.logger.error("Error parsing meta data:", err);
8064
- }
8065
- }
8066
- }
8067
- if (extracted.length > 0) {
8068
- this.logger.log("Successfully extracted QuestMaster data:", extracted);
8069
- await this.onStatusUpdate(this.quest, "Extracted QuestMaster data");
8070
- const validQuests = extracted.filter((quest) => quest !== null && typeof quest === "object" && quest.id && quest.title && quest.title !== "Step 1" && // Explicitly filter out generic Step 1
8071
- quest.description && quest.description !== "No description available");
8072
- if (validQuests.length === 0) {
8073
- throw new Error("No valid quests found after filtering");
8074
- }
8181
+ if (typeof input === "object") {
8182
+ this.logger.log("Processing quest plan from function calling result");
8075
8183
  questResponse = {
8076
8184
  type: "quest_plan",
8077
8185
  meta: {
8078
- goal,
8079
- totalSteps: validQuests.length,
8080
- tags
8186
+ goal: input.goal,
8187
+ totalSteps: input.quests.length,
8188
+ tags: input.tags
8081
8189
  },
8082
- quests: validQuests.map((quest) => ({
8190
+ quests: input.quests.map((quest) => ({
8083
8191
  id: quest.id,
8084
- // No fallback - we filtered for valid IDs
8085
8192
  title: quest.title,
8086
- // No fallback - we filtered for valid titles
8087
8193
  description: quest.description,
8088
- // No fallback - we filtered for valid descriptions
8089
- complexity: quest.complexity || "Medium",
8194
+ complexity: quest.complexity,
8090
8195
  status: "not_started",
8091
- subQuests: Array.isArray(quest.subQuests) ? quest.subQuests.filter((sub) => sub && sub.id && sub.title && !sub.title.startsWith("Subtask")).map((sub) => ({
8196
+ subQuests: quest.subQuests.map((sub) => ({
8092
8197
  id: sub.id,
8093
8198
  title: sub.title,
8094
8199
  status: "not_started"
8095
- })) : []
8200
+ }))
8096
8201
  }))
8097
8202
  };
8098
- if (questResponse.quests.some((quest) => quest.subQuests.length === 0)) {
8099
- throw new Error("Some quests have no valid subquests after filtering");
8100
- }
8101
8203
  } else {
8102
- try {
8103
- const questPlan = JSON.parse(questPlanText);
8104
- if (questPlan && questPlan.questChain && Array.isArray(questPlan.questChain)) {
8105
- const validQuestChain = questPlan.questChain.filter((item) => item && item.quest && item.quest !== "Step 1" && item.details && item.details !== "No description available");
8106
- if (validQuestChain.length === 0) {
8107
- throw new Error("No valid quests found in quest chain");
8204
+ const questPlanText = input;
8205
+ this.logger.log("Processing quest plan:", questPlanText);
8206
+ if (!questPlanText || typeof questPlanText !== "string") {
8207
+ this.logger.warn("Invalid questPlanText received:", questPlanText);
8208
+ if (this.quest) {
8209
+ this.quest.questMasterReply = JSON.stringify(defaultResponse);
8210
+ await this.db.quests.update({
8211
+ id: this.quest.id,
8212
+ questMasterReply: this.quest.questMasterReply,
8213
+ reply: null,
8214
+ replies: [],
8215
+ status: "done",
8216
+ type: "message"
8217
+ });
8218
+ }
8219
+ return;
8220
+ }
8221
+ const extracted = extractQuestMasterData(questPlanText, { logger: this.logger });
8222
+ const metaStartTag = "<!--QuestMasterMeta";
8223
+ const metaEndTag = "-->";
8224
+ const metaStartIndex = questPlanText.indexOf(metaStartTag);
8225
+ let goal = "Quest Plan";
8226
+ let tags = [];
8227
+ if (metaStartIndex !== -1) {
8228
+ const metaEndIndex = questPlanText.indexOf(metaEndTag, metaStartIndex);
8229
+ if (metaEndIndex !== -1) {
8230
+ const metaContent = questPlanText.slice(metaStartIndex + metaStartTag.length, metaEndIndex).trim();
8231
+ try {
8232
+ const jsonStartIndex = metaContent.indexOf("{");
8233
+ if (jsonStartIndex !== -1) {
8234
+ const metaData = JSON.parse(metaContent.slice(jsonStartIndex));
8235
+ goal = metaData.goal || goal;
8236
+ tags = Array.isArray(metaData.tags) ? metaData.tags : [];
8237
+ }
8238
+ } catch (err) {
8239
+ this.logger.error("Error parsing meta data:", err);
8108
8240
  }
8109
- questResponse = {
8110
- type: "quest_plan",
8111
- meta: {
8112
- goal: questPlan.abstractObjective || "Process Request",
8113
- totalSteps: validQuestChain.length
8114
- },
8115
- quests: validQuestChain.map((item, index) => ({
8116
- id: `quest-${index + 1}`,
8117
- title: item.quest,
8118
- description: item.details,
8119
- complexity: this.determineComplexity(item.details),
8120
- status: "Not Started",
8121
- subQuests: []
8122
- // This will trigger a retry since we require subquests
8123
- }))
8124
- };
8125
- } else {
8126
- if (!questPlanText.trim() || questPlanText.trim() === "Step 1") {
8127
- throw new Error("Empty or invalid response received");
8241
+ }
8242
+ }
8243
+ if (extracted.length > 0) {
8244
+ this.logger.log("Successfully extracted QuestMaster data:", extracted);
8245
+ await this.onStatusUpdate(this.quest, "Extracted QuestMaster data");
8246
+ const validQuests = extracted.filter((quest) => quest !== null && typeof quest === "object" && quest.id && quest.title && quest.title !== "Step 1" && // Explicitly filter out generic Step 1
8247
+ quest.description && quest.description !== "No description available");
8248
+ if (validQuests.length === 0) {
8249
+ throw new Error("No valid quests found after filtering");
8250
+ }
8251
+ questResponse = {
8252
+ type: "quest_plan",
8253
+ meta: {
8254
+ goal,
8255
+ totalSteps: validQuests.length,
8256
+ tags
8257
+ },
8258
+ quests: validQuests.map((quest) => ({
8259
+ id: quest.id,
8260
+ // No fallback - we filtered for valid IDs
8261
+ title: quest.title,
8262
+ // No fallback - we filtered for valid titles
8263
+ description: quest.description,
8264
+ // No fallback - we filtered for valid descriptions
8265
+ complexity: quest.complexity || "Medium",
8266
+ status: "not_started",
8267
+ subQuests: Array.isArray(quest.subQuests) ? quest.subQuests.filter((sub) => sub && sub.id && sub.title && !sub.title.startsWith("Subtask")).map((sub) => ({
8268
+ id: sub.id,
8269
+ title: sub.title,
8270
+ status: "not_started"
8271
+ })) : []
8272
+ }))
8273
+ };
8274
+ if (questResponse.quests.some((quest) => quest.subQuests.length === 0)) {
8275
+ throw new Error("Some quests have no valid subquests after filtering");
8276
+ }
8277
+ } else {
8278
+ try {
8279
+ const questPlan = JSON.parse(questPlanText);
8280
+ if (questPlan && questPlan.questChain && Array.isArray(questPlan.questChain)) {
8281
+ const validQuestChain = questPlan.questChain.filter((item) => item && item.quest && item.quest !== "Step 1" && item.details && item.details !== "No description available");
8282
+ if (validQuestChain.length === 0) {
8283
+ throw new Error("No valid quests found in quest chain");
8284
+ }
8285
+ questResponse = {
8286
+ type: "quest_plan",
8287
+ meta: {
8288
+ goal: questPlan.abstractObjective || "Process Request",
8289
+ totalSteps: validQuestChain.length
8290
+ },
8291
+ quests: validQuestChain.map((item, index) => ({
8292
+ id: `quest-${index + 1}`,
8293
+ title: item.quest,
8294
+ description: item.details,
8295
+ complexity: this.determineComplexity(item.details),
8296
+ status: "Not Started",
8297
+ subQuests: []
8298
+ // This will trigger a retry since we require subquests
8299
+ }))
8300
+ };
8301
+ } else {
8302
+ if (!questPlanText.trim() || questPlanText.trim() === "Step 1") {
8303
+ throw new Error("Empty or invalid response received");
8304
+ }
8305
+ questResponse = {
8306
+ type: "narrative",
8307
+ meta: {
8308
+ goal: "Direct Response",
8309
+ totalSteps: 1
8310
+ },
8311
+ quests: [
8312
+ {
8313
+ id: "narrative-1",
8314
+ title: "Process Response",
8315
+ description: questPlanText.trim(),
8316
+ complexity: "Medium",
8317
+ subQuests: [
8318
+ {
8319
+ id: "narrative-1-1",
8320
+ title: "Review Response",
8321
+ status: "not_started"
8322
+ }
8323
+ ]
8324
+ }
8325
+ ]
8326
+ };
8128
8327
  }
8129
- questResponse = {
8130
- type: "narrative",
8131
- meta: {
8132
- goal: "Direct Response",
8133
- totalSteps: 1
8134
- },
8135
- quests: [
8136
- {
8137
- id: "narrative-1",
8138
- title: "Process Response",
8139
- description: questPlanText.trim(),
8140
- complexity: "Medium",
8141
- subQuests: [
8142
- {
8143
- id: "narrative-1-1",
8144
- title: "Review Response",
8145
- status: "not_started"
8146
- }
8147
- ]
8148
- }
8149
- ]
8150
- };
8328
+ } catch (parseError) {
8329
+ this.logger.error("Error parsing quest plan JSON:", parseError);
8330
+ questResponse = defaultResponse;
8151
8331
  }
8152
- } catch (parseError) {
8153
- this.logger.error("Error parsing quest plan JSON:", parseError);
8154
- questResponse = defaultResponse;
8155
8332
  }
8156
8333
  }
8157
8334
  if (!this.validateQuestResponse(questResponse)) {
@@ -8351,9 +8528,102 @@ var QuestMaster = class {
8351
8528
  }
8352
8529
  -->`;
8353
8530
  }
8531
+ /**
8532
+ * Create a quest plan using GPT-5's function calling feature.
8533
+ * This method is used when the model is a GPT-5 variant that supports tools
8534
+ * but doesn't reliably follow HTML comment formatting.
8535
+ *
8536
+ * @param model - The GPT-5 model to use
8537
+ * @param prompt - The user's request to break down into quests
8538
+ * @returns The parsed quest plan result
8539
+ */
8540
+ async createQuestPlanWithFunctionCalling(model, prompt, retryCount = 0) {
8541
+ const MAX_RETRIES = 2;
8542
+ const messages = [
8543
+ {
8544
+ role: "system",
8545
+ content: `You are the QuestMaster, an AI agent that breaks down complex tasks into manageable quests.
8546
+
8547
+ When given a user request, you MUST call the create_quest_plan function to structure your response.
8548
+ Do NOT respond with plain text - ALWAYS use the function call.
8549
+
8550
+ Guidelines:
8551
+ - Break down complex tasks into 3-7 main quests
8552
+ - Each quest needs 2-5 specific, actionable subquests
8553
+ - Make titles clear and action-oriented (e.g., "Set up database schema" not "Step 1")
8554
+ - Provide detailed descriptions explaining what needs to be done
8555
+ - Assess complexity accurately: Easy (< 1 hour), Medium (1-4 hours), Hard (> 4 hours)
8556
+ - Generate 2-5 relevant tags categorizing the work`
8557
+ },
8558
+ { role: "user", content: prompt }
8559
+ ];
8560
+ const toolDef = {
8561
+ toolFn: async (params) => JSON.stringify(params),
8562
+ // Not executed, just captured
8563
+ toolSchema: createQuestPlanToolSchema
8564
+ };
8565
+ const options = {
8566
+ temperature: retryCount > 0 ? 0.7 : 0.9,
8567
+ // Lower temperature on retries
8568
+ n: 1,
8569
+ stream: false,
8570
+ tools: [toolDef],
8571
+ executeTools: false,
8572
+ // Don't execute, just capture the function call
8573
+ tool_choice: { type: "function", function: { name: "create_quest_plan" } },
8574
+ // Force function call
8575
+ parallel_tool_calls: false
8576
+ // Required for structured outputs
8577
+ };
8578
+ let functionResult = null;
8579
+ let lastError = null;
8580
+ try {
8581
+ await this.llm.complete(model, messages, options, async (text, completionInfo) => {
8582
+ if (completionInfo?.toolsUsed?.length) {
8583
+ const toolCall = completionInfo.toolsUsed[0];
8584
+ if (toolCall.name === "create_quest_plan" && toolCall.arguments) {
8585
+ this.logger.log("GPT-5 function call received, parsing arguments...");
8586
+ functionResult = parseQuestPlanFunctionCall(toolCall.arguments);
8587
+ }
8588
+ }
8589
+ });
8590
+ } catch (error) {
8591
+ lastError = error instanceof Error ? error : new Error(String(error));
8592
+ this.logger.error("Error in GPT-5 function calling:", lastError);
8593
+ }
8594
+ if (!functionResult) {
8595
+ if (retryCount < MAX_RETRIES) {
8596
+ this.logger.warn(`GPT-5 did not return function call, retrying (attempt ${retryCount + 1}/${MAX_RETRIES})...`);
8597
+ return this.createQuestPlanWithFunctionCalling(model, prompt, retryCount + 1);
8598
+ }
8599
+ throw lastError || new Error("GPT-5 did not call create_quest_plan function after retries");
8600
+ }
8601
+ const result = functionResult;
8602
+ const questsWithoutSubquests = result.quests.filter((q) => !q.subQuests || q.subQuests.length === 0);
8603
+ if (questsWithoutSubquests.length > 0 && retryCount < MAX_RETRIES) {
8604
+ this.logger.warn(`Some quests missing subquests: ${questsWithoutSubquests.map((q) => q.id).join(", ")}, retrying...`);
8605
+ return this.createQuestPlanWithFunctionCalling(model, prompt, retryCount + 1);
8606
+ }
8607
+ this.logger.log("Successfully received GPT-5 function call result:", {
8608
+ goal: result.goal,
8609
+ questCount: result.quests.length,
8610
+ tags: result.tags
8611
+ });
8612
+ return result;
8613
+ }
8354
8614
  async createQuestPlan(model, prompt) {
8355
8615
  try {
8356
8616
  await this.onStatusUpdate(this.quest, "Creating quest plan...");
8617
+ if (isGPT5ModelWithToolSupport(model)) {
8618
+ this.logger.log(`Using function calling approach for GPT-5 model: ${model}`);
8619
+ try {
8620
+ const result = await this.createQuestPlanWithFunctionCalling(model, prompt);
8621
+ await this.processQuestPlan(result);
8622
+ return;
8623
+ } catch (functionCallError) {
8624
+ this.logger.error("Function calling failed, falling back to HTML approach:", functionCallError);
8625
+ }
8626
+ }
8357
8627
  const messages = [
8358
8628
  {
8359
8629
  role: "system",
@@ -9952,7 +10222,7 @@ function shouldTriggerFallback(error) {
9952
10222
  return true;
9953
10223
  }
9954
10224
  }
9955
- if ("code" in error && error.code === "ECONNRESET") {
10225
+ if ("code" in error && error.code === "ECONNRESET" && !message.includes("aborted")) {
9956
10226
  return true;
9957
10227
  }
9958
10228
  if (error.name === "TypeError" && message.includes("terminated")) {
@@ -10447,6 +10717,9 @@ export {
10447
10717
  sendToConnection,
10448
10718
  ClientMessageSender,
10449
10719
  OpenaiModerationsService,
10720
+ isGPT5ModelWithToolSupport,
10721
+ createQuestPlanToolSchema,
10722
+ parseQuestPlanFunctionCall,
10450
10723
  QuestMaster,
10451
10724
  OpenAIImageService,
10452
10725
  TestImageService,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  BadRequestError,
4
4
  secureParameters
5
- } from "./chunk-U3GPYBTD.js";
5
+ } from "./chunk-NH74I6GX.js";
6
6
  import {
7
7
  GenericCreditDeductTransaction,
8
8
  ImageEditUsageTransaction,
@@ -22,12 +22,20 @@ var SubtractCreditsSchema = z.discriminatedUnion("type", [
22
22
  ImageEditUsageTransaction.omit({ createdAt: true, updatedAt: true }),
23
23
  TransferCreditTransaction.omit({ createdAt: true, updatedAt: true })
24
24
  ]);
25
- async function subtractCredits(parameters, { db, creditHolderMethods }) {
25
+ async function subtractCredits(parameters, { db, creditHolderMethods, skipBalanceUpdate, currentCreditHolder }) {
26
26
  const params = secureParameters(parameters, SubtractCreditsSchema);
27
27
  const { ownerId, ownerType, credits, type, description, metadata } = params;
28
- const updatedEntity = await creditHolderMethods.incrementCredits(ownerId, -credits);
29
- if (!updatedEntity) {
30
- throw new BadRequestError("Failed to update credits");
28
+ let updatedEntity;
29
+ if (skipBalanceUpdate) {
30
+ if (!currentCreditHolder) {
31
+ throw new BadRequestError("currentCreditHolder is required when skipBalanceUpdate is true");
32
+ }
33
+ updatedEntity = currentCreditHolder;
34
+ } else {
35
+ updatedEntity = await creditHolderMethods.incrementCredits(ownerId, -credits);
36
+ if (!updatedEntity) {
37
+ throw new BadRequestError("Failed to update credits");
38
+ }
31
39
  }
32
40
  if (type === "generic_deduct") {
33
41
  await db.creditTransactions.createTransaction("generic_deduct", {
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  createFabFile,
4
4
  createFabFileSchema
5
- } from "./chunk-F3ETXO5N.js";
6
- import "./chunk-U3GPYBTD.js";
5
+ } from "./chunk-KDBSH6HC.js";
6
+ import "./chunk-NH74I6GX.js";
7
7
  import "./chunk-AMDXHL6S.js";
8
8
  import "./chunk-WR6YZRQV.js";
9
9
  import "./chunk-PDX44BCA.js";