@promptbook/node 0.110.0-5 → 0.110.0-8

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 (24) hide show
  1. package/esm/index.es.js +1480 -924
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/typings/src/_packages/openai.index.d.ts +8 -0
  4. package/esm/typings/src/_packages/types.index.d.ts +4 -0
  5. package/esm/typings/src/book-components/Chat/AgentChip/AgentChip.d.ts +5 -1
  6. package/esm/typings/src/book-components/Chat/LlmChat/LlmChatProps.d.ts +4 -1
  7. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +0 -3
  8. package/esm/typings/src/execution/LlmExecutionTools.d.ts +2 -1
  9. package/esm/typings/src/llm-providers/agent/Agent.d.ts +1 -0
  10. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +5 -0
  11. package/esm/typings/src/llm-providers/agent/AgentOptions.d.ts +4 -3
  12. package/esm/typings/src/llm-providers/agent/CreateAgentLlmExecutionToolsOptions.d.ts +7 -5
  13. package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +2 -1
  14. package/esm/typings/src/llm-providers/openai/OpenAiAgentKitExecutionTools.d.ts +111 -0
  15. package/esm/typings/src/llm-providers/openai/OpenAiAgentKitExecutionToolsOptions.d.ts +15 -0
  16. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +3 -42
  17. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionToolsOptions.d.ts +2 -33
  18. package/esm/typings/src/llm-providers/openai/OpenAiVectorStoreHandler.d.ts +135 -0
  19. package/esm/typings/src/llm-providers/openai/utils/mapToolsToOpenAi.d.ts +1 -1
  20. package/esm/typings/src/utils/toolCalls/getToolCallIdentity.d.ts +10 -0
  21. package/esm/typings/src/version.d.ts +1 -1
  22. package/package.json +7 -3
  23. package/umd/index.umd.js +1483 -928
  24. package/umd/index.umd.js.map +1 -1
package/umd/index.umd.js CHANGED
@@ -1,8 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('fs/promises'), require('path'), require('spacetrim'), require('jszip'), require('crypto'), require('crypto-js'), require('crypto-js/enc-hex'), require('@mozilla/readability'), require('jsdom'), require('showdown'), require('child_process'), require('waitasecond'), require('dotenv'), require('crypto-js/sha256'), require('rxjs'), require('moment'), require('mime-types'), require('papaparse'), require('bottleneck'), require('openai')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'colors', 'fs/promises', 'path', 'spacetrim', 'jszip', 'crypto', 'crypto-js', 'crypto-js/enc-hex', '@mozilla/readability', 'jsdom', 'showdown', 'child_process', 'waitasecond', 'dotenv', 'crypto-js/sha256', 'rxjs', 'moment', 'mime-types', 'papaparse', 'bottleneck', 'openai'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-node"] = {}, global.colors, global.promises, global.path, global.spaceTrim$1, global.JSZip, global.crypto, global.cryptoJs, global.hexEncoder, global.readability, global.jsdom, global.showdown, global.child_process, global.waitasecond, global.dotenv, global.sha256, global.rxjs, global.moment, global.mimeTypes, global.papaparse, global.Bottleneck, global.OpenAI));
5
- })(this, (function (exports, colors, promises, path, spaceTrim$1, JSZip, crypto, cryptoJs, hexEncoder, readability, jsdom, showdown, child_process, waitasecond, dotenv, sha256, rxjs, moment, mimeTypes, papaparse, Bottleneck, OpenAI) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('fs/promises'), require('path'), require('spacetrim'), require('jszip'), require('crypto'), require('crypto-js'), require('crypto-js/enc-hex'), require('@mozilla/readability'), require('jsdom'), require('showdown'), require('child_process'), require('waitasecond'), require('dotenv'), require('crypto-js/sha256'), require('rxjs'), require('moment'), require('mime-types'), require('papaparse'), require('@openai/agents'), require('bottleneck'), require('openai')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'colors', 'fs/promises', 'path', 'spacetrim', 'jszip', 'crypto', 'crypto-js', 'crypto-js/enc-hex', '@mozilla/readability', 'jsdom', 'showdown', 'child_process', 'waitasecond', 'dotenv', 'crypto-js/sha256', 'rxjs', 'moment', 'mime-types', 'papaparse', '@openai/agents', 'bottleneck', 'openai'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-node"] = {}, global.colors, global.promises, global.path, global.spaceTrim$1, global.JSZip, global.crypto, global.cryptoJs, global.hexEncoder, global.readability, global.jsdom, global.showdown, global.child_process, global.waitasecond, global.dotenv, global.sha256, global.rxjs, global.moment, global.mimeTypes, global.papaparse, global.agents, global.Bottleneck, global.OpenAI));
5
+ })(this, (function (exports, colors, promises, path, spaceTrim$1, JSZip, crypto, cryptoJs, hexEncoder, readability, jsdom, showdown, child_process, waitasecond, dotenv, sha256, rxjs, moment, mimeTypes, papaparse, agents, Bottleneck, OpenAI) { 'use strict';
6
6
 
7
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
8
 
@@ -48,7 +48,7 @@
48
48
  * @generated
49
49
  * @see https://github.com/webgptorg/promptbook
50
50
  */
51
- const PROMPTBOOK_ENGINE_VERSION = '0.110.0-5';
51
+ const PROMPTBOOK_ENGINE_VERSION = '0.110.0-8';
52
52
  /**
53
53
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
54
54
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -20323,6 +20323,40 @@
20323
20323
  return toolCall.name === ASSISTANT_PREPARATION_TOOL_CALL_NAME;
20324
20324
  }
20325
20325
 
20326
+ /**
20327
+ * Builds a stable identity string for tool calls across partial updates.
20328
+ *
20329
+ * @param toolCall - Tool call entry to identify.
20330
+ * @returns Stable identity string for deduplication.
20331
+ *
20332
+ * @private function of <Chat/>
20333
+ */
20334
+ function getToolCallIdentity(toolCall) {
20335
+ const rawToolCall = toolCall.rawToolCall;
20336
+ const rawId = (rawToolCall === null || rawToolCall === void 0 ? void 0 : rawToolCall.id) || (rawToolCall === null || rawToolCall === void 0 ? void 0 : rawToolCall.callId) || (rawToolCall === null || rawToolCall === void 0 ? void 0 : rawToolCall.call_id);
20337
+ if (rawId) {
20338
+ return `id:${rawId}`;
20339
+ }
20340
+ if (toolCall.createdAt) {
20341
+ return `time:${toolCall.createdAt}:${toolCall.name}`;
20342
+ }
20343
+ const argsKey = (() => {
20344
+ if (typeof toolCall.arguments === 'string') {
20345
+ return toolCall.arguments;
20346
+ }
20347
+ if (!toolCall.arguments) {
20348
+ return '';
20349
+ }
20350
+ try {
20351
+ return JSON.stringify(toolCall.arguments);
20352
+ }
20353
+ catch (_a) {
20354
+ return '';
20355
+ }
20356
+ })();
20357
+ return `fallback:${toolCall.name}:${argsKey}`;
20358
+ }
20359
+
20326
20360
  /*! *****************************************************************************
20327
20361
  Copyright (c) Microsoft Corporation.
20328
20362
 
@@ -22372,16 +22406,11 @@
22372
22406
  const openAiOptions = { ...this.options };
22373
22407
  delete openAiOptions.isVerbose;
22374
22408
  delete openAiOptions.userId;
22375
- // Enhanced configuration for better ECONNRESET handling
22409
+ // Enhanced configuration with retries and timeouts.
22376
22410
  const enhancedOptions = {
22377
22411
  ...openAiOptions,
22378
22412
  timeout: API_REQUEST_TIMEOUT,
22379
22413
  maxRetries: CONNECTION_RETRIES_LIMIT,
22380
- defaultHeaders: {
22381
- Connection: 'keep-alive',
22382
- 'Keep-Alive': 'timeout=30, max=100',
22383
- ...openAiOptions.defaultHeaders,
22384
- },
22385
22414
  };
22386
22415
  this.client = new OpenAI__default["default"](enhancedOptions);
22387
22416
  }
@@ -23304,604 +23333,151 @@
23304
23333
  }
23305
23334
  }
23306
23335
 
23307
- /**
23308
- * Uploads files to OpenAI and returns their IDs
23309
- *
23310
- * @private utility for `OpenAiAssistantExecutionTools` and `OpenAiCompatibleExecutionTools`
23311
- */
23312
- async function uploadFilesToOpenAi(client, files) {
23313
- const fileIds = [];
23314
- for (const file of files) {
23315
- // Note: OpenAI API expects a File object or a ReadStream
23316
- // In browser environment, we can pass the File object directly
23317
- // In Node.js environment, we might need to convert it or use a different approach
23318
- // But since `Prompt.files` already contains `File` objects, we try to pass them directly
23319
- const uploadedFile = await client.files.create({
23320
- file: file,
23321
- purpose: 'assistants',
23322
- });
23323
- fileIds.push(uploadedFile.id);
23324
- }
23325
- return fileIds;
23326
- }
23327
-
23328
23336
  const DEFAULT_KNOWLEDGE_SOURCE_DOWNLOAD_TIMEOUT_MS = 30000;
23329
23337
  const DEFAULT_KNOWLEDGE_SOURCE_UPLOAD_TIMEOUT_MS = 900000;
23330
23338
  const VECTOR_STORE_PROGRESS_LOG_INTERVAL_MIN_MS = 15000;
23331
23339
  const VECTOR_STORE_STALL_LOG_THRESHOLD_MS = 30000;
23332
23340
  /**
23333
- * Execution Tools for calling OpenAI API Assistants
23334
- *
23335
- * This is useful for calling OpenAI API with a single assistant, for more wide usage use `OpenAiExecutionTools`.
23336
- *
23337
- * Note: [🦖] There are several different things in Promptbook:
23338
- * - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
23339
- * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
23340
- * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
23341
- * - `OpenAiAssistantExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities, recommended for usage in `Agent` or `AgentLlmExecutionTools`
23342
- * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
23341
+ * Base class for OpenAI execution tools that need hosted vector stores.
23343
23342
  *
23344
23343
  * @public exported from `@promptbook/openai`
23345
23344
  */
23346
- class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
23345
+ class OpenAiVectorStoreHandler extends OpenAiExecutionTools {
23347
23346
  /**
23348
- * Creates OpenAI Execution Tools.
23349
- *
23350
- * @param options which are relevant are directly passed to the OpenAI client
23347
+ * Returns the per-knowledge-source download timeout in milliseconds.
23351
23348
  */
23352
- constructor(options) {
23349
+ getKnowledgeSourceDownloadTimeoutMs() {
23353
23350
  var _a;
23354
- if (options.isProxied) {
23355
- throw new NotYetImplementedError(`Proxy mode is not yet implemented for OpenAI assistants`);
23356
- }
23357
- super(options);
23358
- this.isCreatingNewAssistantsAllowed = false;
23359
- this.assistantId = options.assistantId;
23360
- this.isCreatingNewAssistantsAllowed = (_a = options.isCreatingNewAssistantsAllowed) !== null && _a !== void 0 ? _a : false;
23361
- if (this.assistantId === null && !this.isCreatingNewAssistantsAllowed) {
23362
- throw new NotAllowed(`Assistant ID is null and creating new assistants is not allowed - this configuration does not make sense`);
23363
- }
23364
- // <- TODO: !!! `OpenAiAssistantExecutionToolsOptions` - Allow `assistantId: null` together with `isCreatingNewAssistantsAllowed: true`
23365
- // TODO: [👱] Make limiter same as in `OpenAiExecutionTools`
23351
+ return (_a = this.vectorStoreOptions.knowledgeSourceDownloadTimeoutMs) !== null && _a !== void 0 ? _a : DEFAULT_KNOWLEDGE_SOURCE_DOWNLOAD_TIMEOUT_MS;
23366
23352
  }
23367
- get title() {
23368
- return 'OpenAI Assistant';
23353
+ /**
23354
+ * Returns the max concurrency for knowledge source uploads.
23355
+ */
23356
+ getKnowledgeSourceUploadMaxConcurrency() {
23357
+ var _a;
23358
+ return (_a = this.vectorStoreOptions.knowledgeSourceUploadMaxConcurrency) !== null && _a !== void 0 ? _a : 5;
23369
23359
  }
23370
- get description() {
23371
- return 'Use single assistant provided by OpenAI';
23360
+ /**
23361
+ * Returns the polling interval in milliseconds for vector store uploads.
23362
+ */
23363
+ getKnowledgeSourceUploadPollIntervalMs() {
23364
+ var _a;
23365
+ return (_a = this.vectorStoreOptions.knowledgeSourceUploadPollIntervalMs) !== null && _a !== void 0 ? _a : 5000;
23372
23366
  }
23373
23367
  /**
23374
- * Calls OpenAI API to use a chat model.
23368
+ * Returns the overall upload timeout in milliseconds for vector store uploads.
23375
23369
  */
23376
- async callChatModel(prompt) {
23377
- return this.callChatModelStream(prompt, () => { });
23370
+ getKnowledgeSourceUploadTimeoutMs() {
23371
+ var _a;
23372
+ return (_a = this.vectorStoreOptions.knowledgeSourceUploadTimeoutMs) !== null && _a !== void 0 ? _a : DEFAULT_KNOWLEDGE_SOURCE_UPLOAD_TIMEOUT_MS;
23378
23373
  }
23379
23374
  /**
23380
- * Calls OpenAI API to use a chat model with streaming.
23375
+ * Returns true if we should continue even if vector store ingestion stalls.
23381
23376
  */
23382
- async callChatModelStream(prompt, onProgress) {
23383
- var _a, _b, _c, _d, _e, _f;
23384
- if (this.options.isVerbose) {
23385
- console.info('💬 OpenAI callChatModel call', { prompt });
23377
+ shouldContinueOnVectorStoreStall() {
23378
+ var _a;
23379
+ return (_a = this.vectorStoreOptions.shouldContinueOnVectorStoreStall) !== null && _a !== void 0 ? _a : true;
23380
+ }
23381
+ /**
23382
+ * Returns vector-store-specific options with extended settings.
23383
+ */
23384
+ get vectorStoreOptions() {
23385
+ return this.options;
23386
+ }
23387
+ /**
23388
+ * Returns the OpenAI vector stores API surface, supporting stable and beta SDKs.
23389
+ */
23390
+ getVectorStoresApi(client) {
23391
+ var _a, _b;
23392
+ const vectorStores = (_a = client.vectorStores) !== null && _a !== void 0 ? _a : (_b = client.beta) === null || _b === void 0 ? void 0 : _b.vectorStores;
23393
+ if (!vectorStores) {
23394
+ throw new Error('OpenAI client does not support vector stores. Please ensure you are using a compatible version of the OpenAI SDK with vector store support.');
23386
23395
  }
23387
- const { content, parameters, modelRequirements /*, format*/ } = prompt;
23388
- const client = await this.getClient();
23389
- // TODO: [☂] Use here more modelRequirements
23390
- if (modelRequirements.modelVariant !== 'CHAT') {
23391
- throw new PipelineExecutionError('Use callChatModel only for CHAT variant');
23396
+ return vectorStores;
23397
+ }
23398
+ /**
23399
+ * Downloads a knowledge source URL into a File for vector store upload.
23400
+ */
23401
+ async downloadKnowledgeSourceFile(options) {
23402
+ var _a;
23403
+ const { source, timeoutMs, logLabel } = options;
23404
+ const startedAtMs = Date.now();
23405
+ const controller = new AbortController();
23406
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
23407
+ if (this.options.isVerbose) {
23408
+ console.info('[🤰]', 'Downloading knowledge source', {
23409
+ source,
23410
+ timeoutMs,
23411
+ logLabel,
23412
+ });
23392
23413
  }
23393
- // TODO: [👨‍👨‍👧‍👧] Remove:
23394
- for (const key of ['maxTokens', 'modelName', 'seed', 'temperature']) {
23395
- if (modelRequirements[key] !== undefined) {
23396
- throw new NotYetImplementedError(`In \`OpenAiAssistantExecutionTools\` you cannot specify \`${key}\``);
23414
+ try {
23415
+ const response = await fetch(source, { signal: controller.signal });
23416
+ const contentType = (_a = response.headers.get('content-type')) !== null && _a !== void 0 ? _a : undefined;
23417
+ if (!response.ok) {
23418
+ console.error('[🤰]', 'Failed to download knowledge source', {
23419
+ source,
23420
+ status: response.status,
23421
+ statusText: response.statusText,
23422
+ contentType,
23423
+ elapsedMs: Date.now() - startedAtMs,
23424
+ logLabel,
23425
+ });
23426
+ return null;
23427
+ }
23428
+ const buffer = await response.arrayBuffer();
23429
+ let filename = source.split('/').pop() || 'downloaded-file';
23430
+ try {
23431
+ const url = new URL(source);
23432
+ filename = url.pathname.split('/').pop() || filename;
23433
+ }
23434
+ catch (error) {
23435
+ // Keep default filename
23436
+ }
23437
+ const file = new File([buffer], filename, contentType ? { type: contentType } : undefined);
23438
+ const elapsedMs = Date.now() - startedAtMs;
23439
+ const sizeBytes = buffer.byteLength;
23440
+ if (this.options.isVerbose) {
23441
+ console.info('[🤰]', 'Downloaded knowledge source', {
23442
+ source,
23443
+ filename,
23444
+ sizeBytes,
23445
+ contentType,
23446
+ elapsedMs,
23447
+ logLabel,
23448
+ });
23397
23449
  }
23450
+ return { file, sizeBytes, filename, elapsedMs };
23398
23451
  }
23399
- /*
23400
- TODO: [👨‍👨‍👧‍👧] Implement all of this for Assistants
23401
- const modelName = modelRequirements.modelName || this.getDefaultChatModel().modelName;
23402
- const modelSettings = {
23403
- model: modelName,
23404
-
23405
- temperature: modelRequirements.temperature,
23406
-
23407
- // <- TODO: [🈁] Use `seed` here AND/OR use is `isDeterministic` for entire execution tools
23408
- // <- Note: [🧆]
23409
- } as OpenAI.Chat.Completions.CompletionCreateParamsNonStreaming; // <- TODO: Guard here types better
23410
-
23411
- if (format === 'JSON') {
23412
- modelSettings.response_format = {
23413
- type: 'json_object',
23414
- };
23452
+ catch (error) {
23453
+ assertsError(error);
23454
+ console.error('[🤰]', 'Error downloading knowledge source', {
23455
+ source,
23456
+ elapsedMs: Date.now() - startedAtMs,
23457
+ logLabel,
23458
+ error: serializeError(error),
23459
+ });
23460
+ return null;
23415
23461
  }
23416
- */
23417
- // <- TODO: [🚸] Not all models are compatible with JSON mode
23418
- // > 'response_format' of type 'json_object' is not supported with this model.
23419
- const rawPromptContent = templateParameters(content, {
23420
- ...parameters,
23421
- modelName: 'assistant',
23422
- // <- [🧠] What is the best value here
23423
- });
23424
- // Build thread messages: include previous thread messages + current user message
23425
- const threadMessages = [];
23426
- // TODO: [🈹] Maybe this should not be here but in other place, look at commit 39d705e75e5bcf7a818c3af36bc13e1c8475c30c
23427
- // Add previous messages from thread (if any)
23428
- if ('thread' in prompt && Array.isArray(prompt.thread)) {
23429
- const previousMessages = prompt.thread.map((msg) => ({
23430
- role: (msg.sender === 'assistant' ? 'assistant' : 'user'),
23431
- content: msg.content,
23432
- }));
23433
- threadMessages.push(...previousMessages);
23462
+ finally {
23463
+ clearTimeout(timeoutId);
23434
23464
  }
23435
- // Always add the current user message
23436
- const currentUserMessage = {
23437
- role: 'user',
23438
- content: rawPromptContent,
23439
- };
23440
- if ('files' in prompt && Array.isArray(prompt.files) && prompt.files.length > 0) {
23441
- const fileIds = await uploadFilesToOpenAi(client, prompt.files);
23442
- currentUserMessage.attachments = fileIds.map((fileId) => ({
23443
- file_id: fileId,
23444
- tools: [{ type: 'file_search' }, { type: 'code_interpreter' }],
23445
- }));
23465
+ }
23466
+ /**
23467
+ * Logs vector store file batch diagnostics to help trace ingestion stalls or failures.
23468
+ */
23469
+ async logVectorStoreFileBatchDiagnostics(options) {
23470
+ var _a, _b, _c, _d, _e;
23471
+ const { client, vectorStoreId, batchId, uploadedFiles, logLabel, reason } = options;
23472
+ if (reason === 'stalled' && !this.options.isVerbose) {
23473
+ return;
23446
23474
  }
23447
- threadMessages.push(currentUserMessage);
23448
- // Check if tools are being used - if so, use non-streaming mode
23449
- const hasTools = modelRequirements.tools !== undefined && modelRequirements.tools.length > 0;
23450
- const start = $getCurrentDate();
23451
- let complete;
23452
- // [🐱‍🚀] When tools are present, we need to use the non-streaming Runs API
23453
- // because streaming doesn't support tool execution flow properly
23454
- if (hasTools) {
23455
- onProgress({
23456
- content: '',
23457
- modelName: 'assistant',
23458
- timing: { start, complete: $getCurrentDate() },
23459
- usage: UNCERTAIN_USAGE,
23460
- rawPromptContent,
23461
- rawRequest: null,
23462
- rawResponse: null,
23463
- });
23464
- const rawRequest = {
23465
- assistant_id: this.assistantId,
23466
- thread: {
23467
- messages: threadMessages,
23468
- },
23469
- tools: mapToolsToOpenAi(modelRequirements.tools),
23470
- };
23471
- if (this.options.isVerbose) {
23472
- console.info(colors__default["default"].bgWhite('rawRequest (non-streaming with tools)'), JSON.stringify(rawRequest, null, 4));
23473
- }
23474
- // Create thread and run
23475
- const threadAndRun = await client.beta.threads.createAndRun(rawRequest);
23476
- let run = threadAndRun;
23477
- const completedToolCalls = [];
23478
- const toolCallStartedAt = new Map();
23479
- // Poll until run completes or requires action
23480
- while (run.status === 'queued' || run.status === 'in_progress' || run.status === 'requires_action') {
23481
- if (run.status === 'requires_action' && ((_a = run.required_action) === null || _a === void 0 ? void 0 : _a.type) === 'submit_tool_outputs') {
23482
- // Execute tools
23483
- const toolCalls = run.required_action.submit_tool_outputs.tool_calls;
23484
- const toolOutputs = [];
23485
- for (const toolCall of toolCalls) {
23486
- if (toolCall.type === 'function') {
23487
- const functionName = toolCall.function.name;
23488
- const functionArgs = JSON.parse(toolCall.function.arguments);
23489
- const calledAt = $getCurrentDate();
23490
- if (toolCall.id) {
23491
- toolCallStartedAt.set(toolCall.id, calledAt);
23492
- }
23493
- onProgress({
23494
- content: '',
23495
- modelName: 'assistant',
23496
- timing: { start, complete: $getCurrentDate() },
23497
- usage: UNCERTAIN_USAGE,
23498
- rawPromptContent,
23499
- rawRequest: null,
23500
- rawResponse: null,
23501
- toolCalls: [
23502
- {
23503
- name: functionName,
23504
- arguments: toolCall.function.arguments,
23505
- result: '',
23506
- rawToolCall: toolCall,
23507
- createdAt: calledAt,
23508
- },
23509
- ],
23510
- });
23511
- if (this.options.isVerbose) {
23512
- console.info(`🔧 Executing tool: ${functionName}`, functionArgs);
23513
- }
23514
- // Get execution tools for script execution
23515
- const executionTools = this.options
23516
- .executionTools;
23517
- if (!executionTools || !executionTools.script) {
23518
- throw new PipelineExecutionError(`Model requested tool '${functionName}' but no executionTools.script were provided in OpenAiAssistantExecutionTools options`);
23519
- }
23520
- // TODO: [DRY] Use some common tool caller (similar to OpenAiCompatibleExecutionTools)
23521
- const scriptTools = Array.isArray(executionTools.script)
23522
- ? executionTools.script
23523
- : [executionTools.script];
23524
- let functionResponse;
23525
- let errors;
23526
- try {
23527
- const scriptTool = scriptTools[0]; // <- TODO: [🧠] Which script tool to use?
23528
- functionResponse = await scriptTool.execute({
23529
- scriptLanguage: 'javascript',
23530
- script: `
23531
- const args = ${JSON.stringify(functionArgs)};
23532
- return await ${functionName}(args);
23533
- `,
23534
- parameters: prompt.parameters,
23535
- });
23536
- if (this.options.isVerbose) {
23537
- console.info(`✅ Tool ${functionName} executed:`, functionResponse);
23538
- }
23539
- }
23540
- catch (error) {
23541
- assertsError(error);
23542
- const serializedError = serializeError(error);
23543
- errors = [serializedError];
23544
- functionResponse = spaceTrim__default["default"]((block) => `
23545
-
23546
- The invoked tool \`${functionName}\` failed with error:
23547
-
23548
- \`\`\`json
23549
- ${block(JSON.stringify(serializedError, null, 4))}
23550
- \`\`\`
23551
-
23552
- `);
23553
- console.error(colors__default["default"].bgRed(`❌ Error executing tool ${functionName}:`));
23554
- console.error(error);
23555
- }
23556
- toolOutputs.push({
23557
- tool_call_id: toolCall.id,
23558
- output: functionResponse,
23559
- });
23560
- completedToolCalls.push({
23561
- name: functionName,
23562
- arguments: toolCall.function.arguments,
23563
- result: functionResponse,
23564
- rawToolCall: toolCall,
23565
- createdAt: toolCall.id ? toolCallStartedAt.get(toolCall.id) || calledAt : calledAt,
23566
- errors,
23567
- });
23568
- }
23569
- }
23570
- // Submit tool outputs
23571
- run = await client.beta.threads.runs.submitToolOutputs(run.thread_id, run.id, {
23572
- tool_outputs: toolOutputs,
23573
- });
23574
- }
23575
- else {
23576
- // Wait a bit before polling again
23577
- await new Promise((resolve) => setTimeout(resolve, 500));
23578
- run = await client.beta.threads.runs.retrieve(run.thread_id, run.id);
23579
- }
23580
- }
23581
- if (run.status !== 'completed') {
23582
- throw new PipelineExecutionError(`Assistant run failed with status: ${run.status}`);
23583
- }
23584
- // Get messages from the thread
23585
- const messages = await client.beta.threads.messages.list(run.thread_id);
23586
- const assistantMessages = messages.data.filter((msg) => msg.role === 'assistant');
23587
- if (assistantMessages.length === 0) {
23588
- throw new PipelineExecutionError('No assistant messages found after run completion');
23589
- }
23590
- const lastMessage = assistantMessages[0];
23591
- const textContent = lastMessage.content.find((c) => c.type === 'text');
23592
- if (!textContent || textContent.type !== 'text') {
23593
- throw new PipelineExecutionError('No text content in assistant response');
23594
- }
23595
- complete = $getCurrentDate();
23596
- const resultContent = textContent.text.value;
23597
- const usage = UNCERTAIN_USAGE;
23598
- // Progress callback with final result
23599
- const finalChunk = {
23600
- content: resultContent,
23601
- modelName: 'assistant',
23602
- timing: { start, complete },
23603
- usage,
23604
- rawPromptContent,
23605
- rawRequest,
23606
- rawResponse: { run, messages: messages.data },
23607
- toolCalls: completedToolCalls.length > 0 ? completedToolCalls : undefined,
23608
- };
23609
- onProgress(finalChunk);
23610
- return exportJson({
23611
- name: 'promptResult',
23612
- message: `Result of \`OpenAiAssistantExecutionTools.callChatModelStream\` (with tools)`,
23613
- order: [],
23614
- value: finalChunk,
23615
- });
23616
- }
23617
- // Streaming mode (without tools)
23618
- const rawRequest = {
23619
- // TODO: [👨‍👨‍👧‍👧] ...modelSettings,
23620
- // TODO: [👨‍👨‍👧‍👧][🧠] What about system message for assistants, does it make sense - combination of OpenAI assistants with Promptbook Personas
23621
- assistant_id: this.assistantId,
23622
- thread: {
23623
- messages: threadMessages,
23624
- },
23625
- tools: modelRequirements.tools === undefined ? undefined : mapToolsToOpenAi(modelRequirements.tools),
23626
- // <- TODO: Add user identification here> user: this.options.user,
23627
- };
23628
- if (this.options.isVerbose) {
23629
- console.info(colors__default["default"].bgWhite('rawRequest (streaming)'), JSON.stringify(rawRequest, null, 4));
23630
- }
23631
- const stream = await client.beta.threads.createAndRunStream(rawRequest);
23632
- stream.on('connect', () => {
23633
- if (this.options.isVerbose) {
23634
- console.info('connect', stream.currentEvent);
23635
- }
23636
- });
23637
- stream.on('textDelta', (textDelta, snapshot) => {
23638
- if (this.options.isVerbose && textDelta.value) {
23639
- console.info('textDelta', textDelta.value);
23640
- }
23641
- const chunk = {
23642
- content: snapshot.value,
23643
- modelName: 'assistant',
23644
- timing: {
23645
- start,
23646
- complete: $getCurrentDate(),
23647
- },
23648
- usage: UNCERTAIN_USAGE,
23649
- rawPromptContent,
23650
- rawRequest,
23651
- rawResponse: snapshot,
23652
- };
23653
- onProgress(chunk);
23654
- });
23655
- stream.on('messageCreated', (message) => {
23656
- if (this.options.isVerbose) {
23657
- console.info('messageCreated', message);
23658
- }
23659
- });
23660
- stream.on('messageDone', (message) => {
23661
- if (this.options.isVerbose) {
23662
- console.info('messageDone', message);
23663
- }
23664
- });
23665
- // TODO: [🐱‍🚀] Handle tool calls in assistants
23666
- // Note: OpenAI Assistant streaming with tool calls requires special handling.
23667
- // The stream will pause when a tool call is needed, and we need to:
23668
- // 1. Wait for the run to reach 'requires_action' status
23669
- // 2. Execute the tool calls
23670
- // 3. Submit tool outputs via a separate API call (not on the stream)
23671
- // 4. Continue the run
23672
- // This requires switching to non-streaming mode or using the Runs API directly.
23673
- // For now, tools with assistants should use the non-streaming chat completions API instead.
23674
- const rawResponse = await stream.finalMessages();
23675
- if (this.options.isVerbose) {
23676
- console.info(colors__default["default"].bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
23677
- }
23678
- if (rawResponse.length !== 1) {
23679
- throw new PipelineExecutionError(`There is NOT 1 BUT ${rawResponse.length} finalMessages from OpenAI`);
23680
- }
23681
- if (rawResponse[0].content.length !== 1) {
23682
- throw new PipelineExecutionError(`There is NOT 1 BUT ${rawResponse[0].content.length} finalMessages content from OpenAI`);
23683
- }
23684
- if (((_b = rawResponse[0].content[0]) === null || _b === void 0 ? void 0 : _b.type) !== 'text') {
23685
- throw new PipelineExecutionError(`There is NOT 'text' BUT ${(_c = rawResponse[0].content[0]) === null || _c === void 0 ? void 0 : _c.type} finalMessages content type from OpenAI`);
23686
- }
23687
- let resultContent = (_d = rawResponse[0].content[0]) === null || _d === void 0 ? void 0 : _d.text.value;
23688
- // Process annotations to replace file IDs with filenames
23689
- if ((_e = rawResponse[0].content[0]) === null || _e === void 0 ? void 0 : _e.text.annotations) {
23690
- const annotations = (_f = rawResponse[0].content[0]) === null || _f === void 0 ? void 0 : _f.text.annotations;
23691
- // Map to store file ID -> filename to avoid duplicate requests
23692
- const fileIdToName = new Map();
23693
- for (const annotation of annotations) {
23694
- if (annotation.type === 'file_citation') {
23695
- const fileId = annotation.file_citation.file_id;
23696
- let filename = fileIdToName.get(fileId);
23697
- if (!filename) {
23698
- try {
23699
- const file = await client.files.retrieve(fileId);
23700
- filename = file.filename;
23701
- fileIdToName.set(fileId, filename);
23702
- }
23703
- catch (error) {
23704
- console.error(`Failed to retrieve file info for ${fileId}`, error);
23705
- // Fallback to "Source" or keep original if fetch fails
23706
- filename = 'Source';
23707
- }
23708
- }
23709
- if (filename && resultContent) {
23710
- // Replace the citation marker with filename
23711
- // Regex to match the second part of the citation: 【id†source】 -> 【id†filename】
23712
- // Note: annotation.text contains the exact marker like 【4:0†source】
23713
- const newText = annotation.text.replace(/†.*?】/, `†${filename}】`);
23714
- resultContent = resultContent.replace(annotation.text, newText);
23715
- }
23716
- }
23717
- }
23718
- }
23719
- // eslint-disable-next-line prefer-const
23720
- complete = $getCurrentDate();
23721
- const usage = UNCERTAIN_USAGE;
23722
- // <- TODO: [🥘] Compute real usage for assistant
23723
- // ?> const usage = computeOpenAiUsage(content, resultContent || '', rawResponse);
23724
- if (resultContent === null) {
23725
- throw new PipelineExecutionError('No response message from OpenAI');
23726
- }
23727
- return exportJson({
23728
- name: 'promptResult',
23729
- message: `Result of \`OpenAiAssistantExecutionTools.callChatModelStream\``,
23730
- order: [],
23731
- value: {
23732
- content: resultContent,
23733
- modelName: 'assistant',
23734
- // <- TODO: [🥘] Detect used model in assistant
23735
- // ?> model: rawResponse.model || modelName,
23736
- timing: {
23737
- start,
23738
- complete,
23739
- },
23740
- usage,
23741
- rawPromptContent,
23742
- rawRequest,
23743
- rawResponse,
23744
- // <- [🗯]
23745
- },
23746
- });
23747
- }
23748
- /*
23749
- public async playground() {
23750
- const client = await this.getClient();
23751
-
23752
- // List all assistants
23753
- const assistants = await client.beta.assistants.list();
23754
-
23755
- // Get details of a specific assistant
23756
- const assistantId = 'asst_MO8fhZf4dGloCfXSHeLcIik0';
23757
- const assistant = await client.beta.assistants.retrieve(assistantId);
23758
-
23759
- // Update an assistant
23760
- const updatedAssistant = await client.beta.assistants.update(assistantId, {
23761
- name: assistant.name + '(M)',
23762
- description: 'Updated description via Promptbook',
23763
- metadata: {
23764
- [Math.random().toString(36).substring(2, 15)]: new Date().toISOString(),
23765
- },
23766
- });
23767
-
23768
- await forEver();
23769
- }
23770
- */
23771
- /**
23772
- * Get an existing assistant tool wrapper
23773
- */
23774
- getAssistant(assistantId) {
23775
- return new OpenAiAssistantExecutionTools({
23776
- ...this.options,
23777
- isCreatingNewAssistantsAllowed: this.isCreatingNewAssistantsAllowed,
23778
- assistantId,
23779
- });
23780
- }
23781
- /**
23782
- * Returns the per-knowledge-source download timeout in milliseconds.
23783
- */
23784
- getKnowledgeSourceDownloadTimeoutMs() {
23785
- var _a;
23786
- return (_a = this.assistantOptions.knowledgeSourceDownloadTimeoutMs) !== null && _a !== void 0 ? _a : DEFAULT_KNOWLEDGE_SOURCE_DOWNLOAD_TIMEOUT_MS;
23787
- }
23788
- /**
23789
- * Returns the max concurrency for knowledge source uploads.
23790
- */
23791
- getKnowledgeSourceUploadMaxConcurrency() {
23792
- var _a;
23793
- return (_a = this.assistantOptions.knowledgeSourceUploadMaxConcurrency) !== null && _a !== void 0 ? _a : 5;
23794
- }
23795
- /**
23796
- * Returns the polling interval in milliseconds for vector store uploads.
23797
- */
23798
- getKnowledgeSourceUploadPollIntervalMs() {
23799
- var _a;
23800
- return (_a = this.assistantOptions.knowledgeSourceUploadPollIntervalMs) !== null && _a !== void 0 ? _a : 5000;
23801
- }
23802
- /**
23803
- * Returns the overall upload timeout in milliseconds for vector store uploads.
23804
- */
23805
- getKnowledgeSourceUploadTimeoutMs() {
23806
- var _a;
23807
- return (_a = this.assistantOptions.knowledgeSourceUploadTimeoutMs) !== null && _a !== void 0 ? _a : DEFAULT_KNOWLEDGE_SOURCE_UPLOAD_TIMEOUT_MS;
23808
- }
23809
- /**
23810
- * Returns true if we should continue even if vector store ingestion stalls.
23811
- */
23812
- shouldContinueOnVectorStoreStall() {
23813
- var _a;
23814
- return (_a = this.assistantOptions.shouldContinueOnVectorStoreStall) !== null && _a !== void 0 ? _a : true;
23815
- }
23816
- /**
23817
- * Returns assistant-specific options with extended settings.
23818
- */
23819
- get assistantOptions() {
23820
- return this.options;
23821
- }
23822
- /**
23823
- * Downloads a knowledge source URL into a File for vector store upload.
23824
- */
23825
- async downloadKnowledgeSourceFile(options) {
23826
- var _a;
23827
- const { source, timeoutMs, logLabel } = options;
23828
- const startedAtMs = Date.now();
23829
- const controller = new AbortController();
23830
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
23831
- if (this.options.isVerbose) {
23832
- console.info('[🤰]', 'Downloading knowledge source', {
23833
- source,
23834
- timeoutMs,
23835
- logLabel,
23836
- });
23837
- }
23838
- try {
23839
- const response = await fetch(source, { signal: controller.signal });
23840
- const contentType = (_a = response.headers.get('content-type')) !== null && _a !== void 0 ? _a : undefined;
23841
- if (!response.ok) {
23842
- console.error('[🤰]', 'Failed to download knowledge source', {
23843
- source,
23844
- status: response.status,
23845
- statusText: response.statusText,
23846
- contentType,
23847
- elapsedMs: Date.now() - startedAtMs,
23848
- logLabel,
23849
- });
23850
- return null;
23851
- }
23852
- const buffer = await response.arrayBuffer();
23853
- let filename = source.split('/').pop() || 'downloaded-file';
23854
- try {
23855
- const url = new URL(source);
23856
- filename = url.pathname.split('/').pop() || filename;
23857
- }
23858
- catch (error) {
23859
- // Keep default filename
23860
- }
23861
- const file = new File([buffer], filename, contentType ? { type: contentType } : undefined);
23862
- const elapsedMs = Date.now() - startedAtMs;
23863
- const sizeBytes = buffer.byteLength;
23864
- if (this.options.isVerbose) {
23865
- console.info('[🤰]', 'Downloaded knowledge source', {
23866
- source,
23867
- filename,
23868
- sizeBytes,
23869
- contentType,
23870
- elapsedMs,
23871
- logLabel,
23872
- });
23873
- }
23874
- return { file, sizeBytes, filename, elapsedMs };
23875
- }
23876
- catch (error) {
23877
- assertsError(error);
23878
- console.error('[🤰]', 'Error downloading knowledge source', {
23879
- source,
23880
- elapsedMs: Date.now() - startedAtMs,
23881
- logLabel,
23882
- error: serializeError(error),
23883
- });
23884
- return null;
23885
- }
23886
- finally {
23887
- clearTimeout(timeoutId);
23888
- }
23889
- }
23890
- /**
23891
- * Logs vector store file batch diagnostics to help trace ingestion stalls or failures.
23892
- */
23893
- async logVectorStoreFileBatchDiagnostics(options) {
23894
- var _a, _b;
23895
- const { client, vectorStoreId, batchId, uploadedFiles, logLabel, reason } = options;
23896
- if (reason === 'stalled' && !this.options.isVerbose) {
23897
- return;
23898
- }
23899
- if (!batchId.startsWith('vsfb_')) {
23900
- console.error('[🤰]', 'Vector store file batch diagnostics skipped (invalid batch id)', {
23901
- vectorStoreId,
23902
- batchId,
23903
- reason,
23904
- logLabel,
23475
+ if (!batchId.startsWith('vsfb_')) {
23476
+ console.error('[🤰]', 'Vector store file batch diagnostics skipped (invalid batch id)', {
23477
+ vectorStoreId,
23478
+ batchId,
23479
+ reason,
23480
+ logLabel,
23905
23481
  });
23906
23482
  return;
23907
23483
  }
@@ -23910,8 +23486,10 @@
23910
23486
  fileIdToMetadata.set(file.fileId, file);
23911
23487
  }
23912
23488
  try {
23489
+ const vectorStores = this.getVectorStoresApi(client);
23913
23490
  const limit = Math.min(100, Math.max(10, uploadedFiles.length));
23914
- const batchFilesPage = await client.beta.vectorStores.fileBatches.listFiles(vectorStoreId, batchId, {
23491
+ const batchFilesPage = await vectorStores.fileBatches.listFiles(batchId, {
23492
+ vector_store_id: vectorStoreId,
23915
23493
  limit,
23916
23494
  });
23917
23495
  const batchFiles = (_a = batchFilesPage.data) !== null && _a !== void 0 ? _a : [];
@@ -23925,23 +23503,27 @@
23925
23503
  const inProgressSamples = [];
23926
23504
  const batchFileIds = new Set();
23927
23505
  for (const file of batchFiles) {
23928
- batchFileIds.add(file.id);
23929
- statusCounts[file.status] = ((_b = statusCounts[file.status]) !== null && _b !== void 0 ? _b : 0) + 1;
23930
- const metadata = fileIdToMetadata.get(file.id);
23931
- if (file.last_error) {
23506
+ const status = (_b = file.status) !== null && _b !== void 0 ? _b : 'unknown';
23507
+ statusCounts[status] = ((_c = statusCounts[status]) !== null && _c !== void 0 ? _c : 0) + 1;
23508
+ const vectorStoreFileId = file.id;
23509
+ const uploadedFileId = (_d = file.file_id) !== null && _d !== void 0 ? _d : file.fileId;
23510
+ const fileId = uploadedFileId !== null && uploadedFileId !== void 0 ? uploadedFileId : vectorStoreFileId;
23511
+ batchFileIds.add(fileId);
23512
+ const metadata = fileIdToMetadata.get(fileId);
23513
+ if (status === 'failed') {
23932
23514
  errorSamples.push({
23933
- fileId: file.id,
23515
+ fileId,
23516
+ status,
23517
+ error: (_e = file.last_error) === null || _e === void 0 ? void 0 : _e.message,
23934
23518
  filename: metadata === null || metadata === void 0 ? void 0 : metadata.filename,
23935
- sizeBytes: metadata === null || metadata === void 0 ? void 0 : metadata.sizeBytes,
23936
- status: file.status,
23937
- lastError: file.last_error,
23519
+ vectorStoreFileId: uploadedFileId ? vectorStoreFileId : undefined,
23938
23520
  });
23939
23521
  }
23940
- else if (file.status === 'in_progress' && inProgressSamples.length < 5) {
23522
+ if (status === 'in_progress') {
23941
23523
  inProgressSamples.push({
23942
- fileId: file.id,
23524
+ fileId,
23943
23525
  filename: metadata === null || metadata === void 0 ? void 0 : metadata.filename,
23944
- sizeBytes: metadata === null || metadata === void 0 ? void 0 : metadata.sizeBytes,
23526
+ vectorStoreFileId: uploadedFileId ? vectorStoreFileId : undefined,
23945
23527
  });
23946
23528
  }
23947
23529
  }
@@ -23953,7 +23535,7 @@
23953
23535
  filename: file.filename,
23954
23536
  sizeBytes: file.sizeBytes,
23955
23537
  }));
23956
- const vectorStore = await client.beta.vectorStores.retrieve(vectorStoreId);
23538
+ const vectorStore = await vectorStores.retrieve(vectorStoreId);
23957
23539
  const logPayload = {
23958
23540
  vectorStoreId,
23959
23541
  batchId,
@@ -23987,8 +23569,9 @@
23987
23569
  * Uploads knowledge source files to the vector store and polls until processing completes.
23988
23570
  */
23989
23571
  async uploadKnowledgeSourceFilesToVectorStore(options) {
23990
- var _a, _b, _c, _d;
23572
+ var _a, _b, _c, _d, _e, _f;
23991
23573
  const { client, vectorStoreId, files, totalBytes, logLabel } = options;
23574
+ const vectorStores = this.getVectorStoresApi(client);
23992
23575
  const uploadStartedAtMs = Date.now();
23993
23576
  const maxConcurrency = Math.max(1, this.getKnowledgeSourceUploadMaxConcurrency());
23994
23577
  const pollIntervalMs = Math.max(1000, this.getKnowledgeSourceUploadPollIntervalMs());
@@ -24105,373 +23688,1288 @@
24105
23688
  failedCount: failedUploads.length,
24106
23689
  logLabel,
24107
23690
  });
24108
- return null;
23691
+ return null;
23692
+ }
23693
+ const batch = await vectorStores.fileBatches.create(vectorStoreId, {
23694
+ file_ids: fileIds,
23695
+ });
23696
+ const expectedBatchId = batch.id;
23697
+ const expectedBatchIdValid = expectedBatchId.startsWith('vsfb_');
23698
+ if (!expectedBatchIdValid) {
23699
+ console.error('[🤰]', 'Vector store file batch id looks invalid', {
23700
+ vectorStoreId,
23701
+ batchId: expectedBatchId,
23702
+ batchVectorStoreId: batch.vector_store_id,
23703
+ logLabel,
23704
+ });
23705
+ }
23706
+ else if (batch.vector_store_id !== vectorStoreId) {
23707
+ console.error('[🤰]', 'Vector store file batch vector store id mismatch', {
23708
+ vectorStoreId,
23709
+ batchId: expectedBatchId,
23710
+ batchVectorStoreId: batch.vector_store_id,
23711
+ logLabel,
23712
+ });
23713
+ }
23714
+ if (this.options.isVerbose) {
23715
+ console.info('[🤰]', 'Created vector store file batch', {
23716
+ vectorStoreId,
23717
+ batchId: expectedBatchId,
23718
+ fileCount: fileIds.length,
23719
+ logLabel,
23720
+ });
23721
+ }
23722
+ const pollStartedAtMs = Date.now();
23723
+ const progressLogIntervalMs = Math.max(VECTOR_STORE_PROGRESS_LOG_INTERVAL_MIN_MS, pollIntervalMs);
23724
+ const diagnosticsIntervalMs = Math.max(60000, pollIntervalMs * 5);
23725
+ // let lastStatus: string | undefined;
23726
+ let lastCountsKey = '';
23727
+ let lastProgressKey = '';
23728
+ let lastLogAtMs = 0;
23729
+ let lastProgressAtMs = pollStartedAtMs;
23730
+ let lastDiagnosticsAtMs = pollStartedAtMs;
23731
+ let latestBatch = batch;
23732
+ let loggedBatchIdMismatch = false;
23733
+ let loggedBatchIdFallback = false;
23734
+ let loggedBatchIdInvalid = false;
23735
+ let shouldPoll = true;
23736
+ while (shouldPoll) {
23737
+ const nowMs = Date.now();
23738
+ // [🤰] Note: Sometimes OpenAI returns Vector Store object instead of Batch object, or IDs get swapped.
23739
+ const rawBatchId = typeof latestBatch.id === 'string' ? latestBatch.id : '';
23740
+ const rawVectorStoreId = latestBatch.vector_store_id;
23741
+ let returnedBatchId = rawBatchId;
23742
+ let returnedBatchIdValid = typeof returnedBatchId === 'string' && returnedBatchId.startsWith('vsfb_');
23743
+ if (!returnedBatchIdValid && expectedBatchIdValid) {
23744
+ if (!loggedBatchIdFallback) {
23745
+ console.error('[🤰]', 'Vector store file batch id missing from response; falling back to expected', {
23746
+ vectorStoreId,
23747
+ expectedBatchId,
23748
+ returnedBatchId,
23749
+ rawVectorStoreId,
23750
+ logLabel,
23751
+ });
23752
+ loggedBatchIdFallback = true;
23753
+ }
23754
+ returnedBatchId = expectedBatchId;
23755
+ returnedBatchIdValid = true;
23756
+ }
23757
+ if (!returnedBatchIdValid && !loggedBatchIdInvalid) {
23758
+ console.error('[🤰]', 'Vector store file batch id is invalid; stopping polling', {
23759
+ vectorStoreId,
23760
+ expectedBatchId,
23761
+ returnedBatchId,
23762
+ rawVectorStoreId,
23763
+ logLabel,
23764
+ });
23765
+ loggedBatchIdInvalid = true;
23766
+ }
23767
+ const batchIdMismatch = expectedBatchIdValid && returnedBatchIdValid && returnedBatchId !== expectedBatchId;
23768
+ if (batchIdMismatch && !loggedBatchIdMismatch) {
23769
+ console.error('[🤰]', 'Vector store file batch id mismatch', {
23770
+ vectorStoreId,
23771
+ expectedBatchId,
23772
+ returnedBatchId,
23773
+ logLabel,
23774
+ });
23775
+ loggedBatchIdMismatch = true;
23776
+ }
23777
+ if (returnedBatchIdValid) {
23778
+ latestBatch = await vectorStores.fileBatches.retrieve(returnedBatchId, {
23779
+ vector_store_id: vectorStoreId,
23780
+ });
23781
+ }
23782
+ else {
23783
+ shouldPoll = false;
23784
+ continue;
23785
+ }
23786
+ const status = (_e = latestBatch.status) !== null && _e !== void 0 ? _e : 'unknown';
23787
+ const fileCounts = (_f = latestBatch.file_counts) !== null && _f !== void 0 ? _f : {};
23788
+ const progressKey = JSON.stringify(fileCounts);
23789
+ const statusCountsKey = `${status}-${progressKey}`;
23790
+ const isProgressing = progressKey !== lastProgressKey;
23791
+ if (isProgressing) {
23792
+ lastProgressAtMs = nowMs;
23793
+ lastProgressKey = progressKey;
23794
+ }
23795
+ if (this.options.isVerbose &&
23796
+ (statusCountsKey !== lastCountsKey || nowMs - lastLogAtMs >= progressLogIntervalMs)) {
23797
+ console.info('[🤰]', 'Vector store file batch status', {
23798
+ vectorStoreId,
23799
+ batchId: returnedBatchId,
23800
+ status,
23801
+ fileCounts,
23802
+ elapsedMs: nowMs - pollStartedAtMs,
23803
+ logLabel,
23804
+ });
23805
+ lastCountsKey = statusCountsKey;
23806
+ lastLogAtMs = nowMs;
23807
+ }
23808
+ if (status === 'in_progress' &&
23809
+ nowMs - lastProgressAtMs >= VECTOR_STORE_STALL_LOG_THRESHOLD_MS &&
23810
+ nowMs - lastDiagnosticsAtMs >= diagnosticsIntervalMs) {
23811
+ lastDiagnosticsAtMs = nowMs;
23812
+ await this.logVectorStoreFileBatchDiagnostics({
23813
+ client,
23814
+ vectorStoreId,
23815
+ batchId: returnedBatchId,
23816
+ uploadedFiles,
23817
+ logLabel,
23818
+ reason: 'stalled',
23819
+ });
23820
+ }
23821
+ if (status === 'completed') {
23822
+ if (this.options.isVerbose) {
23823
+ console.info('[🤰]', 'Vector store file batch completed', {
23824
+ vectorStoreId,
23825
+ batchId: returnedBatchId,
23826
+ fileCounts,
23827
+ elapsedMs: nowMs - pollStartedAtMs,
23828
+ logLabel,
23829
+ });
23830
+ }
23831
+ shouldPoll = false;
23832
+ continue;
23833
+ }
23834
+ if (status === 'failed') {
23835
+ console.error('[🤰]', 'Vector store file batch completed with failures', {
23836
+ vectorStoreId,
23837
+ batchId: returnedBatchId,
23838
+ fileCounts,
23839
+ elapsedMs: nowMs - pollStartedAtMs,
23840
+ logLabel,
23841
+ });
23842
+ await this.logVectorStoreFileBatchDiagnostics({
23843
+ client,
23844
+ vectorStoreId,
23845
+ batchId: returnedBatchId,
23846
+ uploadedFiles,
23847
+ logLabel,
23848
+ reason: 'failed',
23849
+ });
23850
+ shouldPoll = false;
23851
+ continue;
23852
+ }
23853
+ if (status === 'cancelled') {
23854
+ console.error('[🤰]', 'Vector store file batch did not complete', {
23855
+ vectorStoreId,
23856
+ batchId: returnedBatchId,
23857
+ status,
23858
+ fileCounts,
23859
+ elapsedMs: nowMs - pollStartedAtMs,
23860
+ logLabel,
23861
+ });
23862
+ await this.logVectorStoreFileBatchDiagnostics({
23863
+ client,
23864
+ vectorStoreId,
23865
+ batchId: returnedBatchId,
23866
+ uploadedFiles,
23867
+ logLabel,
23868
+ reason: 'failed',
23869
+ });
23870
+ shouldPoll = false;
23871
+ continue;
23872
+ }
23873
+ if (nowMs - pollStartedAtMs >= uploadTimeoutMs) {
23874
+ console.error('[🤰]', 'Timed out waiting for vector store file batch', {
23875
+ vectorStoreId,
23876
+ batchId: returnedBatchId,
23877
+ fileCounts,
23878
+ elapsedMs: nowMs - pollStartedAtMs,
23879
+ uploadTimeoutMs,
23880
+ logLabel,
23881
+ });
23882
+ await this.logVectorStoreFileBatchDiagnostics({
23883
+ client,
23884
+ vectorStoreId,
23885
+ batchId: returnedBatchId,
23886
+ uploadedFiles,
23887
+ logLabel,
23888
+ reason: 'timeout',
23889
+ });
23890
+ if (this.shouldContinueOnVectorStoreStall()) {
23891
+ console.warn('[🤰]', 'Continuing despite vector store timeout as requested', {
23892
+ vectorStoreId,
23893
+ logLabel,
23894
+ });
23895
+ shouldPoll = false;
23896
+ continue;
23897
+ }
23898
+ try {
23899
+ const cancelBatchId = batchIdMismatch && returnedBatchId.startsWith('vsfb_') ? returnedBatchId : expectedBatchId;
23900
+ if (!cancelBatchId.startsWith('vsfb_')) {
23901
+ console.error('[🤰]', 'Skipping vector store file batch cancel (invalid batch id)', {
23902
+ vectorStoreId,
23903
+ batchId: cancelBatchId,
23904
+ logLabel,
23905
+ });
23906
+ }
23907
+ else {
23908
+ await vectorStores.fileBatches.cancel(cancelBatchId, {
23909
+ vector_store_id: vectorStoreId,
23910
+ });
23911
+ }
23912
+ if (this.options.isVerbose) {
23913
+ console.info('[🤰]', 'Cancelled vector store file batch after timeout', {
23914
+ vectorStoreId,
23915
+ batchId: batchIdMismatch && returnedBatchId.startsWith('vsfb_')
23916
+ ? returnedBatchId
23917
+ : expectedBatchId,
23918
+ ...(batchIdMismatch ? { returnedBatchId } : {}),
23919
+ logLabel,
23920
+ });
23921
+ }
23922
+ }
23923
+ catch (error) {
23924
+ assertsError(error);
23925
+ console.error('[🤰]', 'Failed to cancel vector store file batch after timeout', {
23926
+ vectorStoreId,
23927
+ batchId: expectedBatchId,
23928
+ ...(batchIdMismatch ? { returnedBatchId } : {}),
23929
+ logLabel,
23930
+ error: serializeError(error),
23931
+ });
23932
+ }
23933
+ shouldPoll = false;
23934
+ continue;
23935
+ }
23936
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
23937
+ }
23938
+ return latestBatch;
23939
+ }
23940
+ /**
23941
+ * Creates a vector store and uploads knowledge sources, returning its ID.
23942
+ */
23943
+ async createVectorStoreWithKnowledgeSources(options) {
23944
+ const { client, name, knowledgeSources, logLabel } = options;
23945
+ const vectorStores = this.getVectorStoresApi(client);
23946
+ const knowledgeSourcesCount = knowledgeSources.length;
23947
+ const downloadTimeoutMs = this.getKnowledgeSourceDownloadTimeoutMs();
23948
+ if (this.options.isVerbose) {
23949
+ console.info('[🤰]', 'Creating vector store with knowledge sources', {
23950
+ name,
23951
+ knowledgeSourcesCount,
23952
+ downloadTimeoutMs,
23953
+ logLabel,
23954
+ });
24109
23955
  }
24110
- const batch = await client.beta.vectorStores.fileBatches.create(vectorStoreId, {
24111
- file_ids: fileIds,
23956
+ const vectorStore = await vectorStores.create({
23957
+ name: `${name} Knowledge Base`,
24112
23958
  });
24113
- const expectedBatchId = batch.id;
24114
- const expectedBatchIdValid = expectedBatchId.startsWith('vsfb_');
24115
- if (!expectedBatchIdValid) {
24116
- console.error('[🤰]', 'Vector store file batch id looks invalid', {
23959
+ const vectorStoreId = vectorStore.id;
23960
+ if (this.options.isVerbose) {
23961
+ console.info('[🤰]', 'Vector store created', {
24117
23962
  vectorStoreId,
24118
- batchId: expectedBatchId,
24119
- batchVectorStoreId: batch.vector_store_id,
24120
23963
  logLabel,
24121
23964
  });
24122
23965
  }
24123
- else if (batch.vector_store_id !== vectorStoreId) {
24124
- console.error('[🤰]', 'Vector store file batch vector store id mismatch', {
24125
- vectorStoreId,
24126
- batchId: expectedBatchId,
24127
- batchVectorStoreId: batch.vector_store_id,
24128
- logLabel,
24129
- });
23966
+ const fileStreams = [];
23967
+ const skippedSources = [];
23968
+ let totalBytes = 0;
23969
+ const processingStartedAtMs = Date.now();
23970
+ for (const [index, source] of knowledgeSources.entries()) {
23971
+ try {
23972
+ const sourceType = source.startsWith('http') || source.startsWith('https') ? 'url' : 'file';
23973
+ if (this.options.isVerbose) {
23974
+ console.info('[🤰]', 'Processing knowledge source', {
23975
+ index: index + 1,
23976
+ total: knowledgeSourcesCount,
23977
+ source,
23978
+ sourceType,
23979
+ logLabel,
23980
+ });
23981
+ }
23982
+ // Check if it's a URL
23983
+ if (source.startsWith('http://') || source.startsWith('https://')) {
23984
+ const downloadResult = await this.downloadKnowledgeSourceFile({
23985
+ source,
23986
+ timeoutMs: downloadTimeoutMs,
23987
+ logLabel,
23988
+ });
23989
+ if (downloadResult) {
23990
+ fileStreams.push(downloadResult.file);
23991
+ totalBytes += downloadResult.sizeBytes;
23992
+ }
23993
+ else {
23994
+ skippedSources.push({ source, reason: 'download_failed' });
23995
+ }
23996
+ }
23997
+ else {
23998
+ skippedSources.push({ source, reason: 'unsupported_source_type' });
23999
+ if (this.options.isVerbose) {
24000
+ console.info('[🤰]', 'Skipping knowledge source (unsupported type)', {
24001
+ source,
24002
+ sourceType,
24003
+ logLabel,
24004
+ });
24005
+ }
24006
+ /*
24007
+ TODO: [🤰] Resolve problem with browser environment
24008
+ // Assume it's a local file path
24009
+ // Note: This will work in Node.js environment
24010
+ // For browser environments, this would need different handling
24011
+ const fs = await import('fs');
24012
+ const fileStream = fs.createReadStream(source);
24013
+ fileStreams.push(fileStream);
24014
+ */
24015
+ }
24016
+ }
24017
+ catch (error) {
24018
+ assertsError(error);
24019
+ skippedSources.push({ source, reason: 'processing_error' });
24020
+ console.error('[🤰]', 'Error processing knowledge source', {
24021
+ source,
24022
+ logLabel,
24023
+ error: serializeError(error),
24024
+ });
24025
+ }
24130
24026
  }
24131
24027
  if (this.options.isVerbose) {
24132
- console.info('[🤰]', 'Created vector store file batch', {
24133
- vectorStoreId,
24134
- batchId: expectedBatchId,
24135
- fileCount: fileIds.length,
24028
+ console.info('[🤰]', 'Finished processing knowledge sources', {
24029
+ total: knowledgeSourcesCount,
24030
+ downloadedCount: fileStreams.length,
24031
+ skippedCount: skippedSources.length,
24032
+ totalBytes,
24033
+ elapsedMs: Date.now() - processingStartedAtMs,
24034
+ skippedSamples: skippedSources.slice(0, 3),
24136
24035
  logLabel,
24137
24036
  });
24138
24037
  }
24139
- const pollStartedAtMs = Date.now();
24140
- const progressLogIntervalMs = Math.max(VECTOR_STORE_PROGRESS_LOG_INTERVAL_MIN_MS, pollIntervalMs);
24141
- const diagnosticsIntervalMs = Math.max(60000, pollIntervalMs * 5);
24142
- let lastStatus;
24143
- let lastCountsKey = '';
24144
- let lastProgressKey = '';
24145
- let lastLogAtMs = 0;
24146
- let lastProgressAtMs = pollStartedAtMs;
24147
- let lastDiagnosticsAtMs = pollStartedAtMs;
24148
- let latestBatch = batch;
24149
- let loggedBatchIdMismatch = false;
24150
- let shouldPoll = true;
24151
- while (shouldPoll) {
24152
- latestBatch = await client.beta.vectorStores.fileBatches.retrieve(vectorStoreId, expectedBatchId);
24153
- const counts = latestBatch.file_counts;
24154
- const countsKey = `${counts.completed}/${counts.failed}/${counts.in_progress}/${counts.cancelled}/${counts.total}`;
24155
- const nowMs = Date.now();
24156
- const returnedBatchId = latestBatch.id;
24157
- // [🤰] Note: Sometimes OpenAI returns Vector Store object instead of Batch object, or IDs get swapped.
24158
- // We only consider it a mismatch if the returned ID looks like a Batch ID.
24159
- const batchIdMismatch = returnedBatchId !== expectedBatchId && returnedBatchId.startsWith('vsfb_');
24160
- const diagnosticsBatchId = batchIdMismatch && returnedBatchId.startsWith('vsfb_') ? returnedBatchId : expectedBatchId;
24161
- const shouldLog = this.options.isVerbose &&
24162
- (latestBatch.status !== lastStatus ||
24163
- countsKey !== lastCountsKey ||
24164
- nowMs - lastLogAtMs >= progressLogIntervalMs);
24165
- if (batchIdMismatch && !loggedBatchIdMismatch) {
24166
- console.error('[🤰]', 'Vector store file batch id mismatch', {
24038
+ if (fileStreams.length > 0) {
24039
+ if (this.options.isVerbose) {
24040
+ console.info('[🤰]', 'Uploading files to vector store', {
24167
24041
  vectorStoreId,
24168
- expectedBatchId,
24169
- returnedBatchId,
24170
- status: latestBatch.status,
24171
- fileCounts: counts,
24042
+ fileCount: fileStreams.length,
24043
+ totalBytes,
24044
+ maxConcurrency: this.getKnowledgeSourceUploadMaxConcurrency(),
24045
+ pollIntervalMs: this.getKnowledgeSourceUploadPollIntervalMs(),
24046
+ uploadTimeoutMs: this.getKnowledgeSourceUploadTimeoutMs(),
24172
24047
  logLabel,
24173
24048
  });
24174
- loggedBatchIdMismatch = true;
24175
- }
24176
- if (countsKey !== lastProgressKey) {
24177
- lastProgressKey = countsKey;
24178
- lastProgressAtMs = nowMs;
24179
24049
  }
24180
- if (shouldLog) {
24181
- console.info('[🤰]', 'Vector store file batch status', {
24050
+ try {
24051
+ await this.uploadKnowledgeSourceFilesToVectorStore({
24052
+ client,
24182
24053
  vectorStoreId,
24183
- batchId: expectedBatchId,
24184
- ...(batchIdMismatch ? { returnedBatchId } : {}),
24185
- status: latestBatch.status,
24186
- fileCounts: counts,
24187
- elapsedMs: nowMs - pollStartedAtMs,
24054
+ files: fileStreams,
24055
+ totalBytes,
24188
24056
  logLabel,
24189
24057
  });
24190
- // [🤰] If there are in-progress files for a long time, log their details
24191
- if (counts.in_progress > 0 && nowMs - lastProgressAtMs > VECTOR_STORE_STALL_LOG_THRESHOLD_MS) {
24192
- await this.logVectorStoreFileBatchDiagnostics({
24193
- client,
24194
- vectorStoreId,
24195
- batchId: diagnosticsBatchId,
24196
- uploadedFiles,
24197
- logLabel,
24198
- reason: 'stalled',
24199
- });
24200
- }
24201
- lastStatus = latestBatch.status;
24202
- lastCountsKey = countsKey;
24203
- lastLogAtMs = nowMs;
24204
24058
  }
24205
- if (nowMs - lastProgressAtMs >= diagnosticsIntervalMs &&
24206
- nowMs - lastDiagnosticsAtMs >= diagnosticsIntervalMs) {
24207
- lastDiagnosticsAtMs = nowMs;
24208
- await this.logVectorStoreFileBatchDiagnostics({
24209
- client,
24059
+ catch (error) {
24060
+ assertsError(error);
24061
+ console.error('[🤰]', 'Error uploading files to vector store', {
24210
24062
  vectorStoreId,
24211
- batchId: diagnosticsBatchId,
24212
- uploadedFiles,
24213
24063
  logLabel,
24214
- reason: 'stalled',
24064
+ error: serializeError(error),
24215
24065
  });
24216
24066
  }
24217
- if (latestBatch.status === 'completed') {
24218
- if (this.options.isVerbose) {
24219
- console.info('[🤰]', 'Vector store file batch completed', {
24220
- vectorStoreId,
24221
- batchId: expectedBatchId,
24222
- ...(batchIdMismatch ? { returnedBatchId } : {}),
24223
- fileCounts: latestBatch.file_counts,
24224
- elapsedMs: Date.now() - uploadStartedAtMs,
24225
- logLabel,
24226
- });
24227
- }
24228
- if (latestBatch.file_counts.failed > 0) {
24229
- console.error('[🤰]', 'Vector store file batch completed with failures', {
24230
- vectorStoreId,
24231
- batchId: expectedBatchId,
24232
- ...(batchIdMismatch ? { returnedBatchId } : {}),
24233
- fileCounts: latestBatch.file_counts,
24234
- logLabel,
24235
- });
24236
- await this.logVectorStoreFileBatchDiagnostics({
24237
- client,
24238
- vectorStoreId,
24239
- batchId: diagnosticsBatchId,
24240
- uploadedFiles,
24241
- logLabel,
24242
- reason: 'failed',
24243
- });
24244
- }
24245
- shouldPoll = false;
24246
- continue;
24067
+ }
24068
+ else if (this.options.isVerbose) {
24069
+ console.info('[🤰]', 'No knowledge source files to upload', {
24070
+ vectorStoreId,
24071
+ skippedCount: skippedSources.length,
24072
+ logLabel,
24073
+ });
24074
+ }
24075
+ return {
24076
+ vectorStoreId,
24077
+ uploadedFileCount: fileStreams.length,
24078
+ skippedCount: skippedSources.length,
24079
+ totalBytes,
24080
+ };
24081
+ }
24082
+ }
24083
+
24084
+ const DEFAULT_AGENT_KIT_MODEL_NAME = 'gpt-5.2';
24085
+ /**
24086
+ * Execution tools for OpenAI AgentKit (Agents SDK).
24087
+ *
24088
+ * @public exported from `@promptbook/openai`
24089
+ */
24090
+ class OpenAiAgentKitExecutionTools extends OpenAiVectorStoreHandler {
24091
+ /**
24092
+ * Creates OpenAI AgentKit execution tools.
24093
+ */
24094
+ constructor(options) {
24095
+ var _a;
24096
+ if (options.isProxied) {
24097
+ throw new NotYetImplementedError(`Proxy mode is not yet implemented for OpenAI AgentKit`);
24098
+ }
24099
+ super(options);
24100
+ this.preparedAgentKitAgent = null;
24101
+ this.agentKitModelName = (_a = options.agentKitModelName) !== null && _a !== void 0 ? _a : DEFAULT_AGENT_KIT_MODEL_NAME;
24102
+ }
24103
+ get title() {
24104
+ return 'OpenAI AgentKit';
24105
+ }
24106
+ get description() {
24107
+ return 'Use OpenAI AgentKit for agent-style chat with tools and knowledge';
24108
+ }
24109
+ /**
24110
+ * Calls OpenAI AgentKit with a chat prompt (non-streaming).
24111
+ */
24112
+ async callChatModel(prompt) {
24113
+ return this.callChatModelStream(prompt, () => { });
24114
+ }
24115
+ /**
24116
+ * Calls OpenAI AgentKit with a chat prompt (streaming).
24117
+ */
24118
+ async callChatModelStream(prompt, onProgress) {
24119
+ const { content, parameters, modelRequirements } = prompt;
24120
+ if (modelRequirements.modelVariant !== 'CHAT') {
24121
+ throw new PipelineExecutionError('Use callChatModel only for CHAT variant');
24122
+ }
24123
+ for (const key of ['maxTokens', 'modelName', 'seed', 'temperature']) {
24124
+ if (modelRequirements[key] !== undefined) {
24125
+ throw new NotYetImplementedError(`In \`OpenAiAgentKitExecutionTools\` you cannot specify \`${key}\``);
24126
+ }
24127
+ }
24128
+ const rawPromptContent = templateParameters(content, {
24129
+ ...parameters,
24130
+ modelName: this.agentKitModelName,
24131
+ });
24132
+ const preparedAgentKitAgent = await this.prepareAgentKitAgent({
24133
+ name: (prompt.title || 'Agent'),
24134
+ instructions: modelRequirements.systemMessage || '',
24135
+ knowledgeSources: modelRequirements.knowledgeSources,
24136
+ tools: 'tools' in prompt && Array.isArray(prompt.tools) ? prompt.tools : modelRequirements.tools,
24137
+ });
24138
+ return this.callChatModelStreamWithPreparedAgent({
24139
+ openAiAgentKitAgent: preparedAgentKitAgent.agent,
24140
+ prompt,
24141
+ rawPromptContent,
24142
+ onProgress,
24143
+ });
24144
+ }
24145
+ /**
24146
+ * Returns a prepared AgentKit agent when the server wants to manage caching externally.
24147
+ */
24148
+ getPreparedAgentKitAgent() {
24149
+ return this.preparedAgentKitAgent;
24150
+ }
24151
+ /**
24152
+ * Stores a prepared AgentKit agent for later reuse by external cache managers.
24153
+ */
24154
+ setPreparedAgentKitAgent(preparedAgent) {
24155
+ this.preparedAgentKitAgent = preparedAgent;
24156
+ }
24157
+ /**
24158
+ * Creates a new tools instance bound to a prepared AgentKit agent.
24159
+ */
24160
+ getPreparedAgentTools(preparedAgent) {
24161
+ const tools = new OpenAiAgentKitExecutionTools(this.agentKitOptions);
24162
+ tools.setPreparedAgentKitAgent(preparedAgent);
24163
+ return tools;
24164
+ }
24165
+ /**
24166
+ * Prepares an AgentKit agent with optional knowledge sources and tool definitions.
24167
+ */
24168
+ async prepareAgentKitAgent(options) {
24169
+ var _a, _b;
24170
+ const { name, instructions, knowledgeSources, tools, vectorStoreId: cachedVectorStoreId, storeAsPrepared, } = options;
24171
+ await this.ensureAgentKitDefaults();
24172
+ if (this.options.isVerbose) {
24173
+ console.info('[🤰]', 'Preparing OpenAI AgentKit agent', {
24174
+ name,
24175
+ instructionsLength: instructions.length,
24176
+ knowledgeSourcesCount: (_a = knowledgeSources === null || knowledgeSources === void 0 ? void 0 : knowledgeSources.length) !== null && _a !== void 0 ? _a : 0,
24177
+ toolsCount: (_b = tools === null || tools === void 0 ? void 0 : tools.length) !== null && _b !== void 0 ? _b : 0,
24178
+ });
24179
+ }
24180
+ let vectorStoreId = cachedVectorStoreId;
24181
+ if (!vectorStoreId && knowledgeSources && knowledgeSources.length > 0) {
24182
+ const vectorStoreResult = await this.createVectorStoreWithKnowledgeSources({
24183
+ client: await this.getClient(),
24184
+ name,
24185
+ knowledgeSources,
24186
+ logLabel: 'agentkit preparation',
24187
+ });
24188
+ vectorStoreId = vectorStoreResult.vectorStoreId;
24189
+ }
24190
+ else if (vectorStoreId && this.options.isVerbose) {
24191
+ console.info('[🤰]', 'Using cached vector store for AgentKit agent', {
24192
+ name,
24193
+ vectorStoreId,
24194
+ });
24195
+ }
24196
+ const agentKitTools = this.buildAgentKitTools({ tools, vectorStoreId });
24197
+ const openAiAgentKitAgent = new agents.Agent({
24198
+ name,
24199
+ model: this.agentKitModelName,
24200
+ instructions: instructions || 'You are a helpful assistant.',
24201
+ tools: agentKitTools,
24202
+ });
24203
+ const preparedAgent = {
24204
+ agent: openAiAgentKitAgent,
24205
+ vectorStoreId,
24206
+ };
24207
+ if (storeAsPrepared) {
24208
+ this.setPreparedAgentKitAgent(preparedAgent);
24209
+ }
24210
+ if (this.options.isVerbose) {
24211
+ console.info('[🤰]', 'OpenAI AgentKit agent ready', {
24212
+ name,
24213
+ model: this.agentKitModelName,
24214
+ toolCount: agentKitTools.length,
24215
+ hasVectorStore: Boolean(vectorStoreId),
24216
+ });
24217
+ }
24218
+ return preparedAgent;
24219
+ }
24220
+ /**
24221
+ * Ensures the AgentKit SDK is wired to the OpenAI client and API key.
24222
+ */
24223
+ async ensureAgentKitDefaults() {
24224
+ const client = await this.getClient();
24225
+ agents.setDefaultOpenAIClient(client);
24226
+ const apiKey = this.agentKitOptions.apiKey;
24227
+ if (apiKey && typeof apiKey === 'string') {
24228
+ agents.setDefaultOpenAIKey(apiKey);
24229
+ }
24230
+ }
24231
+ /**
24232
+ * Builds the tool list for AgentKit, including hosted file search when applicable.
24233
+ */
24234
+ buildAgentKitTools(options) {
24235
+ var _a;
24236
+ const { tools, vectorStoreId } = options;
24237
+ const agentKitTools = [];
24238
+ if (vectorStoreId) {
24239
+ agentKitTools.push(agents.fileSearchTool(vectorStoreId));
24240
+ }
24241
+ if (tools && tools.length > 0) {
24242
+ const scriptTools = this.resolveScriptTools();
24243
+ for (const toolDefinition of tools) {
24244
+ agentKitTools.push(agents.tool({
24245
+ name: toolDefinition.name,
24246
+ description: toolDefinition.description,
24247
+ parameters: toolDefinition.parameters
24248
+ ? {
24249
+ ...toolDefinition.parameters,
24250
+ additionalProperties: false,
24251
+ required: (_a = toolDefinition.parameters.required) !== null && _a !== void 0 ? _a : [],
24252
+ }
24253
+ : undefined,
24254
+ strict: false,
24255
+ execute: async (input, runContext, details) => {
24256
+ var _a, _b, _c;
24257
+ const scriptTool = scriptTools[0];
24258
+ const functionName = toolDefinition.name;
24259
+ const calledAt = $getCurrentDate();
24260
+ const callId = (_a = details === null || details === void 0 ? void 0 : details.toolCall) === null || _a === void 0 ? void 0 : _a.callId;
24261
+ const functionArgs = input !== null && input !== void 0 ? input : {};
24262
+ if (this.options.isVerbose) {
24263
+ console.info('[🤰]', 'Executing AgentKit tool', {
24264
+ functionName,
24265
+ callId,
24266
+ calledAt,
24267
+ });
24268
+ }
24269
+ try {
24270
+ return await scriptTool.execute({
24271
+ scriptLanguage: 'javascript',
24272
+ script: `
24273
+ const args = ${JSON.stringify(functionArgs)};
24274
+ return await ${functionName}(args);
24275
+ `,
24276
+ parameters: (_c = (_b = runContext === null || runContext === void 0 ? void 0 : runContext.context) === null || _b === void 0 ? void 0 : _b.parameters) !== null && _c !== void 0 ? _c : {},
24277
+ });
24278
+ }
24279
+ catch (error) {
24280
+ assertsError(error);
24281
+ const serializedError = serializeError(error);
24282
+ const errorMessage = spaceTrim__default["default"]((block) => `
24283
+
24284
+ The invoked tool \`${functionName}\` failed with error:
24285
+
24286
+ \`\`\`json
24287
+ ${block(JSON.stringify(serializedError, null, 4))}
24288
+ \`\`\`
24289
+
24290
+ `);
24291
+ console.error('[🤰]', 'AgentKit tool execution failed', {
24292
+ functionName,
24293
+ callId,
24294
+ error: serializedError,
24295
+ });
24296
+ return errorMessage;
24297
+ }
24298
+ },
24299
+ }));
24247
24300
  }
24248
- if (latestBatch.status === 'failed' || latestBatch.status === 'cancelled') {
24249
- console.error('[🤰]', 'Vector store file batch did not complete', {
24250
- vectorStoreId,
24251
- batchId: expectedBatchId,
24252
- ...(batchIdMismatch ? { returnedBatchId } : {}),
24253
- status: latestBatch.status,
24254
- fileCounts: latestBatch.file_counts,
24255
- elapsedMs: Date.now() - uploadStartedAtMs,
24256
- logLabel,
24257
- });
24258
- await this.logVectorStoreFileBatchDiagnostics({
24259
- client,
24260
- vectorStoreId,
24261
- batchId: diagnosticsBatchId,
24262
- uploadedFiles,
24263
- logLabel,
24264
- reason: 'failed',
24301
+ }
24302
+ return agentKitTools;
24303
+ }
24304
+ /**
24305
+ * Resolves the configured script tools for tool execution.
24306
+ */
24307
+ resolveScriptTools() {
24308
+ const executionTools = this.options.executionTools;
24309
+ if (!executionTools || !executionTools.script) {
24310
+ throw new PipelineExecutionError(`Model requested tools but no executionTools.script were provided in OpenAiAgentKitExecutionTools options`);
24311
+ }
24312
+ return Array.isArray(executionTools.script) ? executionTools.script : [executionTools.script];
24313
+ }
24314
+ /**
24315
+ * Runs a prepared AgentKit agent and streams results back to the caller.
24316
+ */
24317
+ async callChatModelStreamWithPreparedAgent(options) {
24318
+ var _a, _b, _c, _d;
24319
+ const { openAiAgentKitAgent, prompt, onProgress } = options;
24320
+ const rawPromptContent = (_a = options.rawPromptContent) !== null && _a !== void 0 ? _a : templateParameters(prompt.content, {
24321
+ ...prompt.parameters,
24322
+ modelName: this.agentKitModelName,
24323
+ });
24324
+ const start = $getCurrentDate();
24325
+ let latestContent = '';
24326
+ const toolCalls = [];
24327
+ const toolCallIndexById = new Map();
24328
+ const inputItems = await this.buildAgentKitInputItems(prompt, rawPromptContent);
24329
+ const rawRequest = {
24330
+ agentName: openAiAgentKitAgent.name,
24331
+ input: inputItems,
24332
+ };
24333
+ const streamResult = await agents.run(openAiAgentKitAgent, inputItems, {
24334
+ stream: true,
24335
+ context: { parameters: prompt.parameters },
24336
+ });
24337
+ for await (const event of streamResult) {
24338
+ if (event.type === 'raw_model_stream_event' && ((_b = event.data) === null || _b === void 0 ? void 0 : _b.type) === 'output_text_delta') {
24339
+ latestContent += event.data.delta;
24340
+ onProgress({
24341
+ content: latestContent,
24342
+ modelName: this.agentKitModelName,
24343
+ timing: { start, complete: $getCurrentDate() },
24344
+ usage: UNCERTAIN_USAGE,
24345
+ rawPromptContent: rawPromptContent,
24346
+ rawRequest: null,
24347
+ rawResponse: {},
24265
24348
  });
24266
- shouldPoll = false;
24267
24349
  continue;
24268
24350
  }
24269
- if (nowMs - pollStartedAtMs >= uploadTimeoutMs) {
24270
- console.error('[🤰]', 'Timed out waiting for vector store file batch', {
24271
- vectorStoreId,
24272
- batchId: expectedBatchId,
24273
- ...(batchIdMismatch ? { returnedBatchId } : {}),
24274
- fileCounts: latestBatch.file_counts,
24275
- elapsedMs: nowMs - pollStartedAtMs,
24276
- uploadTimeoutMs,
24277
- logLabel,
24278
- });
24279
- await this.logVectorStoreFileBatchDiagnostics({
24280
- client,
24281
- vectorStoreId,
24282
- batchId: diagnosticsBatchId,
24283
- uploadedFiles,
24284
- logLabel,
24285
- reason: 'timeout',
24286
- });
24287
- if (this.shouldContinueOnVectorStoreStall()) {
24288
- console.warn('[🤰]', 'Continuing despite vector store timeout as requested', {
24289
- vectorStoreId,
24290
- logLabel,
24351
+ if (event.type === 'run_item_stream_event') {
24352
+ const rawItem = (_c = event.item) === null || _c === void 0 ? void 0 : _c.rawItem;
24353
+ if (event.name === 'tool_called' && (rawItem === null || rawItem === void 0 ? void 0 : rawItem.type) === 'function_call') {
24354
+ const toolCall = {
24355
+ name: rawItem.name,
24356
+ arguments: rawItem.arguments,
24357
+ rawToolCall: rawItem,
24358
+ createdAt: $getCurrentDate(),
24359
+ };
24360
+ toolCallIndexById.set(rawItem.callId, toolCalls.length);
24361
+ toolCalls.push(toolCall);
24362
+ onProgress({
24363
+ content: latestContent,
24364
+ modelName: this.agentKitModelName,
24365
+ timing: { start, complete: $getCurrentDate() },
24366
+ usage: UNCERTAIN_USAGE,
24367
+ rawPromptContent: rawPromptContent,
24368
+ rawRequest: null,
24369
+ rawResponse: {},
24370
+ toolCalls: [toolCall],
24291
24371
  });
24292
- shouldPoll = false;
24293
- continue;
24294
24372
  }
24295
- try {
24296
- const cancelBatchId = batchIdMismatch && returnedBatchId.startsWith('vsfb_') ? returnedBatchId : expectedBatchId;
24297
- if (!cancelBatchId.startsWith('vsfb_')) {
24298
- console.error('[🤰]', 'Skipping vector store file batch cancel (invalid batch id)', {
24299
- vectorStoreId,
24300
- batchId: cancelBatchId,
24301
- logLabel,
24302
- });
24303
- }
24304
- else {
24305
- await client.beta.vectorStores.fileBatches.cancel(vectorStoreId, cancelBatchId);
24306
- }
24307
- if (this.options.isVerbose) {
24308
- console.info('[🤰]', 'Cancelled vector store file batch after timeout', {
24309
- vectorStoreId,
24310
- batchId: batchIdMismatch && returnedBatchId.startsWith('vsfb_')
24311
- ? returnedBatchId
24312
- : expectedBatchId,
24313
- ...(batchIdMismatch ? { returnedBatchId } : {}),
24314
- logLabel,
24373
+ if (event.name === 'tool_output' && (rawItem === null || rawItem === void 0 ? void 0 : rawItem.type) === 'function_call_result') {
24374
+ const index = toolCallIndexById.get(rawItem.callId);
24375
+ const result = this.formatAgentKitToolOutput(rawItem.output);
24376
+ if (index !== undefined) {
24377
+ const existingToolCall = toolCalls[index];
24378
+ const completedToolCall = {
24379
+ ...existingToolCall,
24380
+ result,
24381
+ rawToolCall: rawItem,
24382
+ };
24383
+ toolCalls[index] = completedToolCall;
24384
+ onProgress({
24385
+ content: latestContent,
24386
+ modelName: this.agentKitModelName,
24387
+ timing: { start, complete: $getCurrentDate() },
24388
+ usage: UNCERTAIN_USAGE,
24389
+ rawPromptContent: rawPromptContent,
24390
+ rawRequest: null,
24391
+ rawResponse: {},
24392
+ toolCalls: [completedToolCall],
24315
24393
  });
24316
24394
  }
24317
24395
  }
24318
- catch (error) {
24319
- assertsError(error);
24320
- console.error('[🤰]', 'Failed to cancel vector store file batch after timeout', {
24321
- vectorStoreId,
24322
- batchId: expectedBatchId,
24323
- ...(batchIdMismatch ? { returnedBatchId } : {}),
24324
- logLabel,
24325
- error: serializeError(error),
24396
+ }
24397
+ }
24398
+ await streamResult.completed;
24399
+ const complete = $getCurrentDate();
24400
+ const finalContent = ((_d = streamResult.finalOutput) !== null && _d !== void 0 ? _d : latestContent);
24401
+ const finalResult = {
24402
+ content: finalContent,
24403
+ modelName: this.agentKitModelName,
24404
+ timing: { start, complete },
24405
+ usage: UNCERTAIN_USAGE,
24406
+ rawPromptContent: rawPromptContent,
24407
+ rawRequest,
24408
+ rawResponse: { runResult: streamResult },
24409
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
24410
+ };
24411
+ onProgress(finalResult);
24412
+ return finalResult;
24413
+ }
24414
+ /**
24415
+ * Builds AgentKit input items from the prompt and optional thread.
24416
+ */
24417
+ async buildAgentKitInputItems(prompt, rawPromptContent) {
24418
+ var _a;
24419
+ const inputItems = [];
24420
+ if ('thread' in prompt && Array.isArray(prompt.thread)) {
24421
+ for (const message of prompt.thread) {
24422
+ const sender = message.sender;
24423
+ const content = (_a = message.content) !== null && _a !== void 0 ? _a : '';
24424
+ if (sender === 'assistant' || sender === 'agent') {
24425
+ inputItems.push({
24426
+ role: 'assistant',
24427
+ status: 'completed',
24428
+ content: [{ type: 'output_text', text: content }],
24429
+ });
24430
+ }
24431
+ else {
24432
+ inputItems.push({
24433
+ role: 'user',
24434
+ content,
24326
24435
  });
24327
24436
  }
24328
- shouldPoll = false;
24329
- continue;
24330
24437
  }
24331
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
24332
24438
  }
24333
- return latestBatch;
24439
+ const userContent = await this.buildAgentKitUserContent(prompt, rawPromptContent);
24440
+ inputItems.push({
24441
+ role: 'user',
24442
+ content: userContent,
24443
+ });
24444
+ return inputItems;
24334
24445
  }
24335
24446
  /**
24336
- * Creates a vector store and uploads knowledge sources, returning its ID.
24447
+ * Builds the user message content for AgentKit runs, including file inputs when provided.
24337
24448
  */
24338
- async createVectorStoreWithKnowledgeSources(options) {
24339
- const { client, name, knowledgeSources, logLabel } = options;
24340
- const knowledgeSourcesCount = knowledgeSources.length;
24341
- const downloadTimeoutMs = this.getKnowledgeSourceDownloadTimeoutMs();
24449
+ async buildAgentKitUserContent(prompt, rawPromptContent) {
24450
+ if ('files' in prompt && Array.isArray(prompt.files) && prompt.files.length > 0) {
24451
+ const fileItems = await Promise.all(prompt.files.map(async (file) => {
24452
+ const arrayBuffer = await file.arrayBuffer();
24453
+ const base64 = Buffer.from(arrayBuffer).toString('base64');
24454
+ return {
24455
+ type: 'input_image',
24456
+ image: `data:${file.type};base64,${base64}`,
24457
+ };
24458
+ }));
24459
+ return [{ type: 'input_text', text: rawPromptContent }, ...fileItems];
24460
+ }
24461
+ return rawPromptContent;
24462
+ }
24463
+ /**
24464
+ * Normalizes AgentKit tool outputs into a string for Promptbook tool call results.
24465
+ */
24466
+ formatAgentKitToolOutput(output) {
24467
+ if (typeof output === 'string') {
24468
+ return output;
24469
+ }
24470
+ if (output && typeof output === 'object') {
24471
+ const textOutput = output;
24472
+ if (textOutput.type === 'text' && typeof textOutput.text === 'string') {
24473
+ return textOutput.text;
24474
+ }
24475
+ }
24476
+ return JSON.stringify(output !== null && output !== void 0 ? output : null);
24477
+ }
24478
+ /**
24479
+ * Returns AgentKit-specific options.
24480
+ */
24481
+ get agentKitOptions() {
24482
+ return this.options;
24483
+ }
24484
+ /**
24485
+ * Discriminant for type guards.
24486
+ */
24487
+ get discriminant() {
24488
+ return DISCRIMINANT$1;
24489
+ }
24490
+ /**
24491
+ * Type guard to check if given `LlmExecutionTools` are instanceof `OpenAiAgentKitExecutionTools`.
24492
+ */
24493
+ static isOpenAiAgentKitExecutionTools(llmExecutionTools) {
24494
+ return llmExecutionTools.discriminant === DISCRIMINANT$1;
24495
+ }
24496
+ }
24497
+ /**
24498
+ * Discriminant for type guards.
24499
+ *
24500
+ * @private const of `OpenAiAgentKitExecutionTools`
24501
+ */
24502
+ const DISCRIMINANT$1 = 'OPEN_AI_AGENT_KIT_V1';
24503
+
24504
+ /**
24505
+ * Uploads files to OpenAI and returns their IDs
24506
+ *
24507
+ * @private utility for `OpenAiAssistantExecutionTools` and `OpenAiCompatibleExecutionTools`
24508
+ */
24509
+ async function uploadFilesToOpenAi(client, files) {
24510
+ const fileIds = [];
24511
+ for (const file of files) {
24512
+ // Note: OpenAI API expects a File object or a ReadStream
24513
+ // In browser environment, we can pass the File object directly
24514
+ // In Node.js environment, we might need to convert it or use a different approach
24515
+ // But since `Prompt.files` already contains `File` objects, we try to pass them directly
24516
+ const uploadedFile = await client.files.create({
24517
+ file: file,
24518
+ purpose: 'assistants',
24519
+ });
24520
+ fileIds.push(uploadedFile.id);
24521
+ }
24522
+ return fileIds;
24523
+ }
24524
+
24525
+ /**
24526
+ * Execution Tools for calling OpenAI API Assistants
24527
+ *
24528
+ * This is useful for calling OpenAI API with a single assistant, for more wide usage use `OpenAiExecutionTools`.
24529
+ *
24530
+ * Note: [🦖] There are several different things in Promptbook:
24531
+ * - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
24532
+ * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
24533
+ * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
24534
+ * - `OpenAiAssistantExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities, recommended for usage in `Agent` or `AgentLlmExecutionTools`
24535
+ * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
24536
+ *
24537
+ * @deprecated Use `OpenAiAgentKitExecutionTools` instead.
24538
+ * @public exported from `@promptbook/openai`
24539
+ */
24540
+ class OpenAiAssistantExecutionTools extends OpenAiVectorStoreHandler {
24541
+ /**
24542
+ * Creates OpenAI Execution Tools.
24543
+ *
24544
+ * @param options which are relevant are directly passed to the OpenAI client
24545
+ */
24546
+ constructor(options) {
24547
+ var _a;
24548
+ if (options.isProxied) {
24549
+ throw new NotYetImplementedError(`Proxy mode is not yet implemented for OpenAI assistants`);
24550
+ }
24551
+ super(options);
24552
+ this.isCreatingNewAssistantsAllowed = false;
24553
+ this.assistantId = options.assistantId;
24554
+ this.isCreatingNewAssistantsAllowed = (_a = options.isCreatingNewAssistantsAllowed) !== null && _a !== void 0 ? _a : false;
24555
+ if (this.assistantId === null && !this.isCreatingNewAssistantsAllowed) {
24556
+ throw new NotAllowed(`Assistant ID is null and creating new assistants is not allowed - this configuration does not make sense`);
24557
+ }
24558
+ // <- TODO: !!! `OpenAiAssistantExecutionToolsOptions` - Allow `assistantId: null` together with `isCreatingNewAssistantsAllowed: true`
24559
+ // TODO: [👱] Make limiter same as in `OpenAiExecutionTools`
24560
+ }
24561
+ get title() {
24562
+ return 'OpenAI Assistant';
24563
+ }
24564
+ get description() {
24565
+ return 'Use single assistant provided by OpenAI';
24566
+ }
24567
+ /**
24568
+ * Calls OpenAI API to use a chat model.
24569
+ */
24570
+ async callChatModel(prompt) {
24571
+ return this.callChatModelStream(prompt, () => { });
24572
+ }
24573
+ /**
24574
+ * Calls OpenAI API to use a chat model with streaming.
24575
+ */
24576
+ async callChatModelStream(prompt, onProgress) {
24577
+ var _a, _b, _c, _d, _e, _f;
24342
24578
  if (this.options.isVerbose) {
24343
- console.info('[🤰]', 'Creating vector store with knowledge sources', {
24344
- name,
24345
- knowledgeSourcesCount,
24346
- downloadTimeoutMs,
24347
- logLabel,
24348
- });
24579
+ console.info('💬 OpenAI callChatModel call', { prompt });
24580
+ }
24581
+ const { content, parameters, modelRequirements /*, format*/ } = prompt;
24582
+ const client = await this.getClient();
24583
+ // TODO: [☂] Use here more modelRequirements
24584
+ if (modelRequirements.modelVariant !== 'CHAT') {
24585
+ throw new PipelineExecutionError('Use callChatModel only for CHAT variant');
24586
+ }
24587
+ // TODO: [👨‍👨‍👧‍👧] Remove:
24588
+ for (const key of ['maxTokens', 'modelName', 'seed', 'temperature']) {
24589
+ if (modelRequirements[key] !== undefined) {
24590
+ throw new NotYetImplementedError(`In \`OpenAiAssistantExecutionTools\` you cannot specify \`${key}\``);
24591
+ }
24349
24592
  }
24350
- const vectorStore = await client.beta.vectorStores.create({
24351
- name: `${name} Knowledge Base`,
24593
+ /*
24594
+ TODO: [👨‍👨‍👧‍👧] Implement all of this for Assistants
24595
+ const modelName = modelRequirements.modelName || this.getDefaultChatModel().modelName;
24596
+ const modelSettings = {
24597
+ model: modelName,
24598
+
24599
+ temperature: modelRequirements.temperature,
24600
+
24601
+ // <- TODO: [🈁] Use `seed` here AND/OR use is `isDeterministic` for entire execution tools
24602
+ // <- Note: [🧆]
24603
+ } as OpenAI.Chat.Completions.CompletionCreateParamsNonStreaming; // <- TODO: Guard here types better
24604
+
24605
+ if (format === 'JSON') {
24606
+ modelSettings.response_format = {
24607
+ type: 'json_object',
24608
+ };
24609
+ }
24610
+ */
24611
+ // <- TODO: [🚸] Not all models are compatible with JSON mode
24612
+ // > 'response_format' of type 'json_object' is not supported with this model.
24613
+ const rawPromptContent = templateParameters(content, {
24614
+ ...parameters,
24615
+ modelName: 'assistant',
24616
+ // <- [🧠] What is the best value here
24352
24617
  });
24353
- const vectorStoreId = vectorStore.id;
24354
- if (this.options.isVerbose) {
24355
- console.info('[🤰]', 'Vector store created', {
24356
- vectorStoreId,
24357
- logLabel,
24358
- });
24618
+ // Build thread messages: include previous thread messages + current user message
24619
+ const threadMessages = [];
24620
+ // TODO: [🈹] Maybe this should not be here but in other place, look at commit 39d705e75e5bcf7a818c3af36bc13e1c8475c30c
24621
+ // Add previous messages from thread (if any)
24622
+ if ('thread' in prompt && Array.isArray(prompt.thread)) {
24623
+ const previousMessages = prompt.thread.map((msg) => ({
24624
+ role: (msg.sender === 'assistant' ? 'assistant' : 'user'),
24625
+ content: msg.content,
24626
+ }));
24627
+ threadMessages.push(...previousMessages);
24359
24628
  }
24360
- const fileStreams = [];
24361
- const skippedSources = [];
24362
- let totalBytes = 0;
24363
- const processingStartedAtMs = Date.now();
24364
- for (const [index, source] of knowledgeSources.entries()) {
24365
- try {
24366
- const sourceType = source.startsWith('http') || source.startsWith('https') ? 'url' : 'file';
24367
- if (this.options.isVerbose) {
24368
- console.info('[🤰]', 'Processing knowledge source', {
24369
- index: index + 1,
24370
- total: knowledgeSourcesCount,
24371
- source,
24372
- sourceType,
24373
- logLabel,
24374
- });
24375
- }
24376
- // Check if it's a URL
24377
- if (source.startsWith('http://') || source.startsWith('https://')) {
24378
- const downloadResult = await this.downloadKnowledgeSourceFile({
24379
- source,
24380
- timeoutMs: downloadTimeoutMs,
24381
- logLabel,
24382
- });
24383
- if (downloadResult) {
24384
- fileStreams.push(downloadResult.file);
24385
- totalBytes += downloadResult.sizeBytes;
24386
- }
24387
- else {
24388
- skippedSources.push({ source, reason: 'download_failed' });
24629
+ // Always add the current user message
24630
+ const currentUserMessage = {
24631
+ role: 'user',
24632
+ content: rawPromptContent,
24633
+ };
24634
+ if ('files' in prompt && Array.isArray(prompt.files) && prompt.files.length > 0) {
24635
+ const fileIds = await uploadFilesToOpenAi(client, prompt.files);
24636
+ currentUserMessage.attachments = fileIds.map((fileId) => ({
24637
+ file_id: fileId,
24638
+ tools: [{ type: 'file_search' }, { type: 'code_interpreter' }],
24639
+ }));
24640
+ }
24641
+ threadMessages.push(currentUserMessage);
24642
+ // Check if tools are being used - if so, use non-streaming mode
24643
+ const hasTools = modelRequirements.tools !== undefined && modelRequirements.tools.length > 0;
24644
+ const start = $getCurrentDate();
24645
+ let complete;
24646
+ // [🐱‍🚀] When tools are present, we need to use the non-streaming Runs API
24647
+ // because streaming doesn't support tool execution flow properly
24648
+ if (hasTools) {
24649
+ onProgress({
24650
+ content: '',
24651
+ modelName: 'assistant',
24652
+ timing: { start, complete: $getCurrentDate() },
24653
+ usage: UNCERTAIN_USAGE,
24654
+ rawPromptContent,
24655
+ rawRequest: null,
24656
+ rawResponse: null,
24657
+ });
24658
+ const rawRequest = {
24659
+ assistant_id: this.assistantId,
24660
+ thread: {
24661
+ messages: threadMessages,
24662
+ },
24663
+ tools: mapToolsToOpenAi(modelRequirements.tools),
24664
+ };
24665
+ if (this.options.isVerbose) {
24666
+ console.info(colors__default["default"].bgWhite('rawRequest (non-streaming with tools)'), JSON.stringify(rawRequest, null, 4));
24667
+ }
24668
+ // Create thread and run
24669
+ let run = (await client.beta.threads.createAndRun(rawRequest));
24670
+ const completedToolCalls = [];
24671
+ const toolCallStartedAt = new Map();
24672
+ // Poll until run completes or requires action
24673
+ while (run.status === 'queued' || run.status === 'in_progress' || run.status === 'requires_action') {
24674
+ if (run.status === 'requires_action' && ((_a = run.required_action) === null || _a === void 0 ? void 0 : _a.type) === 'submit_tool_outputs') {
24675
+ // Execute tools
24676
+ const toolCalls = run.required_action.submit_tool_outputs.tool_calls;
24677
+ const toolOutputs = [];
24678
+ for (const toolCall of toolCalls) {
24679
+ if (toolCall.type === 'function') {
24680
+ const functionName = toolCall.function.name;
24681
+ const functionArgs = JSON.parse(toolCall.function.arguments);
24682
+ const calledAt = $getCurrentDate();
24683
+ if (toolCall.id) {
24684
+ toolCallStartedAt.set(toolCall.id, calledAt);
24685
+ }
24686
+ onProgress({
24687
+ content: '',
24688
+ modelName: 'assistant',
24689
+ timing: { start, complete: $getCurrentDate() },
24690
+ usage: UNCERTAIN_USAGE,
24691
+ rawPromptContent,
24692
+ rawRequest: null,
24693
+ rawResponse: null,
24694
+ toolCalls: [
24695
+ {
24696
+ name: functionName,
24697
+ arguments: toolCall.function.arguments,
24698
+ result: '',
24699
+ rawToolCall: toolCall,
24700
+ createdAt: calledAt,
24701
+ },
24702
+ ],
24703
+ });
24704
+ if (this.options.isVerbose) {
24705
+ console.info(`🔧 Executing tool: ${functionName}`, functionArgs);
24706
+ }
24707
+ // Get execution tools for script execution
24708
+ const executionTools = this.options
24709
+ .executionTools;
24710
+ if (!executionTools || !executionTools.script) {
24711
+ throw new PipelineExecutionError(`Model requested tool '${functionName}' but no executionTools.script were provided in OpenAiAssistantExecutionTools options`);
24712
+ }
24713
+ // TODO: [DRY] Use some common tool caller (similar to OpenAiCompatibleExecutionTools)
24714
+ const scriptTools = Array.isArray(executionTools.script)
24715
+ ? executionTools.script
24716
+ : [executionTools.script];
24717
+ let functionResponse;
24718
+ let errors;
24719
+ try {
24720
+ const scriptTool = scriptTools[0]; // <- TODO: [🧠] Which script tool to use?
24721
+ functionResponse = await scriptTool.execute({
24722
+ scriptLanguage: 'javascript',
24723
+ script: `
24724
+ const args = ${JSON.stringify(functionArgs)};
24725
+ return await ${functionName}(args);
24726
+ `,
24727
+ parameters: prompt.parameters,
24728
+ });
24729
+ if (this.options.isVerbose) {
24730
+ console.info(`✅ Tool ${functionName} executed:`, functionResponse);
24731
+ }
24732
+ }
24733
+ catch (error) {
24734
+ assertsError(error);
24735
+ const serializedError = serializeError(error);
24736
+ errors = [serializedError];
24737
+ functionResponse = spaceTrim__default["default"]((block) => `
24738
+
24739
+ The invoked tool \`${functionName}\` failed with error:
24740
+
24741
+ \`\`\`json
24742
+ ${block(JSON.stringify(serializedError, null, 4))}
24743
+ \`\`\`
24744
+
24745
+ `);
24746
+ console.error(colors__default["default"].bgRed(`❌ Error executing tool ${functionName}:`));
24747
+ console.error(error);
24748
+ }
24749
+ toolOutputs.push({
24750
+ tool_call_id: toolCall.id,
24751
+ output: functionResponse,
24752
+ });
24753
+ completedToolCalls.push({
24754
+ name: functionName,
24755
+ arguments: toolCall.function.arguments,
24756
+ result: functionResponse,
24757
+ rawToolCall: toolCall,
24758
+ createdAt: toolCall.id ? toolCallStartedAt.get(toolCall.id) || calledAt : calledAt,
24759
+ errors,
24760
+ });
24761
+ }
24389
24762
  }
24763
+ // Submit tool outputs
24764
+ run = (await client.beta.threads.runs.submitToolOutputs(run.thread_id, run.id, {
24765
+ tool_outputs: toolOutputs,
24766
+ }));
24390
24767
  }
24391
24768
  else {
24392
- skippedSources.push({ source, reason: 'unsupported_source_type' });
24393
- if (this.options.isVerbose) {
24394
- console.info('[🤰]', 'Skipping knowledge source (unsupported type)', {
24395
- source,
24396
- sourceType,
24397
- logLabel,
24398
- });
24399
- }
24400
- /*
24401
- TODO: [?????] Resolve problem with browser environment
24402
- // Assume it's a local file path
24403
- // Note: This will work in Node.js environment
24404
- // For browser environments, this would need different handling
24405
- const fs = await import('fs');
24406
- const fileStream = fs.createReadStream(source);
24407
- fileStreams.push(fileStream);
24408
- */
24769
+ // Wait a bit before polling again
24770
+ await new Promise((resolve) => setTimeout(resolve, 500));
24771
+ run = (await client.beta.threads.runs.retrieve(run.thread_id, run.id));
24409
24772
  }
24410
24773
  }
24411
- catch (error) {
24412
- assertsError(error);
24413
- skippedSources.push({ source, reason: 'processing_error' });
24414
- console.error('[🤰]', 'Error processing knowledge source', {
24415
- source,
24416
- logLabel,
24417
- error: serializeError(error),
24418
- });
24774
+ if (run.status !== 'completed') {
24775
+ throw new PipelineExecutionError(`Assistant run failed with status: ${run.status}`);
24776
+ }
24777
+ // Get messages from the thread
24778
+ const messages = await client.beta.threads.messages.list(run.thread_id);
24779
+ const assistantMessages = messages.data.filter((msg) => msg.role === 'assistant');
24780
+ if (assistantMessages.length === 0) {
24781
+ throw new PipelineExecutionError('No assistant messages found after run completion');
24782
+ }
24783
+ const lastMessage = assistantMessages[0];
24784
+ const textContent = lastMessage.content.find((c) => c.type === 'text');
24785
+ if (!textContent || textContent.type !== 'text') {
24786
+ throw new PipelineExecutionError('No text content in assistant response');
24419
24787
  }
24788
+ complete = $getCurrentDate();
24789
+ const resultContent = textContent.text.value;
24790
+ const usage = UNCERTAIN_USAGE;
24791
+ // Progress callback with final result
24792
+ const finalChunk = {
24793
+ content: resultContent,
24794
+ modelName: 'assistant',
24795
+ timing: { start, complete },
24796
+ usage,
24797
+ rawPromptContent,
24798
+ rawRequest,
24799
+ rawResponse: { run, messages: messages.data },
24800
+ toolCalls: completedToolCalls.length > 0 ? completedToolCalls : undefined,
24801
+ };
24802
+ onProgress(finalChunk);
24803
+ return exportJson({
24804
+ name: 'promptResult',
24805
+ message: `Result of \`OpenAiAssistantExecutionTools.callChatModelStream\` (with tools)`,
24806
+ order: [],
24807
+ value: finalChunk,
24808
+ });
24420
24809
  }
24810
+ // Streaming mode (without tools)
24811
+ const rawRequest = {
24812
+ // TODO: [👨‍👨‍👧‍👧] ...modelSettings,
24813
+ // TODO: [👨‍👨‍👧‍👧][🧠] What about system message for assistants, does it make sense - combination of OpenAI assistants with Promptbook Personas
24814
+ assistant_id: this.assistantId,
24815
+ thread: {
24816
+ messages: threadMessages,
24817
+ },
24818
+ tools: modelRequirements.tools === undefined ? undefined : mapToolsToOpenAi(modelRequirements.tools),
24819
+ // <- TODO: Add user identification here> user: this.options.user,
24820
+ };
24421
24821
  if (this.options.isVerbose) {
24422
- console.info('[🤰]', 'Finished processing knowledge sources', {
24423
- total: knowledgeSourcesCount,
24424
- downloadedCount: fileStreams.length,
24425
- skippedCount: skippedSources.length,
24426
- totalBytes,
24427
- elapsedMs: Date.now() - processingStartedAtMs,
24428
- skippedSamples: skippedSources.slice(0, 3),
24429
- logLabel,
24430
- });
24822
+ console.info(colors__default["default"].bgWhite('rawRequest (streaming)'), JSON.stringify(rawRequest, null, 4));
24431
24823
  }
24432
- if (fileStreams.length > 0) {
24824
+ const stream = await client.beta.threads.createAndRunStream(rawRequest);
24825
+ stream.on('connect', () => {
24433
24826
  if (this.options.isVerbose) {
24434
- console.info('[🤰]', 'Uploading files to vector store', {
24435
- vectorStoreId,
24436
- fileCount: fileStreams.length,
24437
- totalBytes,
24438
- maxConcurrency: this.getKnowledgeSourceUploadMaxConcurrency(),
24439
- pollIntervalMs: this.getKnowledgeSourceUploadPollIntervalMs(),
24440
- uploadTimeoutMs: this.getKnowledgeSourceUploadTimeoutMs(),
24441
- logLabel,
24442
- });
24827
+ console.info('connect', stream.currentEvent);
24443
24828
  }
24444
- try {
24445
- await this.uploadKnowledgeSourceFilesToVectorStore({
24446
- client,
24447
- vectorStoreId,
24448
- files: fileStreams,
24449
- totalBytes,
24450
- logLabel,
24451
- });
24829
+ });
24830
+ stream.on('textDelta', (textDelta, snapshot) => {
24831
+ if (this.options.isVerbose && textDelta.value) {
24832
+ console.info('textDelta', textDelta.value);
24452
24833
  }
24453
- catch (error) {
24454
- assertsError(error);
24455
- console.error('[🤰]', 'Error uploading files to vector store', {
24456
- vectorStoreId,
24457
- logLabel,
24458
- error: serializeError(error),
24459
- });
24834
+ const chunk = {
24835
+ content: snapshot.value,
24836
+ modelName: 'assistant',
24837
+ timing: {
24838
+ start,
24839
+ complete: $getCurrentDate(),
24840
+ },
24841
+ usage: UNCERTAIN_USAGE,
24842
+ rawPromptContent,
24843
+ rawRequest,
24844
+ rawResponse: snapshot,
24845
+ };
24846
+ onProgress(chunk);
24847
+ });
24848
+ stream.on('messageCreated', (message) => {
24849
+ if (this.options.isVerbose) {
24850
+ console.info('messageCreated', message);
24851
+ }
24852
+ });
24853
+ stream.on('messageDone', (message) => {
24854
+ if (this.options.isVerbose) {
24855
+ console.info('messageDone', message);
24460
24856
  }
24857
+ });
24858
+ // TODO: [🐱‍🚀] Handle tool calls in assistants
24859
+ // Note: OpenAI Assistant streaming with tool calls requires special handling.
24860
+ // The stream will pause when a tool call is needed, and we need to:
24861
+ // 1. Wait for the run to reach 'requires_action' status
24862
+ // 2. Execute the tool calls
24863
+ // 3. Submit tool outputs via a separate API call (not on the stream)
24864
+ // 4. Continue the run
24865
+ // This requires switching to non-streaming mode or using the Runs API directly.
24866
+ // For now, tools with assistants should use the non-streaming chat completions API instead.
24867
+ const rawResponse = await stream.finalMessages();
24868
+ if (this.options.isVerbose) {
24869
+ console.info(colors__default["default"].bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
24461
24870
  }
24462
- else if (this.options.isVerbose) {
24463
- console.info('[🤰]', 'No knowledge source files to upload', {
24464
- vectorStoreId,
24465
- skippedCount: skippedSources.length,
24466
- logLabel,
24467
- });
24871
+ if (rawResponse.length !== 1) {
24872
+ throw new PipelineExecutionError(`There is NOT 1 BUT ${rawResponse.length} finalMessages from OpenAI`);
24468
24873
  }
24469
- return {
24470
- vectorStoreId,
24471
- uploadedFileCount: fileStreams.length,
24472
- skippedCount: skippedSources.length,
24473
- totalBytes,
24474
- };
24874
+ if (rawResponse[0].content.length !== 1) {
24875
+ throw new PipelineExecutionError(`There is NOT 1 BUT ${rawResponse[0].content.length} finalMessages content from OpenAI`);
24876
+ }
24877
+ if (((_b = rawResponse[0].content[0]) === null || _b === void 0 ? void 0 : _b.type) !== 'text') {
24878
+ throw new PipelineExecutionError(`There is NOT 'text' BUT ${(_c = rawResponse[0].content[0]) === null || _c === void 0 ? void 0 : _c.type} finalMessages content type from OpenAI`);
24879
+ }
24880
+ let resultContent = (_d = rawResponse[0].content[0]) === null || _d === void 0 ? void 0 : _d.text.value;
24881
+ // Process annotations to replace file IDs with filenames
24882
+ if ((_e = rawResponse[0].content[0]) === null || _e === void 0 ? void 0 : _e.text.annotations) {
24883
+ const annotations = (_f = rawResponse[0].content[0]) === null || _f === void 0 ? void 0 : _f.text.annotations;
24884
+ // Map to store file ID -> filename to avoid duplicate requests
24885
+ const fileIdToName = new Map();
24886
+ for (const annotation of annotations) {
24887
+ if (annotation.type === 'file_citation') {
24888
+ const fileId = annotation.file_citation.file_id;
24889
+ let filename = fileIdToName.get(fileId);
24890
+ if (!filename) {
24891
+ try {
24892
+ const file = await client.files.retrieve(fileId);
24893
+ filename = file.filename;
24894
+ fileIdToName.set(fileId, filename);
24895
+ }
24896
+ catch (error) {
24897
+ console.error(`Failed to retrieve file info for ${fileId}`, error);
24898
+ // Fallback to "Source" or keep original if fetch fails
24899
+ filename = 'Source';
24900
+ }
24901
+ }
24902
+ if (filename && resultContent) {
24903
+ // Replace the citation marker with filename
24904
+ // Regex to match the second part of the citation: 【id†source】 -> 【id†filename】
24905
+ // Note: annotation.text contains the exact marker like 【4:0†source】
24906
+ const newText = annotation.text.replace(/†.*?】/, `†${filename}】`);
24907
+ resultContent = resultContent.replace(annotation.text, newText);
24908
+ }
24909
+ }
24910
+ }
24911
+ }
24912
+ // eslint-disable-next-line prefer-const
24913
+ complete = $getCurrentDate();
24914
+ const usage = UNCERTAIN_USAGE;
24915
+ // <- TODO: [🥘] Compute real usage for assistant
24916
+ // ?> const usage = computeOpenAiUsage(content, resultContent || '', rawResponse);
24917
+ if (resultContent === null) {
24918
+ throw new PipelineExecutionError('No response message from OpenAI');
24919
+ }
24920
+ return exportJson({
24921
+ name: 'promptResult',
24922
+ message: `Result of \`OpenAiAssistantExecutionTools.callChatModelStream\``,
24923
+ order: [],
24924
+ value: {
24925
+ content: resultContent,
24926
+ modelName: 'assistant',
24927
+ // <- TODO: [🥘] Detect used model in assistant
24928
+ // ?> model: rawResponse.model || modelName,
24929
+ timing: {
24930
+ start,
24931
+ complete,
24932
+ },
24933
+ usage,
24934
+ rawPromptContent,
24935
+ rawRequest,
24936
+ rawResponse,
24937
+ // <- [🗯]
24938
+ },
24939
+ });
24940
+ }
24941
+ /*
24942
+ public async playground() {
24943
+ const client = await this.getClient();
24944
+
24945
+ // List all assistants
24946
+ const assistants = await client.beta.assistants.list();
24947
+
24948
+ // Get details of a specific assistant
24949
+ const assistantId = 'asst_MO8fhZf4dGloCfXSHeLcIik0';
24950
+ const assistant = await client.beta.assistants.retrieve(assistantId);
24951
+
24952
+ // Update an assistant
24953
+ const updatedAssistant = await client.beta.assistants.update(assistantId, {
24954
+ name: assistant.name + '(M)',
24955
+ description: 'Updated description via Promptbook',
24956
+ metadata: {
24957
+ [Math.random().toString(36).substring(2, 15)]: new Date().toISOString(),
24958
+ },
24959
+ });
24960
+
24961
+ await forEver();
24962
+ }
24963
+ */
24964
+ /**
24965
+ * Get an existing assistant tool wrapper
24966
+ */
24967
+ getAssistant(assistantId) {
24968
+ return new OpenAiAssistantExecutionTools({
24969
+ ...this.options,
24970
+ isCreatingNewAssistantsAllowed: this.isCreatingNewAssistantsAllowed,
24971
+ assistantId,
24972
+ });
24475
24973
  }
24476
24974
  async createNewAssistant(options) {
24477
24975
  var _a, _b, _c;
@@ -24680,6 +25178,7 @@
24680
25178
  * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
24681
25179
  * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
24682
25180
  * - `OpenAiAssistantExecutionTools` - (Deprecated) which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities
25181
+ * - `OpenAiAgentKitExecutionTools` - which is a specific implementation of `LlmExecutionTools` backed by OpenAI AgentKit
24683
25182
  * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
24684
25183
  *
24685
25184
  * @public exported from `@promptbook/core`
@@ -24814,6 +25313,7 @@
24814
25313
  * Calls the chat model with agent-specific system prompt and requirements with streaming
24815
25314
  */
24816
25315
  async callChatModelStream(prompt, onProgress) {
25316
+ var _a, _b;
24817
25317
  // Ensure we're working with a chat prompt
24818
25318
  if (prompt.modelRequirements.modelVariant !== 'CHAT') {
24819
25319
  throw new Error('AgentLlmExecutionTools only supports chat prompts');
@@ -24841,7 +25341,75 @@
24841
25341
  }, // Cast to avoid readonly mismatch from spread
24842
25342
  };
24843
25343
  console.log('!!!! promptWithAgentModelRequirements:', promptWithAgentModelRequirements);
24844
- if (OpenAiAssistantExecutionTools.isOpenAiAssistantExecutionTools(this.options.llmTools)) {
25344
+ if (OpenAiAgentKitExecutionTools.isOpenAiAgentKitExecutionTools(this.options.llmTools)) {
25345
+ const requirementsHash = cryptoJs.SHA256(JSON.stringify(modelRequirements)).toString();
25346
+ const vectorStoreHash = cryptoJs.SHA256(JSON.stringify((_a = modelRequirements.knowledgeSources) !== null && _a !== void 0 ? _a : [])).toString();
25347
+ const cachedVectorStore = AgentLlmExecutionTools.vectorStoreCache.get(this.title);
25348
+ const cachedAgentKit = AgentLlmExecutionTools.agentKitAgentCache.get(this.title);
25349
+ let preparedAgentKit = this.options.assistantPreparationMode === 'external'
25350
+ ? this.options.llmTools.getPreparedAgentKitAgent()
25351
+ : null;
25352
+ const vectorStoreId = (preparedAgentKit === null || preparedAgentKit === void 0 ? void 0 : preparedAgentKit.vectorStoreId) ||
25353
+ (cachedVectorStore && cachedVectorStore.requirementsHash === vectorStoreHash
25354
+ ? cachedVectorStore.vectorStoreId
25355
+ : undefined);
25356
+ if (!preparedAgentKit && cachedAgentKit && cachedAgentKit.requirementsHash === requirementsHash) {
25357
+ if (this.options.isVerbose) {
25358
+ console.info('[🤰]', 'Using cached OpenAI AgentKit agent', {
25359
+ agent: this.title,
25360
+ });
25361
+ }
25362
+ preparedAgentKit = {
25363
+ agent: cachedAgentKit.agent,
25364
+ vectorStoreId: cachedAgentKit.vectorStoreId,
25365
+ };
25366
+ }
25367
+ if (!preparedAgentKit) {
25368
+ if (this.options.isVerbose) {
25369
+ console.info('[🤰]', 'Preparing OpenAI AgentKit agent', {
25370
+ agent: this.title,
25371
+ });
25372
+ }
25373
+ if (!vectorStoreId && ((_b = modelRequirements.knowledgeSources) === null || _b === void 0 ? void 0 : _b.length)) {
25374
+ emitAssistantPreparationProgress({
25375
+ onProgress,
25376
+ prompt,
25377
+ modelName: this.modelName,
25378
+ phase: 'Creating knowledge base',
25379
+ });
25380
+ }
25381
+ emitAssistantPreparationProgress({
25382
+ onProgress,
25383
+ prompt,
25384
+ modelName: this.modelName,
25385
+ phase: 'Preparing AgentKit agent',
25386
+ });
25387
+ preparedAgentKit = await this.options.llmTools.prepareAgentKitAgent({
25388
+ name: this.title,
25389
+ instructions: modelRequirements.systemMessage || '',
25390
+ knowledgeSources: modelRequirements.knowledgeSources,
25391
+ tools: modelRequirements.tools ? [...modelRequirements.tools] : undefined,
25392
+ vectorStoreId,
25393
+ });
25394
+ }
25395
+ if (preparedAgentKit.vectorStoreId) {
25396
+ AgentLlmExecutionTools.vectorStoreCache.set(this.title, {
25397
+ vectorStoreId: preparedAgentKit.vectorStoreId,
25398
+ requirementsHash: vectorStoreHash,
25399
+ });
25400
+ }
25401
+ AgentLlmExecutionTools.agentKitAgentCache.set(this.title, {
25402
+ agent: preparedAgentKit.agent,
25403
+ requirementsHash,
25404
+ vectorStoreId: preparedAgentKit.vectorStoreId,
25405
+ });
25406
+ underlyingLlmResult = await this.options.llmTools.callChatModelStreamWithPreparedAgent({
25407
+ openAiAgentKitAgent: preparedAgentKit.agent,
25408
+ prompt: promptWithAgentModelRequirements,
25409
+ onProgress,
25410
+ });
25411
+ }
25412
+ else if (OpenAiAssistantExecutionTools.isOpenAiAssistantExecutionTools(this.options.llmTools)) {
24845
25413
  // ... deprecated path ...
24846
25414
  const requirementsHash = cryptoJs.SHA256(JSON.stringify(modelRequirements)).toString();
24847
25415
  const cached = AgentLlmExecutionTools.assistantCache.get(this.title);
@@ -24966,6 +25534,10 @@
24966
25534
  return agentResult;
24967
25535
  }
24968
25536
  }
25537
+ /**
25538
+ * Cached AgentKit agents to avoid rebuilding identical instances.
25539
+ */
25540
+ AgentLlmExecutionTools.agentKitAgentCache = new Map();
24969
25541
  /**
24970
25542
  * Cache of OpenAI assistants to avoid creating duplicates
24971
25543
  */
@@ -25047,6 +25619,7 @@
25047
25619
  * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
25048
25620
  * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
25049
25621
  * - `OpenAiAssistantExecutionTools` - (Deprecated) which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities
25622
+ * - `OpenAiAgentKitExecutionTools` - which is a specific implementation of `LlmExecutionTools` backed by OpenAI AgentKit
25050
25623
  * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
25051
25624
  *
25052
25625
  * @public exported from `@promptbook/core`
@@ -25417,7 +25990,8 @@
25417
25990
  * - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
25418
25991
  * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
25419
25992
  * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
25420
- * - `OpenAiAssistantExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities, recommended for usage in `Agent` or `AgentLlmExecutionTools`
25993
+ * - `OpenAiAssistantExecutionTools` - (Deprecated) which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities
25994
+ * - `OpenAiAgentKitExecutionTools` - which is a specific implementation of `LlmExecutionTools` backed by OpenAI AgentKit
25421
25995
  * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
25422
25996
  *
25423
25997
  * @public exported from `@promptbook/core`
@@ -25581,26 +26155,7 @@
25581
26155
  };
25582
26156
  };
25583
26157
  const getToolCallKey = (toolCall) => {
25584
- var _a;
25585
- const rawId = (_a = toolCall.rawToolCall) === null || _a === void 0 ? void 0 : _a.id;
25586
- if (rawId) {
25587
- return `id:${rawId}`;
25588
- }
25589
- const argsKey = (() => {
25590
- if (typeof toolCall.arguments === 'string') {
25591
- return toolCall.arguments;
25592
- }
25593
- if (!toolCall.arguments) {
25594
- return '';
25595
- }
25596
- try {
25597
- return JSON.stringify(toolCall.arguments);
25598
- }
25599
- catch (_a) {
25600
- return '';
25601
- }
25602
- })();
25603
- return `${toolCall.name}:${toolCall.createdAt || ''}:${argsKey}`;
26158
+ return getToolCallIdentity(toolCall);
25604
26159
  };
25605
26160
  const mergeToolCall = (existing, incoming) => {
25606
26161
  const incomingResult = incoming.result;