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