@promptbook/node 0.110.0-3 → 0.110.0-5

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.
Files changed (27) hide show
  1. package/esm/index.es.js +798 -456
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/typings/src/_packages/openai.index.d.ts +0 -4
  4. package/esm/typings/src/_packages/types.index.d.ts +0 -4
  5. package/esm/typings/src/book-components/Chat/Chat/ChatActionsBar.d.ts +4 -0
  6. package/esm/typings/src/book-components/Chat/Chat/ChatMessageItem.d.ts +1 -1
  7. package/esm/typings/src/book-components/Chat/Chat/ChatMessageList.d.ts +0 -2
  8. package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +0 -7
  9. package/esm/typings/src/book-components/Chat/SourceChip/SourceChip.d.ts +5 -1
  10. package/esm/typings/src/book-components/Chat/hooks/useChatActionsOverlap.d.ts +6 -3
  11. package/esm/typings/src/book-components/Chat/utils/collectTeamToolCallSummary.d.ts +69 -0
  12. package/esm/typings/src/book-components/Chat/utils/getToolCallChipletInfo.d.ts +13 -6
  13. package/esm/typings/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +9 -0
  14. package/esm/typings/src/book-components/Chat/utils/toolCallParsing.d.ts +4 -0
  15. package/esm/typings/src/llm-providers/agent/Agent.d.ts +0 -7
  16. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +0 -1
  17. package/esm/typings/src/llm-providers/agent/AgentOptions.d.ts +9 -0
  18. package/esm/typings/src/llm-providers/agent/CreateAgentLlmExecutionToolsOptions.d.ts +9 -0
  19. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +40 -1
  20. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionToolsOptions.d.ts +30 -0
  21. package/esm/typings/src/utils/agents/resolveAgentAvatarImageUrl.d.ts +29 -0
  22. package/esm/typings/src/version.d.ts +1 -1
  23. package/package.json +3 -5
  24. package/umd/index.umd.js +798 -456
  25. package/umd/index.umd.js.map +1 -1
  26. package/esm/typings/src/llm-providers/openai/OpenAiAgentExecutionTools.d.ts +0 -43
  27. package/esm/typings/src/llm-providers/openai/createOpenAiAgentExecutionTools.d.ts +0 -11
package/esm/index.es.js CHANGED
@@ -34,7 +34,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
34
34
  * @generated
35
35
  * @see https://github.com/webgptorg/promptbook
36
36
  */
37
- const PROMPTBOOK_ENGINE_VERSION = '0.110.0-3';
37
+ const PROMPTBOOK_ENGINE_VERSION = '0.110.0-5';
38
38
  /**
39
39
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
40
40
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -16974,11 +16974,16 @@ function createTeamToolFunction(entry) {
16974
16974
  const request = buildTeammateRequest(message, args.context);
16975
16975
  let response = '';
16976
16976
  let error = null;
16977
+ let toolCalls;
16977
16978
  try {
16978
16979
  const remoteAgent = await getRemoteTeammateAgent(entry.teammate.url);
16979
16980
  const prompt = buildTeammatePrompt(request);
16980
16981
  const teammateResult = await remoteAgent.callChatModel(prompt);
16981
16982
  response = teammateResult.content || '';
16983
+ toolCalls =
16984
+ 'toolCalls' in teammateResult && Array.isArray(teammateResult.toolCalls)
16985
+ ? teammateResult.toolCalls
16986
+ : undefined;
16982
16987
  }
16983
16988
  catch (err) {
16984
16989
  error = err instanceof Error ? err.message : String(err);
@@ -16988,6 +16993,7 @@ function createTeamToolFunction(entry) {
16988
16993
  teammate: teammateMetadata,
16989
16994
  request,
16990
16995
  response: teammateReply,
16996
+ toolCalls: toolCalls && toolCalls.length > 0 ? toolCalls : undefined,
16991
16997
  error,
16992
16998
  conversation: [
16993
16999
  {
@@ -23250,18 +23256,6 @@ class OpenAiExecutionTools extends OpenAiCompatibleExecutionTools {
23250
23256
  get profile() {
23251
23257
  return OPENAI_PROVIDER_PROFILE;
23252
23258
  }
23253
- /*
23254
- Note: Commenting this out to avoid circular dependency
23255
- /**
23256
- * Create (sub)tools for calling OpenAI API Assistants
23257
- *
23258
- * @param assistantId Which assistant to use
23259
- * @returns Tools for calling OpenAI API Assistants with same token
23260
- * /
23261
- public createAssistantSubtools(assistantId: string_token): OpenAiAssistantExecutionTools {
23262
- return new OpenAiAssistantExecutionTools({ ...this.options, assistantId });
23263
- }
23264
- */
23265
23259
  /**
23266
23260
  * List all available models (non dynamically)
23267
23261
  *
@@ -23296,207 +23290,6 @@ class OpenAiExecutionTools extends OpenAiCompatibleExecutionTools {
23296
23290
  }
23297
23291
  }
23298
23292
 
23299
- /**
23300
- * Execution Tools for calling OpenAI API using the Responses API (Agents)
23301
- *
23302
- * @public exported from `@promptbook/openai`
23303
- */
23304
- class OpenAiAgentExecutionTools extends OpenAiExecutionTools {
23305
- constructor(options) {
23306
- super(options);
23307
- this.vectorStoreId = options.vectorStoreId;
23308
- }
23309
- get title() {
23310
- return 'OpenAI Agent';
23311
- }
23312
- get description() {
23313
- return 'Use OpenAI Responses API (Agentic)';
23314
- }
23315
- /**
23316
- * Calls OpenAI API to use a chat model with streaming.
23317
- */
23318
- async callChatModelStream(prompt, onProgress) {
23319
- if (this.options.isVerbose) {
23320
- console.info('💬 OpenAI Agent callChatModel call', { prompt });
23321
- }
23322
- const { content, parameters, modelRequirements } = prompt;
23323
- const client = await this.getClient();
23324
- if (modelRequirements.modelVariant !== 'CHAT') {
23325
- throw new PipelineExecutionError('Use callChatModel only for CHAT variant');
23326
- }
23327
- const rawPromptContent = templateParameters(content, {
23328
- ...parameters,
23329
- modelName: 'agent',
23330
- });
23331
- // Build input items
23332
- const input = []; // TODO: Type properly when OpenAI types are updated
23333
- // Add previous messages from thread (if any)
23334
- if ('thread' in prompt && Array.isArray(prompt.thread)) {
23335
- const previousMessages = prompt.thread.map((msg) => ({
23336
- role: msg.sender === 'assistant' ? 'assistant' : 'user',
23337
- content: msg.content,
23338
- }));
23339
- input.push(...previousMessages);
23340
- }
23341
- // Add current user message
23342
- input.push({
23343
- role: 'user',
23344
- content: rawPromptContent,
23345
- });
23346
- // Prepare tools
23347
- const tools = modelRequirements.tools ? mapToolsToOpenAi(modelRequirements.tools) : undefined;
23348
- // Add file_search if vector store is present
23349
- const agentTools = tools ? [...tools] : [];
23350
- let toolResources = undefined;
23351
- if (this.vectorStoreId) {
23352
- agentTools.push({ type: 'file_search' });
23353
- toolResources = {
23354
- file_search: {
23355
- vector_store_ids: [this.vectorStoreId],
23356
- },
23357
- };
23358
- }
23359
- // Add file_search also if knowledgeSources are present in the prompt (passed via AgentLlmExecutionTools)
23360
- if (modelRequirements.knowledgeSources &&
23361
- modelRequirements.knowledgeSources.length > 0 &&
23362
- !this.vectorStoreId) {
23363
- // Note: Vector store should have been created by AgentLlmExecutionTools and passed via options.
23364
- // If we are here, it means we have knowledge sources but no vector store ID.
23365
- // We can't easily create one here without persisting it.
23366
- console.warn('Knowledge sources provided but no vector store ID. Creating temporary vector store is not implemented in callChatModelStream.');
23367
- }
23368
- const start = $getCurrentDate();
23369
- // Construct the request
23370
- const rawRequest = {
23371
- // TODO: Type properly as OpenAI.Responses.CreateResponseParams
23372
- model: modelRequirements.modelName || 'gpt-4o',
23373
- input,
23374
- instructions: modelRequirements.systemMessage,
23375
- tools: agentTools.length > 0 ? agentTools : undefined,
23376
- tool_resources: toolResources,
23377
- store: false, // Stateless by default as we pass full history
23378
- };
23379
- if (this.options.isVerbose) {
23380
- console.info(colors.bgWhite('rawRequest (Responses API)'), JSON.stringify(rawRequest, null, 4));
23381
- }
23382
- // Call Responses API
23383
- // Note: Using any cast because types might not be updated yet
23384
- const response = await client.responses.create(rawRequest);
23385
- if (this.options.isVerbose) {
23386
- console.info(colors.bgWhite('rawResponse'), JSON.stringify(response, null, 4));
23387
- }
23388
- const complete = $getCurrentDate();
23389
- let resultContent = '';
23390
- const toolCalls = [];
23391
- // Parse output items
23392
- if (response.output) {
23393
- for (const item of response.output) {
23394
- if (item.type === 'message' && item.role === 'assistant') {
23395
- for (const contentPart of item.content) {
23396
- if (contentPart.type === 'output_text') {
23397
- // "output_text" based on migration guide, or "text"? Guide says "output_text" in example.
23398
- resultContent += contentPart.text;
23399
- }
23400
- else if (contentPart.type === 'text') {
23401
- resultContent += contentPart.text.value || contentPart.text;
23402
- }
23403
- }
23404
- }
23405
- else if (item.type === 'function_call') ;
23406
- }
23407
- }
23408
- // Use output_text helper if available (mentioned in guide)
23409
- if (response.output_text) {
23410
- resultContent = response.output_text;
23411
- }
23412
- // TODO: Handle tool calls properly (Requires clearer docs or experimentation)
23413
- onProgress({
23414
- content: resultContent,
23415
- modelName: response.model || 'agent',
23416
- timing: { start, complete },
23417
- usage: UNCERTAIN_USAGE,
23418
- rawPromptContent,
23419
- rawRequest,
23420
- rawResponse: response,
23421
- });
23422
- return exportJson({
23423
- name: 'promptResult',
23424
- message: `Result of \`OpenAiAgentExecutionTools.callChatModelStream\``,
23425
- order: [],
23426
- value: {
23427
- content: resultContent,
23428
- modelName: response.model || 'agent',
23429
- timing: { start, complete },
23430
- usage: UNCERTAIN_USAGE,
23431
- rawPromptContent,
23432
- rawRequest,
23433
- rawResponse: response,
23434
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
23435
- },
23436
- });
23437
- }
23438
- /**
23439
- * Creates a vector store from knowledge sources
23440
- */
23441
- static async createVectorStore(client, name, knowledgeSources) {
23442
- // Create a vector store
23443
- const vectorStore = await client.beta.vectorStores.create({
23444
- name: `${name} Knowledge Base`,
23445
- });
23446
- const vectorStoreId = vectorStore.id;
23447
- // Upload files from knowledge sources to the vector store
23448
- const fileStreams = [];
23449
- for (const source of knowledgeSources) {
23450
- try {
23451
- // Check if it's a URL
23452
- if (source.startsWith('http://') || source.startsWith('https://')) {
23453
- // Download the file
23454
- const response = await fetch(source);
23455
- if (!response.ok) {
23456
- console.error(`Failed to download ${source}: ${response.statusText}`);
23457
- continue;
23458
- }
23459
- const buffer = await response.arrayBuffer();
23460
- const filename = source.split('/').pop() || 'downloaded-file';
23461
- const blob = new Blob([buffer]);
23462
- const file = new File([blob], filename);
23463
- fileStreams.push(file);
23464
- }
23465
- else {
23466
- // Local files not supported in browser env easily, same as before
23467
- }
23468
- }
23469
- catch (error) {
23470
- console.error(`Error processing knowledge source ${source}:`, error);
23471
- }
23472
- }
23473
- // Batch upload files to the vector store
23474
- if (fileStreams.length > 0) {
23475
- try {
23476
- await client.beta.vectorStores.fileBatches.uploadAndPoll(vectorStoreId, {
23477
- files: fileStreams,
23478
- });
23479
- }
23480
- catch (error) {
23481
- console.error('Error uploading files to vector store:', error);
23482
- }
23483
- }
23484
- return vectorStoreId;
23485
- }
23486
- /**
23487
- * Discriminant for type guards
23488
- */
23489
- get discriminant() {
23490
- return 'OPEN_AI_AGENT';
23491
- }
23492
- /**
23493
- * Type guard to check if given `LlmExecutionTools` are instanceof `OpenAiAgentExecutionTools`
23494
- */
23495
- static isOpenAiAgentExecutionTools(llmExecutionTools) {
23496
- return llmExecutionTools.discriminant === 'OPEN_AI_AGENT';
23497
- }
23498
- }
23499
-
23500
23293
  /**
23501
23294
  * Uploads files to OpenAI and returns their IDs
23502
23295
  *
@@ -23518,6 +23311,10 @@ async function uploadFilesToOpenAi(client, files) {
23518
23311
  return fileIds;
23519
23312
  }
23520
23313
 
23314
+ const DEFAULT_KNOWLEDGE_SOURCE_DOWNLOAD_TIMEOUT_MS = 30000;
23315
+ const DEFAULT_KNOWLEDGE_SOURCE_UPLOAD_TIMEOUT_MS = 900000;
23316
+ const VECTOR_STORE_PROGRESS_LOG_INTERVAL_MIN_MS = 15000;
23317
+ const VECTOR_STORE_STALL_LOG_THRESHOLD_MS = 30000;
23521
23318
  /**
23522
23319
  * Execution Tools for calling OpenAI API Assistants
23523
23320
  *
@@ -23531,7 +23328,6 @@ async function uploadFilesToOpenAi(client, files) {
23531
23328
  * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
23532
23329
  *
23533
23330
  * @public exported from `@promptbook/openai`
23534
- * @deprecated Use `OpenAiAgentExecutionTools` instead which uses the new OpenAI Responses API
23535
23331
  */
23536
23332
  class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
23537
23333
  /**
@@ -23968,111 +23764,731 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
23968
23764
  assistantId,
23969
23765
  });
23970
23766
  }
23971
- async createNewAssistant(options) {
23972
- var _a, _b, _c;
23973
- if (!this.isCreatingNewAssistantsAllowed) {
23974
- throw new NotAllowed(`Creating new assistants is not allowed. Set \`isCreatingNewAssistantsAllowed: true\` in options to enable this feature.`);
23975
- }
23976
- // await this.playground();
23977
- const { name, instructions, knowledgeSources, tools } = options;
23978
- const preparationStartedAtMs = Date.now();
23979
- const knowledgeSourcesCount = (_a = knowledgeSources === null || knowledgeSources === void 0 ? void 0 : knowledgeSources.length) !== null && _a !== void 0 ? _a : 0;
23980
- const toolsCount = (_b = tools === null || tools === void 0 ? void 0 : tools.length) !== null && _b !== void 0 ? _b : 0;
23767
+ /**
23768
+ * Returns the per-knowledge-source download timeout in milliseconds.
23769
+ */
23770
+ getKnowledgeSourceDownloadTimeoutMs() {
23771
+ var _a;
23772
+ return (_a = this.assistantOptions.knowledgeSourceDownloadTimeoutMs) !== null && _a !== void 0 ? _a : DEFAULT_KNOWLEDGE_SOURCE_DOWNLOAD_TIMEOUT_MS;
23773
+ }
23774
+ /**
23775
+ * Returns the max concurrency for knowledge source uploads.
23776
+ */
23777
+ getKnowledgeSourceUploadMaxConcurrency() {
23778
+ var _a;
23779
+ return (_a = this.assistantOptions.knowledgeSourceUploadMaxConcurrency) !== null && _a !== void 0 ? _a : 5;
23780
+ }
23781
+ /**
23782
+ * Returns the polling interval in milliseconds for vector store uploads.
23783
+ */
23784
+ getKnowledgeSourceUploadPollIntervalMs() {
23785
+ var _a;
23786
+ return (_a = this.assistantOptions.knowledgeSourceUploadPollIntervalMs) !== null && _a !== void 0 ? _a : 5000;
23787
+ }
23788
+ /**
23789
+ * Returns the overall upload timeout in milliseconds for vector store uploads.
23790
+ */
23791
+ getKnowledgeSourceUploadTimeoutMs() {
23792
+ var _a;
23793
+ return (_a = this.assistantOptions.knowledgeSourceUploadTimeoutMs) !== null && _a !== void 0 ? _a : DEFAULT_KNOWLEDGE_SOURCE_UPLOAD_TIMEOUT_MS;
23794
+ }
23795
+ /**
23796
+ * Returns true if we should continue even if vector store ingestion stalls.
23797
+ */
23798
+ shouldContinueOnVectorStoreStall() {
23799
+ var _a;
23800
+ return (_a = this.assistantOptions.shouldContinueOnVectorStoreStall) !== null && _a !== void 0 ? _a : true;
23801
+ }
23802
+ /**
23803
+ * Returns assistant-specific options with extended settings.
23804
+ */
23805
+ get assistantOptions() {
23806
+ return this.options;
23807
+ }
23808
+ /**
23809
+ * Downloads a knowledge source URL into a File for vector store upload.
23810
+ */
23811
+ async downloadKnowledgeSourceFile(options) {
23812
+ var _a;
23813
+ const { source, timeoutMs, logLabel } = options;
23814
+ const startedAtMs = Date.now();
23815
+ const controller = new AbortController();
23816
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
23981
23817
  if (this.options.isVerbose) {
23982
- console.info('[🤰]', 'Starting OpenAI assistant creation', {
23983
- name,
23984
- knowledgeSourcesCount,
23985
- toolsCount,
23986
- instructionsLength: instructions.length,
23818
+ console.info('[🤰]', 'Downloading knowledge source', {
23819
+ source,
23820
+ timeoutMs,
23821
+ logLabel,
23987
23822
  });
23988
23823
  }
23989
- const client = await this.getClient();
23990
- let vectorStoreId;
23991
- // If knowledge sources are provided, create a vector store with them
23992
- if (knowledgeSources && knowledgeSources.length > 0) {
23993
- if (this.options.isVerbose) {
23994
- console.info('[🤰]', 'Creating vector store with knowledge sources', {
23995
- name,
23996
- knowledgeSourcesCount,
23824
+ try {
23825
+ const response = await fetch(source, { signal: controller.signal });
23826
+ const contentType = (_a = response.headers.get('content-type')) !== null && _a !== void 0 ? _a : undefined;
23827
+ if (!response.ok) {
23828
+ console.error('[🤰]', 'Failed to download knowledge source', {
23829
+ source,
23830
+ status: response.status,
23831
+ statusText: response.statusText,
23832
+ contentType,
23833
+ elapsedMs: Date.now() - startedAtMs,
23834
+ logLabel,
23997
23835
  });
23836
+ return null;
23998
23837
  }
23999
- // Create a vector store
24000
- const vectorStore = await client.beta.vectorStores.create({
24001
- name: `${name} Knowledge Base`,
24002
- });
24003
- vectorStoreId = vectorStore.id;
23838
+ const buffer = await response.arrayBuffer();
23839
+ let filename = source.split('/').pop() || 'downloaded-file';
23840
+ try {
23841
+ const url = new URL(source);
23842
+ filename = url.pathname.split('/').pop() || filename;
23843
+ }
23844
+ catch (error) {
23845
+ // Keep default filename
23846
+ }
23847
+ const file = new File([buffer], filename, contentType ? { type: contentType } : undefined);
23848
+ const elapsedMs = Date.now() - startedAtMs;
23849
+ const sizeBytes = buffer.byteLength;
24004
23850
  if (this.options.isVerbose) {
24005
- console.info('[🤰]', 'Vector store created', {
24006
- vectorStoreId,
23851
+ console.info('[🤰]', 'Downloaded knowledge source', {
23852
+ source,
23853
+ filename,
23854
+ sizeBytes,
23855
+ contentType,
23856
+ elapsedMs,
23857
+ logLabel,
24007
23858
  });
24008
23859
  }
24009
- // Upload files from knowledge sources to the vector store
24010
- const fileStreams = [];
24011
- for (const [index, source] of knowledgeSources.entries()) {
23860
+ return { file, sizeBytes, filename, elapsedMs };
23861
+ }
23862
+ catch (error) {
23863
+ assertsError(error);
23864
+ console.error('[🤰]', 'Error downloading knowledge source', {
23865
+ source,
23866
+ elapsedMs: Date.now() - startedAtMs,
23867
+ logLabel,
23868
+ error: serializeError(error),
23869
+ });
23870
+ return null;
23871
+ }
23872
+ finally {
23873
+ clearTimeout(timeoutId);
23874
+ }
23875
+ }
23876
+ /**
23877
+ * Logs vector store file batch diagnostics to help trace ingestion stalls or failures.
23878
+ */
23879
+ async logVectorStoreFileBatchDiagnostics(options) {
23880
+ var _a, _b;
23881
+ const { client, vectorStoreId, batchId, uploadedFiles, logLabel, reason } = options;
23882
+ if (reason === 'stalled' && !this.options.isVerbose) {
23883
+ return;
23884
+ }
23885
+ if (!batchId.startsWith('vsfb_')) {
23886
+ console.error('[🤰]', 'Vector store file batch diagnostics skipped (invalid batch id)', {
23887
+ vectorStoreId,
23888
+ batchId,
23889
+ reason,
23890
+ logLabel,
23891
+ });
23892
+ return;
23893
+ }
23894
+ const fileIdToMetadata = new Map();
23895
+ for (const file of uploadedFiles) {
23896
+ fileIdToMetadata.set(file.fileId, file);
23897
+ }
23898
+ try {
23899
+ const limit = Math.min(100, Math.max(10, uploadedFiles.length));
23900
+ const batchFilesPage = await client.beta.vectorStores.fileBatches.listFiles(vectorStoreId, batchId, {
23901
+ limit,
23902
+ });
23903
+ const batchFiles = (_a = batchFilesPage.data) !== null && _a !== void 0 ? _a : [];
23904
+ const statusCounts = {
23905
+ in_progress: 0,
23906
+ completed: 0,
23907
+ failed: 0,
23908
+ cancelled: 0,
23909
+ };
23910
+ const errorSamples = [];
23911
+ const inProgressSamples = [];
23912
+ const batchFileIds = new Set();
23913
+ for (const file of batchFiles) {
23914
+ batchFileIds.add(file.id);
23915
+ statusCounts[file.status] = ((_b = statusCounts[file.status]) !== null && _b !== void 0 ? _b : 0) + 1;
23916
+ const metadata = fileIdToMetadata.get(file.id);
23917
+ if (file.last_error) {
23918
+ errorSamples.push({
23919
+ fileId: file.id,
23920
+ filename: metadata === null || metadata === void 0 ? void 0 : metadata.filename,
23921
+ sizeBytes: metadata === null || metadata === void 0 ? void 0 : metadata.sizeBytes,
23922
+ status: file.status,
23923
+ lastError: file.last_error,
23924
+ });
23925
+ }
23926
+ else if (file.status === 'in_progress' && inProgressSamples.length < 5) {
23927
+ inProgressSamples.push({
23928
+ fileId: file.id,
23929
+ filename: metadata === null || metadata === void 0 ? void 0 : metadata.filename,
23930
+ sizeBytes: metadata === null || metadata === void 0 ? void 0 : metadata.sizeBytes,
23931
+ });
23932
+ }
23933
+ }
23934
+ const missingSamples = uploadedFiles
23935
+ .filter((file) => !batchFileIds.has(file.fileId))
23936
+ .slice(0, 5)
23937
+ .map((file) => ({
23938
+ fileId: file.fileId,
23939
+ filename: file.filename,
23940
+ sizeBytes: file.sizeBytes,
23941
+ }));
23942
+ const vectorStore = await client.beta.vectorStores.retrieve(vectorStoreId);
23943
+ const logPayload = {
23944
+ vectorStoreId,
23945
+ batchId,
23946
+ reason,
23947
+ vectorStoreStatus: vectorStore.status,
23948
+ vectorStoreFileCounts: vectorStore.file_counts,
23949
+ vectorStoreUsageBytes: vectorStore.usage_bytes,
23950
+ batchFileCount: batchFiles.length,
23951
+ statusCounts,
23952
+ errorSamples: errorSamples.slice(0, 5),
23953
+ inProgressSamples,
23954
+ missingFileCount: uploadedFiles.length - batchFileIds.size,
23955
+ missingSamples,
23956
+ logLabel,
23957
+ };
23958
+ const logFunction = reason === 'stalled' ? console.info : console.error;
23959
+ logFunction('[🤰]', 'Vector store file batch diagnostics', logPayload);
23960
+ }
23961
+ catch (error) {
23962
+ assertsError(error);
23963
+ console.error('[🤰]', 'Vector store file batch diagnostics failed', {
23964
+ vectorStoreId,
23965
+ batchId,
23966
+ reason,
23967
+ logLabel,
23968
+ error: serializeError(error),
23969
+ });
23970
+ }
23971
+ }
23972
+ /**
23973
+ * Uploads knowledge source files to the vector store and polls until processing completes.
23974
+ */
23975
+ async uploadKnowledgeSourceFilesToVectorStore(options) {
23976
+ var _a, _b, _c, _d;
23977
+ const { client, vectorStoreId, files, totalBytes, logLabel } = options;
23978
+ const uploadStartedAtMs = Date.now();
23979
+ const maxConcurrency = Math.max(1, this.getKnowledgeSourceUploadMaxConcurrency());
23980
+ const pollIntervalMs = Math.max(1000, this.getKnowledgeSourceUploadPollIntervalMs());
23981
+ const uploadTimeoutMs = Math.max(1000, this.getKnowledgeSourceUploadTimeoutMs());
23982
+ if (this.options.isVerbose) {
23983
+ console.info('[🤰]', 'Uploading knowledge source files to OpenAI', {
23984
+ vectorStoreId,
23985
+ fileCount: files.length,
23986
+ totalBytes,
23987
+ maxConcurrency,
23988
+ pollIntervalMs,
23989
+ uploadTimeoutMs,
23990
+ logLabel,
23991
+ });
23992
+ }
23993
+ const fileTypeSummary = {};
23994
+ for (const file of files) {
23995
+ const filename = (_a = file.name) !== null && _a !== void 0 ? _a : '';
23996
+ const extension = filename.includes('.')
23997
+ ? (_c = (_b = filename.split('.').pop()) === null || _b === void 0 ? void 0 : _b.toLowerCase()) !== null && _c !== void 0 ? _c : 'unknown'
23998
+ : 'unknown';
23999
+ const sizeBytes = typeof file.size === 'number' ? file.size : 0;
24000
+ const summary = (_d = fileTypeSummary[extension]) !== null && _d !== void 0 ? _d : { count: 0, totalBytes: 0 };
24001
+ summary.count += 1;
24002
+ summary.totalBytes += sizeBytes;
24003
+ fileTypeSummary[extension] = summary;
24004
+ }
24005
+ if (this.options.isVerbose) {
24006
+ console.info('[🤰]', 'Knowledge source file summary', {
24007
+ vectorStoreId,
24008
+ fileCount: files.length,
24009
+ totalBytes,
24010
+ fileTypeSummary,
24011
+ logLabel,
24012
+ });
24013
+ }
24014
+ const fileEntries = files.map((file, index) => ({ file, index }));
24015
+ const fileIterator = fileEntries.values();
24016
+ const fileIds = [];
24017
+ const uploadedFiles = [];
24018
+ const failedUploads = [];
24019
+ let uploadedCount = 0;
24020
+ const processFiles = async (iterator) => {
24021
+ var _a, _b;
24022
+ for (const { file, index } of iterator) {
24023
+ const uploadIndex = index + 1;
24024
+ const filename = file.name || `knowledge-source-${uploadIndex}`;
24025
+ const extension = filename.includes('.')
24026
+ ? (_b = (_a = filename.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : 'unknown'
24027
+ : 'unknown';
24028
+ const sizeBytes = typeof file.size === 'number' ? file.size : undefined;
24029
+ const fileUploadStartedAtMs = Date.now();
24030
+ if (this.options.isVerbose) {
24031
+ console.info('[🤰]', 'Uploading knowledge source file', {
24032
+ index: uploadIndex,
24033
+ total: files.length,
24034
+ filename,
24035
+ extension,
24036
+ sizeBytes,
24037
+ logLabel,
24038
+ });
24039
+ }
24012
24040
  try {
24041
+ const uploaded = await client.files.create({ file, purpose: 'assistants' });
24042
+ fileIds.push(uploaded.id);
24043
+ uploadedFiles.push({ fileId: uploaded.id, filename, sizeBytes });
24044
+ uploadedCount += 1;
24013
24045
  if (this.options.isVerbose) {
24014
- console.info('[🤰]', 'Processing knowledge source', {
24015
- index: index + 1,
24016
- total: knowledgeSources.length,
24017
- source,
24018
- sourceType: source.startsWith('http') || source.startsWith('https') ? 'url' : 'file',
24046
+ console.info('[🤰]', 'Uploaded knowledge source file', {
24047
+ index: uploadIndex,
24048
+ total: files.length,
24049
+ filename,
24050
+ sizeBytes,
24051
+ fileId: uploaded.id,
24052
+ elapsedMs: Date.now() - fileUploadStartedAtMs,
24053
+ logLabel,
24019
24054
  });
24020
24055
  }
24021
- // Check if it's a URL
24022
- if (source.startsWith('http://') || source.startsWith('https://')) {
24023
- // Download the file
24024
- const response = await fetch(source);
24025
- if (!response.ok) {
24026
- console.error(`Failed to download ${source}: ${response.statusText}`);
24027
- continue;
24028
- }
24029
- const buffer = await response.arrayBuffer();
24030
- let filename = source.split('/').pop() || 'downloaded-file';
24031
- try {
24032
- const url = new URL(source);
24033
- filename = url.pathname.split('/').pop() || filename;
24034
- }
24035
- catch (error) {
24036
- // Keep default filename
24037
- }
24038
- const blob = new Blob([buffer]);
24039
- const file = new File([blob], filename);
24040
- fileStreams.push(file);
24041
- }
24042
- else {
24043
- /*
24044
- TODO: [🐱‍🚀] Resolve problem with browser environment
24045
- // Assume it's a local file path
24046
- // Note: This will work in Node.js environment
24047
- // For browser environments, this would need different handling
24048
- const fs = await import('fs');
24049
- const fileStream = fs.createReadStream(source);
24050
- fileStreams.push(fileStream);
24051
- */
24052
- }
24053
24056
  }
24054
24057
  catch (error) {
24055
- console.error(`Error processing knowledge source ${source}:`, error);
24058
+ assertsError(error);
24059
+ const serializedError = serializeError(error);
24060
+ failedUploads.push({ index: uploadIndex, filename, error: serializedError });
24061
+ console.error('[🤰]', 'Failed to upload knowledge source file', {
24062
+ index: uploadIndex,
24063
+ total: files.length,
24064
+ filename,
24065
+ sizeBytes,
24066
+ elapsedMs: Date.now() - fileUploadStartedAtMs,
24067
+ logLabel,
24068
+ error: serializedError,
24069
+ });
24056
24070
  }
24057
24071
  }
24058
- // Batch upload files to the vector store
24059
- if (fileStreams.length > 0) {
24060
- try {
24061
- await client.beta.vectorStores.fileBatches.uploadAndPoll(vectorStoreId, {
24062
- files: fileStreams,
24072
+ };
24073
+ const workerCount = Math.min(maxConcurrency, files.length);
24074
+ const workers = Array.from({ length: workerCount }, () => processFiles(fileIterator));
24075
+ await Promise.all(workers);
24076
+ if (this.options.isVerbose) {
24077
+ console.info('[🤰]', 'Finished uploading knowledge source files', {
24078
+ vectorStoreId,
24079
+ fileCount: files.length,
24080
+ uploadedCount,
24081
+ failedCount: failedUploads.length,
24082
+ elapsedMs: Date.now() - uploadStartedAtMs,
24083
+ failedSamples: failedUploads.slice(0, 3),
24084
+ logLabel,
24085
+ });
24086
+ }
24087
+ if (fileIds.length === 0) {
24088
+ console.error('[🤰]', 'No knowledge source files were uploaded', {
24089
+ vectorStoreId,
24090
+ fileCount: files.length,
24091
+ failedCount: failedUploads.length,
24092
+ logLabel,
24093
+ });
24094
+ return null;
24095
+ }
24096
+ const batch = await client.beta.vectorStores.fileBatches.create(vectorStoreId, {
24097
+ file_ids: fileIds,
24098
+ });
24099
+ const expectedBatchId = batch.id;
24100
+ const expectedBatchIdValid = expectedBatchId.startsWith('vsfb_');
24101
+ if (!expectedBatchIdValid) {
24102
+ console.error('[🤰]', 'Vector store file batch id looks invalid', {
24103
+ vectorStoreId,
24104
+ batchId: expectedBatchId,
24105
+ batchVectorStoreId: batch.vector_store_id,
24106
+ logLabel,
24107
+ });
24108
+ }
24109
+ else if (batch.vector_store_id !== vectorStoreId) {
24110
+ console.error('[🤰]', 'Vector store file batch vector store id mismatch', {
24111
+ vectorStoreId,
24112
+ batchId: expectedBatchId,
24113
+ batchVectorStoreId: batch.vector_store_id,
24114
+ logLabel,
24115
+ });
24116
+ }
24117
+ if (this.options.isVerbose) {
24118
+ console.info('[🤰]', 'Created vector store file batch', {
24119
+ vectorStoreId,
24120
+ batchId: expectedBatchId,
24121
+ fileCount: fileIds.length,
24122
+ logLabel,
24123
+ });
24124
+ }
24125
+ const pollStartedAtMs = Date.now();
24126
+ const progressLogIntervalMs = Math.max(VECTOR_STORE_PROGRESS_LOG_INTERVAL_MIN_MS, pollIntervalMs);
24127
+ const diagnosticsIntervalMs = Math.max(60000, pollIntervalMs * 5);
24128
+ let lastStatus;
24129
+ let lastCountsKey = '';
24130
+ let lastProgressKey = '';
24131
+ let lastLogAtMs = 0;
24132
+ let lastProgressAtMs = pollStartedAtMs;
24133
+ let lastDiagnosticsAtMs = pollStartedAtMs;
24134
+ let latestBatch = batch;
24135
+ let loggedBatchIdMismatch = false;
24136
+ let shouldPoll = true;
24137
+ while (shouldPoll) {
24138
+ latestBatch = await client.beta.vectorStores.fileBatches.retrieve(vectorStoreId, expectedBatchId);
24139
+ const counts = latestBatch.file_counts;
24140
+ const countsKey = `${counts.completed}/${counts.failed}/${counts.in_progress}/${counts.cancelled}/${counts.total}`;
24141
+ const nowMs = Date.now();
24142
+ const returnedBatchId = latestBatch.id;
24143
+ // [🤰] Note: Sometimes OpenAI returns Vector Store object instead of Batch object, or IDs get swapped.
24144
+ // We only consider it a mismatch if the returned ID looks like a Batch ID.
24145
+ const batchIdMismatch = returnedBatchId !== expectedBatchId && returnedBatchId.startsWith('vsfb_');
24146
+ const diagnosticsBatchId = batchIdMismatch && returnedBatchId.startsWith('vsfb_') ? returnedBatchId : expectedBatchId;
24147
+ const shouldLog = this.options.isVerbose &&
24148
+ (latestBatch.status !== lastStatus ||
24149
+ countsKey !== lastCountsKey ||
24150
+ nowMs - lastLogAtMs >= progressLogIntervalMs);
24151
+ if (batchIdMismatch && !loggedBatchIdMismatch) {
24152
+ console.error('[🤰]', 'Vector store file batch id mismatch', {
24153
+ vectorStoreId,
24154
+ expectedBatchId,
24155
+ returnedBatchId,
24156
+ status: latestBatch.status,
24157
+ fileCounts: counts,
24158
+ logLabel,
24159
+ });
24160
+ loggedBatchIdMismatch = true;
24161
+ }
24162
+ if (countsKey !== lastProgressKey) {
24163
+ lastProgressKey = countsKey;
24164
+ lastProgressAtMs = nowMs;
24165
+ }
24166
+ if (shouldLog) {
24167
+ console.info('[🤰]', 'Vector store file batch status', {
24168
+ vectorStoreId,
24169
+ batchId: expectedBatchId,
24170
+ ...(batchIdMismatch ? { returnedBatchId } : {}),
24171
+ status: latestBatch.status,
24172
+ fileCounts: counts,
24173
+ elapsedMs: nowMs - pollStartedAtMs,
24174
+ logLabel,
24175
+ });
24176
+ // [🤰] If there are in-progress files for a long time, log their details
24177
+ if (counts.in_progress > 0 && nowMs - lastProgressAtMs > VECTOR_STORE_STALL_LOG_THRESHOLD_MS) {
24178
+ await this.logVectorStoreFileBatchDiagnostics({
24179
+ client,
24180
+ vectorStoreId,
24181
+ batchId: diagnosticsBatchId,
24182
+ uploadedFiles,
24183
+ logLabel,
24184
+ reason: 'stalled',
24185
+ });
24186
+ }
24187
+ lastStatus = latestBatch.status;
24188
+ lastCountsKey = countsKey;
24189
+ lastLogAtMs = nowMs;
24190
+ }
24191
+ if (nowMs - lastProgressAtMs >= diagnosticsIntervalMs &&
24192
+ nowMs - lastDiagnosticsAtMs >= diagnosticsIntervalMs) {
24193
+ lastDiagnosticsAtMs = nowMs;
24194
+ await this.logVectorStoreFileBatchDiagnostics({
24195
+ client,
24196
+ vectorStoreId,
24197
+ batchId: diagnosticsBatchId,
24198
+ uploadedFiles,
24199
+ logLabel,
24200
+ reason: 'stalled',
24201
+ });
24202
+ }
24203
+ if (latestBatch.status === 'completed') {
24204
+ if (this.options.isVerbose) {
24205
+ console.info('[🤰]', 'Vector store file batch completed', {
24206
+ vectorStoreId,
24207
+ batchId: expectedBatchId,
24208
+ ...(batchIdMismatch ? { returnedBatchId } : {}),
24209
+ fileCounts: latestBatch.file_counts,
24210
+ elapsedMs: Date.now() - uploadStartedAtMs,
24211
+ logLabel,
24063
24212
  });
24213
+ }
24214
+ if (latestBatch.file_counts.failed > 0) {
24215
+ console.error('[🤰]', 'Vector store file batch completed with failures', {
24216
+ vectorStoreId,
24217
+ batchId: expectedBatchId,
24218
+ ...(batchIdMismatch ? { returnedBatchId } : {}),
24219
+ fileCounts: latestBatch.file_counts,
24220
+ logLabel,
24221
+ });
24222
+ await this.logVectorStoreFileBatchDiagnostics({
24223
+ client,
24224
+ vectorStoreId,
24225
+ batchId: diagnosticsBatchId,
24226
+ uploadedFiles,
24227
+ logLabel,
24228
+ reason: 'failed',
24229
+ });
24230
+ }
24231
+ shouldPoll = false;
24232
+ continue;
24233
+ }
24234
+ if (latestBatch.status === 'failed' || latestBatch.status === 'cancelled') {
24235
+ console.error('[🤰]', 'Vector store file batch did not complete', {
24236
+ vectorStoreId,
24237
+ batchId: expectedBatchId,
24238
+ ...(batchIdMismatch ? { returnedBatchId } : {}),
24239
+ status: latestBatch.status,
24240
+ fileCounts: latestBatch.file_counts,
24241
+ elapsedMs: Date.now() - uploadStartedAtMs,
24242
+ logLabel,
24243
+ });
24244
+ await this.logVectorStoreFileBatchDiagnostics({
24245
+ client,
24246
+ vectorStoreId,
24247
+ batchId: diagnosticsBatchId,
24248
+ uploadedFiles,
24249
+ logLabel,
24250
+ reason: 'failed',
24251
+ });
24252
+ shouldPoll = false;
24253
+ continue;
24254
+ }
24255
+ if (nowMs - pollStartedAtMs >= uploadTimeoutMs) {
24256
+ console.error('[🤰]', 'Timed out waiting for vector store file batch', {
24257
+ vectorStoreId,
24258
+ batchId: expectedBatchId,
24259
+ ...(batchIdMismatch ? { returnedBatchId } : {}),
24260
+ fileCounts: latestBatch.file_counts,
24261
+ elapsedMs: nowMs - pollStartedAtMs,
24262
+ uploadTimeoutMs,
24263
+ logLabel,
24264
+ });
24265
+ await this.logVectorStoreFileBatchDiagnostics({
24266
+ client,
24267
+ vectorStoreId,
24268
+ batchId: diagnosticsBatchId,
24269
+ uploadedFiles,
24270
+ logLabel,
24271
+ reason: 'timeout',
24272
+ });
24273
+ if (this.shouldContinueOnVectorStoreStall()) {
24274
+ console.warn('[🤰]', 'Continuing despite vector store timeout as requested', {
24275
+ vectorStoreId,
24276
+ logLabel,
24277
+ });
24278
+ shouldPoll = false;
24279
+ continue;
24280
+ }
24281
+ try {
24282
+ const cancelBatchId = batchIdMismatch && returnedBatchId.startsWith('vsfb_') ? returnedBatchId : expectedBatchId;
24283
+ if (!cancelBatchId.startsWith('vsfb_')) {
24284
+ console.error('[🤰]', 'Skipping vector store file batch cancel (invalid batch id)', {
24285
+ vectorStoreId,
24286
+ batchId: cancelBatchId,
24287
+ logLabel,
24288
+ });
24289
+ }
24290
+ else {
24291
+ await client.beta.vectorStores.fileBatches.cancel(vectorStoreId, cancelBatchId);
24292
+ }
24064
24293
  if (this.options.isVerbose) {
24065
- console.info('[🤰]', 'Uploaded files to vector store', {
24294
+ console.info('[🤰]', 'Cancelled vector store file batch after timeout', {
24066
24295
  vectorStoreId,
24067
- fileCount: fileStreams.length,
24296
+ batchId: batchIdMismatch && returnedBatchId.startsWith('vsfb_')
24297
+ ? returnedBatchId
24298
+ : expectedBatchId,
24299
+ ...(batchIdMismatch ? { returnedBatchId } : {}),
24300
+ logLabel,
24068
24301
  });
24069
24302
  }
24070
24303
  }
24071
24304
  catch (error) {
24072
- console.error('Error uploading files to vector store:', error);
24305
+ assertsError(error);
24306
+ console.error('[🤰]', 'Failed to cancel vector store file batch after timeout', {
24307
+ vectorStoreId,
24308
+ batchId: expectedBatchId,
24309
+ ...(batchIdMismatch ? { returnedBatchId } : {}),
24310
+ logLabel,
24311
+ error: serializeError(error),
24312
+ });
24313
+ }
24314
+ shouldPoll = false;
24315
+ continue;
24316
+ }
24317
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
24318
+ }
24319
+ return latestBatch;
24320
+ }
24321
+ /**
24322
+ * Creates a vector store and uploads knowledge sources, returning its ID.
24323
+ */
24324
+ async createVectorStoreWithKnowledgeSources(options) {
24325
+ const { client, name, knowledgeSources, logLabel } = options;
24326
+ const knowledgeSourcesCount = knowledgeSources.length;
24327
+ const downloadTimeoutMs = this.getKnowledgeSourceDownloadTimeoutMs();
24328
+ if (this.options.isVerbose) {
24329
+ console.info('[🤰]', 'Creating vector store with knowledge sources', {
24330
+ name,
24331
+ knowledgeSourcesCount,
24332
+ downloadTimeoutMs,
24333
+ logLabel,
24334
+ });
24335
+ }
24336
+ const vectorStore = await client.beta.vectorStores.create({
24337
+ name: `${name} Knowledge Base`,
24338
+ });
24339
+ const vectorStoreId = vectorStore.id;
24340
+ if (this.options.isVerbose) {
24341
+ console.info('[🤰]', 'Vector store created', {
24342
+ vectorStoreId,
24343
+ logLabel,
24344
+ });
24345
+ }
24346
+ const fileStreams = [];
24347
+ const skippedSources = [];
24348
+ let totalBytes = 0;
24349
+ const processingStartedAtMs = Date.now();
24350
+ for (const [index, source] of knowledgeSources.entries()) {
24351
+ try {
24352
+ const sourceType = source.startsWith('http') || source.startsWith('https') ? 'url' : 'file';
24353
+ if (this.options.isVerbose) {
24354
+ console.info('[🤰]', 'Processing knowledge source', {
24355
+ index: index + 1,
24356
+ total: knowledgeSourcesCount,
24357
+ source,
24358
+ sourceType,
24359
+ logLabel,
24360
+ });
24361
+ }
24362
+ // Check if it's a URL
24363
+ if (source.startsWith('http://') || source.startsWith('https://')) {
24364
+ const downloadResult = await this.downloadKnowledgeSourceFile({
24365
+ source,
24366
+ timeoutMs: downloadTimeoutMs,
24367
+ logLabel,
24368
+ });
24369
+ if (downloadResult) {
24370
+ fileStreams.push(downloadResult.file);
24371
+ totalBytes += downloadResult.sizeBytes;
24372
+ }
24373
+ else {
24374
+ skippedSources.push({ source, reason: 'download_failed' });
24375
+ }
24073
24376
  }
24377
+ else {
24378
+ skippedSources.push({ source, reason: 'unsupported_source_type' });
24379
+ if (this.options.isVerbose) {
24380
+ console.info('[🤰]', 'Skipping knowledge source (unsupported type)', {
24381
+ source,
24382
+ sourceType,
24383
+ logLabel,
24384
+ });
24385
+ }
24386
+ /*
24387
+ TODO: [?????] Resolve problem with browser environment
24388
+ // Assume it's a local file path
24389
+ // Note: This will work in Node.js environment
24390
+ // For browser environments, this would need different handling
24391
+ const fs = await import('fs');
24392
+ const fileStream = fs.createReadStream(source);
24393
+ fileStreams.push(fileStream);
24394
+ */
24395
+ }
24396
+ }
24397
+ catch (error) {
24398
+ assertsError(error);
24399
+ skippedSources.push({ source, reason: 'processing_error' });
24400
+ console.error('[🤰]', 'Error processing knowledge source', {
24401
+ source,
24402
+ logLabel,
24403
+ error: serializeError(error),
24404
+ });
24074
24405
  }
24075
24406
  }
24407
+ if (this.options.isVerbose) {
24408
+ console.info('[🤰]', 'Finished processing knowledge sources', {
24409
+ total: knowledgeSourcesCount,
24410
+ downloadedCount: fileStreams.length,
24411
+ skippedCount: skippedSources.length,
24412
+ totalBytes,
24413
+ elapsedMs: Date.now() - processingStartedAtMs,
24414
+ skippedSamples: skippedSources.slice(0, 3),
24415
+ logLabel,
24416
+ });
24417
+ }
24418
+ if (fileStreams.length > 0) {
24419
+ if (this.options.isVerbose) {
24420
+ console.info('[🤰]', 'Uploading files to vector store', {
24421
+ vectorStoreId,
24422
+ fileCount: fileStreams.length,
24423
+ totalBytes,
24424
+ maxConcurrency: this.getKnowledgeSourceUploadMaxConcurrency(),
24425
+ pollIntervalMs: this.getKnowledgeSourceUploadPollIntervalMs(),
24426
+ uploadTimeoutMs: this.getKnowledgeSourceUploadTimeoutMs(),
24427
+ logLabel,
24428
+ });
24429
+ }
24430
+ try {
24431
+ await this.uploadKnowledgeSourceFilesToVectorStore({
24432
+ client,
24433
+ vectorStoreId,
24434
+ files: fileStreams,
24435
+ totalBytes,
24436
+ logLabel,
24437
+ });
24438
+ }
24439
+ catch (error) {
24440
+ assertsError(error);
24441
+ console.error('[🤰]', 'Error uploading files to vector store', {
24442
+ vectorStoreId,
24443
+ logLabel,
24444
+ error: serializeError(error),
24445
+ });
24446
+ }
24447
+ }
24448
+ else if (this.options.isVerbose) {
24449
+ console.info('[🤰]', 'No knowledge source files to upload', {
24450
+ vectorStoreId,
24451
+ skippedCount: skippedSources.length,
24452
+ logLabel,
24453
+ });
24454
+ }
24455
+ return {
24456
+ vectorStoreId,
24457
+ uploadedFileCount: fileStreams.length,
24458
+ skippedCount: skippedSources.length,
24459
+ totalBytes,
24460
+ };
24461
+ }
24462
+ async createNewAssistant(options) {
24463
+ var _a, _b, _c;
24464
+ if (!this.isCreatingNewAssistantsAllowed) {
24465
+ throw new NotAllowed(`Creating new assistants is not allowed. Set \`isCreatingNewAssistantsAllowed: true\` in options to enable this feature.`);
24466
+ }
24467
+ // await this.playground();
24468
+ const { name, instructions, knowledgeSources, tools } = options;
24469
+ const preparationStartedAtMs = Date.now();
24470
+ const knowledgeSourcesCount = (_a = knowledgeSources === null || knowledgeSources === void 0 ? void 0 : knowledgeSources.length) !== null && _a !== void 0 ? _a : 0;
24471
+ const toolsCount = (_b = tools === null || tools === void 0 ? void 0 : tools.length) !== null && _b !== void 0 ? _b : 0;
24472
+ if (this.options.isVerbose) {
24473
+ console.info('[🤰]', 'Starting OpenAI assistant creation', {
24474
+ name,
24475
+ knowledgeSourcesCount,
24476
+ toolsCount,
24477
+ instructionsLength: instructions.length,
24478
+ });
24479
+ }
24480
+ const client = await this.getClient();
24481
+ let vectorStoreId;
24482
+ // If knowledge sources are provided, create a vector store with them
24483
+ if (knowledgeSources && knowledgeSources.length > 0) {
24484
+ const vectorStoreResult = await this.createVectorStoreWithKnowledgeSources({
24485
+ client,
24486
+ name,
24487
+ knowledgeSources,
24488
+ logLabel: 'assistant creation',
24489
+ });
24490
+ vectorStoreId = vectorStoreResult.vectorStoreId;
24491
+ }
24076
24492
  // Create assistant with vector store attached
24077
24493
  const assistantConfig = {
24078
24494
  name,
@@ -24138,91 +24554,14 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
24138
24554
  const client = await this.getClient();
24139
24555
  let vectorStoreId;
24140
24556
  // If knowledge sources are provided, create a vector store with them
24141
- // TODO: [🧠] Reuse vector store creation logic from createNewAssistant
24142
24557
  if (knowledgeSources && knowledgeSources.length > 0) {
24143
- if (this.options.isVerbose) {
24144
- console.info('[🤰]', 'Creating vector store for assistant update', {
24145
- assistantId,
24146
- name,
24147
- knowledgeSourcesCount,
24148
- });
24149
- }
24150
- // Create a vector store
24151
- const vectorStore = await client.beta.vectorStores.create({
24152
- name: `${name} Knowledge Base`,
24558
+ const vectorStoreResult = await this.createVectorStoreWithKnowledgeSources({
24559
+ client,
24560
+ name: name !== null && name !== void 0 ? name : assistantId,
24561
+ knowledgeSources,
24562
+ logLabel: 'assistant update',
24153
24563
  });
24154
- vectorStoreId = vectorStore.id;
24155
- if (this.options.isVerbose) {
24156
- console.info('[🤰]', 'Vector store created for assistant update', {
24157
- vectorStoreId,
24158
- });
24159
- }
24160
- // Upload files from knowledge sources to the vector store
24161
- const fileStreams = [];
24162
- for (const [index, source] of knowledgeSources.entries()) {
24163
- try {
24164
- if (this.options.isVerbose) {
24165
- console.info('[🤰]', 'Processing knowledge source for update', {
24166
- index: index + 1,
24167
- total: knowledgeSources.length,
24168
- source,
24169
- sourceType: source.startsWith('http') || source.startsWith('https') ? 'url' : 'file',
24170
- });
24171
- }
24172
- // Check if it's a URL
24173
- if (source.startsWith('http://') || source.startsWith('https://')) {
24174
- // Download the file
24175
- const response = await fetch(source);
24176
- if (!response.ok) {
24177
- console.error(`Failed to download ${source}: ${response.statusText}`);
24178
- continue;
24179
- }
24180
- const buffer = await response.arrayBuffer();
24181
- let filename = source.split('/').pop() || 'downloaded-file';
24182
- try {
24183
- const url = new URL(source);
24184
- filename = url.pathname.split('/').pop() || filename;
24185
- }
24186
- catch (error) {
24187
- // Keep default filename
24188
- }
24189
- const blob = new Blob([buffer]);
24190
- const file = new File([blob], filename);
24191
- fileStreams.push(file);
24192
- }
24193
- else {
24194
- /*
24195
- TODO: [🐱‍🚀] Resolve problem with browser environment
24196
- // Assume it's a local file path
24197
- // Note: This will work in Node.js environment
24198
- // For browser environments, this would need different handling
24199
- const fs = await import('fs');
24200
- const fileStream = fs.createReadStream(source);
24201
- fileStreams.push(fileStream);
24202
- */
24203
- }
24204
- }
24205
- catch (error) {
24206
- console.error(`Error processing knowledge source ${source}:`, error);
24207
- }
24208
- }
24209
- // Batch upload files to the vector store
24210
- if (fileStreams.length > 0) {
24211
- try {
24212
- await client.beta.vectorStores.fileBatches.uploadAndPoll(vectorStoreId, {
24213
- files: fileStreams,
24214
- });
24215
- if (this.options.isVerbose) {
24216
- console.info('[🤰]', 'Uploaded files to vector store for update', {
24217
- vectorStoreId,
24218
- fileCount: fileStreams.length,
24219
- });
24220
- }
24221
- }
24222
- catch (error) {
24223
- console.error('Error uploading files to vector store:', error);
24224
- }
24225
- }
24564
+ vectorStoreId = vectorStoreResult.vectorStoreId;
24226
24565
  }
24227
24566
  const assistantUpdate = {
24228
24567
  name,
@@ -24326,7 +24665,6 @@ function emitAssistantPreparationProgress(options) {
24326
24665
  * - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
24327
24666
  * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
24328
24667
  * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
24329
- * - `OpenAiAgentExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with agent capabilities (using Responses API), recommended for usage in `Agent` or `AgentLlmExecutionTools`
24330
24668
  * - `OpenAiAssistantExecutionTools` - (Deprecated) which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities
24331
24669
  * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
24332
24670
  *
@@ -24489,70 +24827,25 @@ class AgentLlmExecutionTools {
24489
24827
  }, // Cast to avoid readonly mismatch from spread
24490
24828
  };
24491
24829
  console.log('!!!! promptWithAgentModelRequirements:', promptWithAgentModelRequirements);
24492
- if (OpenAiAgentExecutionTools.isOpenAiAgentExecutionTools(this.options.llmTools)) {
24830
+ if (OpenAiAssistantExecutionTools.isOpenAiAssistantExecutionTools(this.options.llmTools)) {
24831
+ // ... deprecated path ...
24493
24832
  const requirementsHash = SHA256(JSON.stringify(modelRequirements)).toString();
24494
- const cached = AgentLlmExecutionTools.vectorStoreCache.get(this.title);
24495
- let agentTools;
24496
- if (cached && cached.requirementsHash === requirementsHash) {
24497
- if (this.options.isVerbose) {
24498
- console.log(`1️⃣ Using cached OpenAI Agent Vector Store for agent ${this.title}...`);
24499
- }
24500
- // Create new instance with cached vectorStoreId
24501
- // We need to access options from the original tool.
24502
- // We assume isOpenAiAgentExecutionTools implies it has options we can clone.
24503
- // But protected options are not accessible.
24504
- // We can cast to access options if they were public, or use a method to clone.
24505
- // OpenAiAgentExecutionTools doesn't have a clone method.
24506
- // However, we can just assume the passed tool *might* not have the vector store yet, or we are replacing it.
24507
- // Actually, if the passed tool IS OpenAiAgentExecutionTools, we should use it as a base.
24508
- // TODO: [🧠] This is a bit hacky, accessing protected options or recreating tools.
24509
- // Ideally OpenAiAgentExecutionTools should have a method `withVectorStoreId`.
24510
- agentTools = new OpenAiAgentExecutionTools({
24511
- ...this.options.llmTools.options,
24512
- vectorStoreId: cached.vectorStoreId,
24513
- });
24514
- }
24515
- else {
24833
+ const cached = AgentLlmExecutionTools.assistantCache.get(this.title);
24834
+ let assistant;
24835
+ if (this.options.assistantPreparationMode === 'external') {
24836
+ assistant = this.options.llmTools;
24516
24837
  if (this.options.isVerbose) {
24517
- console.log(`1️⃣ Creating/Updating OpenAI Agent Vector Store for agent ${this.title}...`);
24518
- }
24519
- let vectorStoreId;
24520
- if (modelRequirements.knowledgeSources && modelRequirements.knowledgeSources.length > 0) {
24521
- const client = await this.options.llmTools.getClient();
24522
- vectorStoreId = await OpenAiAgentExecutionTools.createVectorStore(client, this.title, modelRequirements.knowledgeSources);
24523
- }
24524
- if (vectorStoreId) {
24525
- AgentLlmExecutionTools.vectorStoreCache.set(this.title, {
24526
- vectorStoreId,
24527
- requirementsHash,
24838
+ console.info('[🤰]', 'Using externally managed OpenAI Assistant', {
24839
+ agent: this.title,
24840
+ assistantId: assistant.assistantId,
24528
24841
  });
24529
24842
  }
24530
- agentTools = new OpenAiAgentExecutionTools({
24531
- ...this.options.llmTools.options,
24532
- vectorStoreId,
24843
+ AgentLlmExecutionTools.assistantCache.set(this.title, {
24844
+ assistantId: assistant.assistantId,
24845
+ requirementsHash,
24533
24846
  });
24534
24847
  }
24535
- // Create modified chat prompt with agent system message specific to OpenAI Agent
24536
- // Note: Unlike Assistants API, Responses API expects instructions (system message) to be passed in the call.
24537
- // So we use promptWithAgentModelRequirements which has the system message prepended.
24538
- // But we need to make sure we pass knowledgeSources in modelRequirements so OpenAiAgentExecutionTools can fallback to warning if vectorStoreId is missing (though we just handled it).
24539
- const promptForAgent = {
24540
- ...promptWithAgentModelRequirements,
24541
- modelRequirements: {
24542
- ...promptWithAgentModelRequirements.modelRequirements,
24543
- knowledgeSources: modelRequirements.knowledgeSources
24544
- ? [...modelRequirements.knowledgeSources]
24545
- : undefined, // Pass knowledge sources explicitly
24546
- },
24547
- };
24548
- underlyingLlmResult = await agentTools.callChatModelStream(promptForAgent, onProgress);
24549
- }
24550
- else if (OpenAiAssistantExecutionTools.isOpenAiAssistantExecutionTools(this.options.llmTools)) {
24551
- // ... deprecated path ...
24552
- const requirementsHash = SHA256(JSON.stringify(modelRequirements)).toString();
24553
- const cached = AgentLlmExecutionTools.assistantCache.get(this.title);
24554
- let assistant;
24555
- if (cached) {
24848
+ else if (cached) {
24556
24849
  if (cached.requirementsHash === requirementsHash) {
24557
24850
  if (this.options.isVerbose) {
24558
24851
  console.info('[🤰]', 'Using cached OpenAI Assistant', {
@@ -24618,11 +24911,6 @@ class AgentLlmExecutionTools {
24618
24911
  requirementsHash,
24619
24912
  });
24620
24913
  }
24621
- // [0] Expose prepared externals
24622
- if (this.preparedExternals) {
24623
- this /* <- TODO: !!!!!! Remove */.preparedExternals.openaiAssistantId =
24624
- assistant.assistantId;
24625
- }
24626
24914
  // Create modified chat prompt with agent system message specific to OpenAI Assistant
24627
24915
  const promptWithAgentModelRequirementsForOpenAiAssistantExecutionTools = {
24628
24916
  ...promptWithAgentModelRequirements,
@@ -24744,7 +25032,6 @@ function buildTeacherSummary(commitments, used) {
24744
25032
  * - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
24745
25033
  * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
24746
25034
  * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
24747
- * - `OpenAiAgentExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with agent capabilities (using Responses API), recommended for usage in `Agent` or `AgentLlmExecutionTools`
24748
25035
  * - `OpenAiAssistantExecutionTools` - (Deprecated) which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities
24749
25036
  * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
24750
25037
  *
@@ -24776,6 +25063,7 @@ class Agent extends AgentLlmExecutionTools {
24776
25063
  super({
24777
25064
  isVerbose: options.isVerbose,
24778
25065
  llmTools: getSingleLlmExecutionTools(options.executionTools.llm),
25066
+ assistantPreparationMode: options.assistantPreparationMode,
24779
25067
  agentSource: agentSource.value, // <- TODO: [🐱‍🚀] Allow to pass BehaviorSubject<string_book> OR refresh llmExecutionTools.callChat on agentSource change
24780
25068
  });
24781
25069
  _Agent_instances.add(this);
@@ -24815,10 +25103,6 @@ class Agent extends AgentLlmExecutionTools {
24815
25103
  * Human-readable titles for tool functions
24816
25104
  */
24817
25105
  this.toolTitles = {};
24818
- /**
24819
- * Externals prepared for the agent, like OpenAI assistant, etc.
24820
- */
24821
- this.preparedExternals = {};
24822
25106
  // TODO: [🐱‍🚀] Add `Agent` simple "mocked" learning by appending to agent source
24823
25107
  // TODO: [🐱‍🚀] Add `Agent` learning by promptbookAgent
24824
25108
  this.teacherAgent = options.teacherAgent;
@@ -25055,6 +25339,63 @@ async function _Agent_selfLearnTeacher(prompt, result) {
25055
25339
  * TODO: [🧠][😰]Agent is not working with the parameters, should it be?
25056
25340
  */
25057
25341
 
25342
+ /**
25343
+ * Resolve a remote META IMAGE value into an absolute URL when possible.
25344
+ */
25345
+ function resolveRemoteImageUrl(imageUrl, agentUrl) {
25346
+ if (!imageUrl) {
25347
+ return undefined;
25348
+ }
25349
+ if (imageUrl.startsWith('http://') ||
25350
+ imageUrl.startsWith('https://') ||
25351
+ imageUrl.startsWith('data:') ||
25352
+ imageUrl.startsWith('blob:')) {
25353
+ return imageUrl;
25354
+ }
25355
+ try {
25356
+ return new URL(imageUrl, agentUrl).href;
25357
+ }
25358
+ catch (_a) {
25359
+ return imageUrl;
25360
+ }
25361
+ }
25362
+ /**
25363
+ * Format a META commitment line when the value is provided.
25364
+ */
25365
+ function formatMetaLine(label, value) {
25366
+ if (!value) {
25367
+ return null;
25368
+ }
25369
+ return `META ${label} ${value}`;
25370
+ }
25371
+ /**
25372
+ * Build a minimal agent source snapshot for remote agents.
25373
+ */
25374
+ function buildRemoteAgentSource(profile, meta) {
25375
+ const metaLines = [
25376
+ formatMetaLine('FULLNAME', meta === null || meta === void 0 ? void 0 : meta.fullname),
25377
+ formatMetaLine('IMAGE', meta === null || meta === void 0 ? void 0 : meta.image),
25378
+ formatMetaLine('DESCRIPTION', meta === null || meta === void 0 ? void 0 : meta.description),
25379
+ formatMetaLine('COLOR', meta === null || meta === void 0 ? void 0 : meta.color),
25380
+ formatMetaLine('FONT', meta === null || meta === void 0 ? void 0 : meta.font),
25381
+ formatMetaLine('LINK', meta === null || meta === void 0 ? void 0 : meta.link),
25382
+ ]
25383
+ .filter((line) => Boolean(line))
25384
+ .join('\n');
25385
+ const personaBlock = profile.personaDescription
25386
+ ? spaceTrim$2((block) => `
25387
+ PERSONA
25388
+ ${block(profile.personaDescription || '')}
25389
+ `)
25390
+ : '';
25391
+ return book `
25392
+ ${profile.agentName}
25393
+
25394
+ ${metaLines}
25395
+
25396
+ ${personaBlock}
25397
+ `;
25398
+ }
25058
25399
  /**
25059
25400
  * Represents one AI Agent
25060
25401
  *
@@ -25069,6 +25410,7 @@ async function _Agent_selfLearnTeacher(prompt, result) {
25069
25410
  */
25070
25411
  class RemoteAgent extends Agent {
25071
25412
  static async connect(options) {
25413
+ var _a, _b, _c;
25072
25414
  const agentProfileUrl = `${options.agentUrl}/api/profile`;
25073
25415
  const profileResponse = await fetch(agentProfileUrl);
25074
25416
  // <- TODO: [🐱‍🚀] What about closed-source agents?
@@ -25088,14 +25430,14 @@ class RemoteAgent extends Agent {
25088
25430
 
25089
25431
  `));
25090
25432
  }
25091
- const profile = await profileResponse.json();
25433
+ const profile = (await profileResponse.json());
25434
+ const resolvedMeta = {
25435
+ ...(profile.meta || {}),
25436
+ image: resolveRemoteImageUrl((_a = profile.meta) === null || _a === void 0 ? void 0 : _a.image, options.agentUrl),
25437
+ };
25092
25438
  // Note: We are creating dummy agent source because we don't have the source from the remote agent
25093
25439
  // But we populate the metadata from the profile
25094
- const agentSource = new BehaviorSubject(book `
25095
- ${profile.agentName}
25096
-
25097
- ${profile.personaDescription}
25098
- `);
25440
+ const agentSource = new BehaviorSubject(buildRemoteAgentSource(profile, resolvedMeta));
25099
25441
  // <- TODO: [🐱‍🚀] createBookFromProfile
25100
25442
  // <- TODO: [🐱‍🚀] Support updating and self-updating
25101
25443
  const remoteAgent = new RemoteAgent({
@@ -25118,10 +25460,10 @@ class RemoteAgent extends Agent {
25118
25460
  });
25119
25461
  remoteAgent._remoteAgentName = profile.agentName;
25120
25462
  remoteAgent._remoteAgentHash = profile.agentHash;
25121
- remoteAgent.personaDescription = profile.personaDescription;
25122
- remoteAgent.initialMessage = profile.initialMessage;
25123
- remoteAgent.links = profile.links;
25124
- remoteAgent.meta = profile.meta;
25463
+ remoteAgent.personaDescription = (_b = profile.personaDescription) !== null && _b !== void 0 ? _b : null;
25464
+ remoteAgent.initialMessage = (_c = profile.initialMessage) !== null && _c !== void 0 ? _c : null;
25465
+ remoteAgent.links = profile.links || [];
25466
+ remoteAgent.meta = resolvedMeta;
25125
25467
  remoteAgent.capabilities = profile.capabilities || [];
25126
25468
  remoteAgent.samples = profile.samples || [];
25127
25469
  remoteAgent.toolTitles = profile.toolTitles || {};