quidproquo-actionprocessor-awslambda 0.1.1 → 0.1.2

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 (48) hide show
  1. package/lib/commonjs/getActionProcessor/core/ai/getAiPromptActionProcessor.js +11 -4
  2. package/lib/commonjs/getActionProcessor/core/ai/getAiPromptStreamActionProcessor.js +11 -4
  3. package/lib/commonjs/getActionProcessor/core/ai/logic/aiStreamPartMappers.js +8 -8
  4. package/lib/commonjs/getActionProcessor/core/ai/logic/createDriveFileResolver.d.ts +3 -0
  5. package/lib/commonjs/getActionProcessor/core/ai/logic/createDriveFileResolver.js +27 -0
  6. package/lib/commonjs/getActionProcessor/core/ai/logic/index.d.ts +1 -0
  7. package/lib/commonjs/getActionProcessor/core/ai/logic/index.js +1 -0
  8. package/lib/commonjs/getActionProcessor/core/ai/logic/toSdkMessages.d.ts +3 -2
  9. package/lib/commonjs/getActionProcessor/core/ai/logic/toSdkMessages.js +41 -24
  10. package/lib/commonjs/getActionProcessor/core/eventBus/getEventBusSendMessagesActionProcessor.js +1 -1
  11. package/lib/commonjs/getActionProcessor/core/file/getFileStreamOpenActionProcessor.js +1 -3
  12. package/lib/commonjs/getActionProcessor/core/keyValueStore/getKeyValueStoreUpsertActionProcessor.js +2 -0
  13. package/lib/commonjs/lambdaHandlers/helpers/logger.js +1 -3
  14. package/lib/commonjs/logic/cognito/utils/transformCognitoResponse.js +1 -1
  15. package/lib/commonjs/logic/dynamo/putItem.d.ts +1 -0
  16. package/lib/commonjs/logic/dynamo/putItem.js +6 -4
  17. package/lib/commonjs/logic/dynamo/qpqDynamoOrm/buildDynamoUpdate.js +1 -5
  18. package/lib/commonjs/logic/s3/generatePresignedUploadUrl.js +4 -4
  19. package/lib/esm/getActionProcessor/core/ai/getAiPromptActionProcessor.js +11 -4
  20. package/lib/esm/getActionProcessor/core/ai/getAiPromptStreamActionProcessor.js +11 -4
  21. package/lib/esm/getActionProcessor/core/ai/logic/aiStreamPartMappers.js +8 -8
  22. package/lib/esm/getActionProcessor/core/ai/logic/createDriveFileResolver.d.ts +3 -0
  23. package/lib/esm/getActionProcessor/core/ai/logic/createDriveFileResolver.js +14 -0
  24. package/lib/esm/getActionProcessor/core/ai/logic/index.d.ts +1 -0
  25. package/lib/esm/getActionProcessor/core/ai/logic/index.js +1 -0
  26. package/lib/esm/getActionProcessor/core/ai/logic/toSdkMessages.d.ts +3 -2
  27. package/lib/esm/getActionProcessor/core/ai/logic/toSdkMessages.js +27 -20
  28. package/lib/esm/getActionProcessor/core/apiKeyValidation/getApiKeyValidationValidateActionProcessor.js +1 -1
  29. package/lib/esm/getActionProcessor/core/eventBus/getEventBusSendMessagesActionProcessor.js +1 -1
  30. package/lib/esm/getActionProcessor/core/file/getFileDeleteActionProcessor.js +1 -1
  31. package/lib/esm/getActionProcessor/core/file/getFileGenerateTemporarySecureUrlActionProcessor.js +1 -1
  32. package/lib/esm/getActionProcessor/core/file/getFileGenerateTemporaryUploadSecureUrlActionProcessor.js +1 -1
  33. package/lib/esm/getActionProcessor/core/file/getFileStreamOpenActionProcessor.js +1 -3
  34. package/lib/esm/getActionProcessor/core/keyValueStore/getKeyValueStoreDeleteActionProcessor.js +1 -1
  35. package/lib/esm/getActionProcessor/core/keyValueStore/getKeyValueStoreGetActionProcessor.js +1 -1
  36. package/lib/esm/getActionProcessor/core/keyValueStore/getKeyValueStoreGetAllActionProcessor.js +1 -1
  37. package/lib/esm/getActionProcessor/core/keyValueStore/getKeyValueStoreQueryActionProcessor.js +1 -1
  38. package/lib/esm/getActionProcessor/core/keyValueStore/getKeyValueStoreScanActionProcessor.js +1 -1
  39. package/lib/esm/getActionProcessor/core/keyValueStore/getKeyValueStoreUpdateActionProcessor.js +1 -1
  40. package/lib/esm/getActionProcessor/core/keyValueStore/getKeyValueStoreUpsertActionProcessor.js +2 -0
  41. package/lib/esm/getActionProcessor/core/queue/getQueueSendMessageActionProcessor.js +1 -1
  42. package/lib/esm/lambdaHandlers/helpers/logger.js +1 -3
  43. package/lib/esm/logic/cognito/utils/transformCognitoResponse.js +2 -2
  44. package/lib/esm/logic/dynamo/putItem.d.ts +1 -0
  45. package/lib/esm/logic/dynamo/putItem.js +6 -0
  46. package/lib/esm/logic/dynamo/qpqDynamoOrm/buildDynamoUpdate.js +1 -5
  47. package/lib/esm/logic/s3/generatePresignedUploadUrl.js +5 -5
  48. package/package.json +7 -7
@@ -15,15 +15,22 @@ const ai_1 = require("ai");
15
15
  const logic_1 = require("./logic");
16
16
  const getProcessAiPrompt = (qpqConfig) => {
17
17
  return (payload, session, actionProcessorList, logger, updateSession, dynamicModuleLoader, streamRegistry) => __awaiter(void 0, void 0, void 0, function* () {
18
+ var _a;
18
19
  const prepared = (0, logic_1.prepareAiPromptCall)(qpqConfig, payload, session, actionProcessorList, logger, dynamicModuleLoader, streamRegistry);
19
20
  if ('error' in prepared) {
20
21
  return (0, quidproquo_core_1.actionResultError)(prepared.error.type, prepared.error.message);
21
22
  }
22
- const promptOrMessages = payload.messages
23
- ? { messages: (0, logic_1.toSdkMessages)(payload.messages) }
24
- : { prompt: payload.prompt };
25
23
  try {
26
- const result = yield (0, ai_1.generateText)(Object.assign(Object.assign({ model: prepared.model, system: payload.system }, promptOrMessages), { tools: prepared.tools, stopWhen: (0, ai_1.stepCountIs)(10) }));
24
+ const promptOrMessages = payload.messages
25
+ ? {
26
+ messages: yield (0, logic_1.toSdkMessages)(payload.messages, (0, logic_1.createDriveFileResolver)(qpqConfig, session, actionProcessorList, logger, dynamicModuleLoader, streamRegistry)),
27
+ }
28
+ : { prompt: payload.prompt };
29
+ // Extended thinking — the model reasons before answering, within the token budget.
30
+ const providerOptions = payload.reasoning
31
+ ? { bedrock: { reasoningConfig: { type: 'enabled', budgetTokens: (_a = payload.reasoning.budgetTokens) !== null && _a !== void 0 ? _a : 4096 } } }
32
+ : undefined;
33
+ const result = yield (0, ai_1.generateText)(Object.assign(Object.assign({ model: prepared.model, system: payload.system }, promptOrMessages), { tools: prepared.tools, providerOptions, stopWhen: (0, ai_1.stepCountIs)(10) }));
27
34
  return (0, quidproquo_core_1.actionResult)({ text: result.text });
28
35
  }
29
36
  catch (error) {
@@ -36,15 +36,22 @@ const awsLambdaUtils_1 = require("../../../awsLambdaUtils");
36
36
  const logic_1 = require("./logic");
37
37
  const getProcessAiPromptStream = (qpqConfig) => {
38
38
  return (payload, session, actionProcessorList, logger, updateSession, dynamicModuleLoader, streamRegistry) => __awaiter(void 0, void 0, void 0, function* () {
39
+ var _a;
39
40
  const prepared = (0, logic_1.prepareAiPromptCall)(qpqConfig, payload, session, actionProcessorList, logger, dynamicModuleLoader, streamRegistry);
40
41
  if ('error' in prepared) {
41
42
  return (0, quidproquo_core_1.actionResultError)(prepared.error.type, prepared.error.message);
42
43
  }
43
- const promptOrMessages = payload.messages
44
- ? { messages: (0, logic_1.toSdkMessages)(payload.messages) }
45
- : { prompt: payload.prompt };
46
44
  try {
47
- const { fullStream } = (0, ai_1.streamText)(Object.assign(Object.assign({ model: prepared.model, system: payload.system }, promptOrMessages), { tools: prepared.tools, stopWhen: (0, ai_1.stepCountIs)(10),
45
+ const promptOrMessages = payload.messages
46
+ ? {
47
+ messages: yield (0, logic_1.toSdkMessages)(payload.messages, (0, logic_1.createDriveFileResolver)(qpqConfig, session, actionProcessorList, logger, dynamicModuleLoader, streamRegistry)),
48
+ }
49
+ : { prompt: payload.prompt };
50
+ // Extended thinking — thinking progress streams out as Reasoning* parts.
51
+ const providerOptions = payload.reasoning
52
+ ? { bedrock: { reasoningConfig: { type: 'enabled', budgetTokens: (_a = payload.reasoning.budgetTokens) !== null && _a !== void 0 ? _a : 4096 } } }
53
+ : undefined;
54
+ const { fullStream } = (0, ai_1.streamText)(Object.assign(Object.assign({ model: prepared.model, system: payload.system }, promptOrMessages), { tools: prepared.tools, providerOptions, stopWhen: (0, ai_1.stepCountIs)(10),
48
55
  // streamText swallows errors by default to keep the server alive — surface them to CloudWatch.
49
56
  onError: ({ error }) => {
50
57
  // todo yeild this out
@@ -28,17 +28,17 @@ const mapAiStreamToolInputStart_1 = require("./mapAiStreamToolInputStart");
28
28
  const mapAiStreamToolOutputDenied_1 = require("./mapAiStreamToolOutputDenied");
29
29
  const mapAiStreamToolResult_1 = require("./mapAiStreamToolResult");
30
30
  exports.aiStreamPartMappers = {
31
- 'start': mapAiStreamStart_1.mapAiStreamStart,
32
- 'finish': mapAiStreamFinish_1.mapAiStreamFinish,
31
+ start: mapAiStreamStart_1.mapAiStreamStart,
32
+ finish: mapAiStreamFinish_1.mapAiStreamFinish,
33
33
  'start-step': mapAiStreamStartStep_1.mapAiStreamStartStep,
34
34
  'finish-step': mapAiStreamFinishStep_1.mapAiStreamFinishStep,
35
- 'abort': mapAiStreamAbort_1.mapAiStreamAbort,
36
- 'error': mapAiStreamError_1.mapAiStreamError,
37
- 'raw': mapAiStreamRaw_1.mapAiStreamRaw,
38
- 'source': mapAiStreamSourcePart_1.mapAiStreamSourcePart,
39
- 'file': mapAiStreamFilePart_1.mapAiStreamFilePart,
35
+ abort: mapAiStreamAbort_1.mapAiStreamAbort,
36
+ error: mapAiStreamError_1.mapAiStreamError,
37
+ raw: mapAiStreamRaw_1.mapAiStreamRaw,
38
+ source: mapAiStreamSourcePart_1.mapAiStreamSourcePart,
39
+ file: mapAiStreamFilePart_1.mapAiStreamFilePart,
40
40
  'reasoning-file': mapAiStreamReasoningFilePart_1.mapAiStreamReasoningFilePart,
41
- 'custom': mapAiStreamCustom_1.mapAiStreamCustom,
41
+ custom: mapAiStreamCustom_1.mapAiStreamCustom,
42
42
  'text-start': mapAiStreamTextStart_1.mapAiStreamTextStart,
43
43
  'text-end': mapAiStreamTextEnd_1.mapAiStreamTextEnd,
44
44
  'text-delta': mapAiStreamTextDelta_1.mapAiStreamTextDelta,
@@ -0,0 +1,3 @@
1
+ import { ActionProcessorList, DynamicModuleLoader, QPQConfig, QpqLogger, StorySession, StreamRegistry } from 'quidproquo-core';
2
+ import { AiDriveFileResolver } from './toSdkMessages';
3
+ export declare const createDriveFileResolver: (qpqConfig: QPQConfig, session: StorySession, actionProcessorList: ActionProcessorList, logger: QpqLogger, dynamicModuleLoader: DynamicModuleLoader, streamRegistry?: StreamRegistry) => AiDriveFileResolver;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.createDriveFileResolver = void 0;
13
+ const quidproquo_core_1 = require("quidproquo-core");
14
+ const awsLambdaUtils_1 = require("../../../../awsLambdaUtils");
15
+ // Reads drive-referenced files through the active file action processor, so the
16
+ // same message works against S3 in production and local disk under the dev server.
17
+ const createDriveFileResolver = (qpqConfig, session, actionProcessorList, logger, dynamicModuleLoader, streamRegistry) => {
18
+ return (drive, filepath) => __awaiter(void 0, void 0, void 0, function* () {
19
+ const resolveStory = (0, quidproquo_core_1.createImplementationRuntime)(qpqConfig, [`AI File: ${drive}/${filepath}`], () => new Date().toISOString(), awsLambdaUtils_1.randomGuid, session, actionProcessorList, logger, dynamicModuleLoader, streamRegistry);
20
+ const storyResult = yield resolveStory(quidproquo_core_1.askFileReadBinaryContents, [drive, filepath]);
21
+ if (storyResult.error) {
22
+ throw new Error(storyResult.error.errorText);
23
+ }
24
+ return storyResult.result;
25
+ });
26
+ };
27
+ exports.createDriveFileResolver = createDriveFileResolver;
@@ -1,4 +1,5 @@
1
1
  export * from './aiStreamPartMappers';
2
+ export * from './createDriveFileResolver';
2
3
  export * from './mapAiStreamAbort';
3
4
  export * from './mapAiStreamCustom';
4
5
  export * from './mapAiStreamError';
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./aiStreamPartMappers"), exports);
18
+ __exportStar(require("./createDriveFileResolver"), exports);
18
19
  __exportStar(require("./mapAiStreamAbort"), exports);
19
20
  __exportStar(require("./mapAiStreamCustom"), exports);
20
21
  __exportStar(require("./mapAiStreamError"), exports);
@@ -1,3 +1,4 @@
1
- import { AiMessage } from 'quidproquo-core';
1
+ import { AiMessage, QPQBinaryData } from 'quidproquo-core';
2
2
  import type { ModelMessage } from 'ai';
3
- export declare const toSdkMessages: (messages: AiMessage[]) => ModelMessage[];
3
+ export type AiDriveFileResolver = (drive: string, filepath: string) => Promise<QPQBinaryData>;
4
+ export declare const toSdkMessages: (messages: AiMessage[], resolveDriveFile: AiDriveFileResolver) => Promise<ModelMessage[]>;
@@ -1,28 +1,45 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  Object.defineProperty(exports, "__esModule", { value: true });
3
12
  exports.toSdkMessages = void 0;
4
- const mapUserPart = (part) => {
5
- if (part.type === 'text') {
6
- return { type: 'text', text: part.text };
13
+ const mapFilePart = (part, resolveDriveFile) => __awaiter(void 0, void 0, void 0, function* () {
14
+ var _a;
15
+ if ('url' in part) {
16
+ return {
17
+ type: 'file',
18
+ data: new URL(part.url),
19
+ mediaType: part.mediaType,
20
+ filename: part.filename,
21
+ };
7
22
  }
23
+ const binary = yield resolveDriveFile(part.drive, part.filepath);
8
24
  return {
9
25
  type: 'file',
10
- data: new URL(part.url),
26
+ data: binary.base64Data,
11
27
  mediaType: part.mediaType,
12
- filename: part.filename,
28
+ filename: (_a = part.filename) !== null && _a !== void 0 ? _a : binary.filename,
13
29
  };
14
- };
15
- const mapAssistantPart = (part) => {
30
+ });
31
+ const mapUserPart = (part, resolveDriveFile) => __awaiter(void 0, void 0, void 0, function* () {
32
+ if (part.type === 'text') {
33
+ return { type: 'text', text: part.text };
34
+ }
35
+ return mapFilePart(part, resolveDriveFile);
36
+ });
37
+ const mapAssistantPart = (part, resolveDriveFile) => __awaiter(void 0, void 0, void 0, function* () {
16
38
  switch (part.type) {
17
39
  case 'text':
18
40
  return { type: 'text', text: part.text };
19
41
  case 'file':
20
- return {
21
- type: 'file',
22
- data: new URL(part.url),
23
- mediaType: part.mediaType,
24
- filename: part.filename,
25
- };
42
+ return mapFilePart(part, resolveDriveFile);
26
43
  case 'tool-call':
27
44
  return {
28
45
  type: 'tool-call',
@@ -37,7 +54,7 @@ const mapAssistantPart = (part) => {
37
54
  providerOptions: part.providerOptions,
38
55
  };
39
56
  }
40
- };
57
+ });
41
58
  const mapToolResult = (part) => {
42
59
  const output = part.isError
43
60
  ? typeof part.output === 'string'
@@ -53,30 +70,30 @@ const mapToolResult = (part) => {
53
70
  output,
54
71
  };
55
72
  };
56
- const mapUserMessage = (message) => {
73
+ const mapUserMessage = (message, resolveDriveFile) => __awaiter(void 0, void 0, void 0, function* () {
57
74
  if (typeof message.content === 'string') {
58
75
  return { role: 'user', content: message.content };
59
76
  }
60
- return { role: 'user', content: message.content.map(mapUserPart) };
61
- };
62
- const mapAssistantMessage = (message) => {
77
+ return { role: 'user', content: yield Promise.all(message.content.map((part) => mapUserPart(part, resolveDriveFile))) };
78
+ });
79
+ const mapAssistantMessage = (message, resolveDriveFile) => __awaiter(void 0, void 0, void 0, function* () {
63
80
  if (typeof message.content === 'string') {
64
81
  return { role: 'assistant', content: message.content };
65
82
  }
66
- return { role: 'assistant', content: message.content.map(mapAssistantPart) };
67
- };
83
+ return { role: 'assistant', content: yield Promise.all(message.content.map((part) => mapAssistantPart(part, resolveDriveFile))) };
84
+ });
68
85
  const mapToolMessage = (message) => ({
69
86
  role: 'tool',
70
87
  content: message.content.map(mapToolResult),
71
88
  });
72
- const toSdkMessages = (messages) => messages.map((message) => {
89
+ const toSdkMessages = (messages, resolveDriveFile) => Promise.all(messages.map((message) => {
73
90
  switch (message.role) {
74
91
  case 'user':
75
- return mapUserMessage(message);
92
+ return mapUserMessage(message, resolveDriveFile);
76
93
  case 'assistant':
77
- return mapAssistantMessage(message);
94
+ return mapAssistantMessage(message, resolveDriveFile);
78
95
  case 'tool':
79
96
  return mapToolMessage(message);
80
97
  }
81
- });
98
+ }));
82
99
  exports.toSdkMessages = toSdkMessages;
@@ -15,7 +15,7 @@ const quidproquo_core_1 = require("quidproquo-core");
15
15
  const awsNamingUtils_1 = require("../../../awsNamingUtils");
16
16
  const publishMessage_1 = require("../../../logic/sns/publishMessage");
17
17
  const getProcessEventBusSendMessage = (qpqConfig) => {
18
- return (_a, session_1) => __awaiter(void 0, [_a, session_1], void 0, function* ({ eventBusName, eventBusMessages, }, session) {
18
+ return (_a, session_1) => __awaiter(void 0, [_a, session_1], void 0, function* ({ eventBusName, eventBusMessages }, session) {
19
19
  var _b, _c, _d, _e, _f;
20
20
  const region = quidproquo_config_aws_1.qpqConfigAwsUtils.getApplicationModuleDeployRegion(qpqConfig);
21
21
  const eventBusConfig = quidproquo_core_1.qpqCoreUtils.getEventBusConfigByName(eventBusName, qpqConfig);
@@ -81,9 +81,7 @@ const getProcessFileStreamOpen = (qpqConfig) => {
81
81
  const streamId = `s3-${(0, awsLambdaUtils_1.randomGuid)()}`;
82
82
  const isText = encoding === 'text';
83
83
  const size = chunkSize !== null && chunkSize !== void 0 ? chunkSize : 65536;
84
- const transform = isText
85
- ? (buf) => buf.toString('utf8')
86
- : (buf) => buf.toString('base64');
84
+ const transform = isText ? (buf) => buf.toString('utf8') : (buf) => buf.toString('base64');
87
85
  const iterator = chunkedReadableIterator(response.Body, size, transform);
88
86
  streamRegistry.register(streamId, iterator);
89
87
  return (0, quidproquo_core_1.actionResult)({ id: streamId, encoding });
@@ -32,6 +32,7 @@ const getProcessKeyValueStoreUpsert = (qpqConfig) => {
32
32
  try {
33
33
  yield (0, dynamo_1.putItem)(dynamoTableName, item, keys, {
34
34
  expires: options === null || options === void 0 ? void 0 : options.ttlInSeconds,
35
+ ifNotExistsAttribute: (options === null || options === void 0 ? void 0 : options.ifNotExists) ? storeConfig.partitionKey.key : undefined,
35
36
  }, region);
36
37
  return (0, quidproquo_core_1.actionResult)(void 0);
37
38
  }
@@ -39,6 +40,7 @@ const getProcessKeyValueStoreUpsert = (qpqConfig) => {
39
40
  return (0, quidproquo_core_1.actionResultErrorFromCaughtError)(error, {
40
41
  InternalServerError: () => (0, quidproquo_core_1.actionResultError)(quidproquo_core_2.KeyValueStoreUpsertErrorTypeEnum.ServiceUnavailable, 'KVS Service Unavailable'),
41
42
  ResourceNotFoundException: () => (0, quidproquo_core_1.actionResultError)(quidproquo_core_2.KeyValueStoreUpsertErrorTypeEnum.ResourceNotFound, 'KVS Resource Not Found'),
43
+ ConditionalCheckFailedException: () => (0, quidproquo_core_1.actionResultError)(quidproquo_core_2.KeyValueStoreUpsertErrorTypeEnum.Conflict, 'KVS item already exists'),
42
44
  });
43
45
  }
44
46
  });
@@ -94,9 +94,7 @@ const createBufferedLogger = (bucketName, regionForBucket, ship) => {
94
94
  let disabledLogCorrelations = [];
95
95
  return {
96
96
  enableLogs: (enable, reason, correlation) => __awaiter(void 0, void 0, void 0, function* () {
97
- disabledLogCorrelations = enable
98
- ? disabledLogCorrelations.filter((dlc) => dlc !== correlation)
99
- : [...disabledLogCorrelations, correlation];
97
+ disabledLogCorrelations = enable ? disabledLogCorrelations.filter((dlc) => dlc !== correlation) : [...disabledLogCorrelations, correlation];
100
98
  }),
101
99
  log: (result) => {
102
100
  // Defer all work (including JSON.stringify) to a microtask so the caller is not blocked.
@@ -7,7 +7,7 @@ const cognitoAuthenticationResultTypeToQpqAuthenticationInfo = (authResult, issu
7
7
  // Parse the issueDateTime and add the expiresIn to get the expiration date
8
8
  let issueDate = new Date(issueDateTime);
9
9
  issueDate.setSeconds(issueDate.getSeconds() + (authResult.ExpiresIn || 0));
10
- const expiresAt = issueDate.toISOString();
10
+ const expiresAt = (0, quidproquo_core_1.getQpqIsoDateTimeFromDate)(issueDate);
11
11
  return {
12
12
  accessToken: authResult.AccessToken,
13
13
  idToken: authResult.IdToken,
@@ -1,5 +1,6 @@
1
1
  import { KvsKey } from 'quidproquo-core';
2
2
  export interface PutItemOptions {
3
3
  expires?: number;
4
+ ifNotExistsAttribute?: string;
4
5
  }
5
6
  export declare function putItem<Item, T extends object = any>(tableName: string, item: Item, attributes: KvsKey[], options: PutItemOptions, region: string): Promise<void>;
@@ -39,9 +39,11 @@ const convertObjectToDynamoItem = (obj) => {
39
39
  function putItem(tableName, item, attributes, options, region) {
40
40
  return __awaiter(this, void 0, void 0, function* () {
41
41
  const dynamoDBClient = (0, createAwsClient_1.createAwsClient)(client_dynamodb_1.DynamoDBClient, { region });
42
- yield dynamoDBClient.send(new client_dynamodb_1.PutItemCommand({
43
- TableName: tableName,
44
- Item: convertObjectToDynamoItem(item),
45
- }));
42
+ yield dynamoDBClient.send(new client_dynamodb_1.PutItemCommand(Object.assign({ TableName: tableName, Item: convertObjectToDynamoItem(item) }, (options.ifNotExistsAttribute
43
+ ? {
44
+ ConditionExpression: 'attribute_not_exists(#ineAttr)',
45
+ ExpressionAttributeNames: { '#ineAttr': options.ifNotExistsAttribute },
46
+ }
47
+ : {}))));
46
48
  });
47
49
  }
@@ -120,11 +120,7 @@ const buildDynamoUpdateExpressionPart = (update, updateIndex) => {
120
120
  }
121
121
  };
122
122
  // Types that generate SET expressions
123
- const SET_ACTION_TYPES = [
124
- quidproquo_core_1.KvsUpdateActionType.Set,
125
- quidproquo_core_1.KvsUpdateActionType.SetIfNotExists,
126
- quidproquo_core_1.KvsUpdateActionType.Increment
127
- ];
123
+ const SET_ACTION_TYPES = [quidproquo_core_1.KvsUpdateActionType.Set, quidproquo_core_1.KvsUpdateActionType.SetIfNotExists, quidproquo_core_1.KvsUpdateActionType.Increment];
128
124
  const buildDynamoUpdateExpressionForClause = (clause, actionTypes, kvsUpdate) => {
129
125
  const actions = kvsUpdate.filter((update) => actionTypes.includes(update.action));
130
126
  if (actions.length === 0) {
@@ -15,9 +15,9 @@ const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
15
15
  // import { createPresignedPost } from '@aws-sdk/s3-presigned-post';
16
16
  const createAwsClient_1 = require("../createAwsClient");
17
17
  // export const generatePresignedUploadFormUrl = async (
18
- // bucketName: string,
19
- // objectKey: string,
20
- // region: string,
18
+ // bucketName: string,
19
+ // objectKey: string,
20
+ // region: string,
21
21
  // expirationMs: number,
22
22
  // contentType: string | undefined,
23
23
  // maxSizeBytes?: number | undefined
@@ -56,7 +56,7 @@ const generatePresignedUploadUrl = (bucketName, objectKey, region, expirationMs,
56
56
  Bucket: bucketName,
57
57
  Key: objectKey,
58
58
  ContentType: contentType,
59
- Metadata: Object.assign(Object.assign({}, (correlationId && { 'correlation-id': correlationId })), { 'upload-timestamp': Date.now().toString() })
59
+ Metadata: Object.assign(Object.assign({}, (correlationId && { 'correlation-id': correlationId })), { 'upload-timestamp': Date.now().toString() }),
60
60
  });
61
61
  const url = yield (0, s3_request_presigner_1.getSignedUrl)(s3Client, putObjectCommand, {
62
62
  expiresIn: expirationMs / 1000,
@@ -1,21 +1,28 @@
1
1
  import { actionResult, actionResultError, AiActionType, ErrorTypeEnum, } from 'quidproquo-core';
2
2
  import { generateText, stepCountIs } from 'ai';
3
- import { prepareAiPromptCall, toSdkMessages } from './logic';
3
+ import { createDriveFileResolver, prepareAiPromptCall, toSdkMessages } from './logic';
4
4
  const getProcessAiPrompt = (qpqConfig) => {
5
5
  return async (payload, session, actionProcessorList, logger, updateSession, dynamicModuleLoader, streamRegistry) => {
6
6
  const prepared = prepareAiPromptCall(qpqConfig, payload, session, actionProcessorList, logger, dynamicModuleLoader, streamRegistry);
7
7
  if ('error' in prepared) {
8
8
  return actionResultError(prepared.error.type, prepared.error.message);
9
9
  }
10
- const promptOrMessages = payload.messages
11
- ? { messages: toSdkMessages(payload.messages) }
12
- : { prompt: payload.prompt };
13
10
  try {
11
+ const promptOrMessages = payload.messages
12
+ ? {
13
+ messages: await toSdkMessages(payload.messages, createDriveFileResolver(qpqConfig, session, actionProcessorList, logger, dynamicModuleLoader, streamRegistry)),
14
+ }
15
+ : { prompt: payload.prompt };
16
+ // Extended thinking — the model reasons before answering, within the token budget.
17
+ const providerOptions = payload.reasoning
18
+ ? { bedrock: { reasoningConfig: { type: 'enabled', budgetTokens: payload.reasoning.budgetTokens ?? 4096 } } }
19
+ : undefined;
14
20
  const result = await generateText({
15
21
  model: prepared.model,
16
22
  system: payload.system,
17
23
  ...promptOrMessages,
18
24
  tools: prepared.tools,
25
+ providerOptions,
19
26
  stopWhen: stepCountIs(10),
20
27
  });
21
28
  return actionResult({ text: result.text });
@@ -1,22 +1,29 @@
1
1
  import { actionResult, actionResultError, AiActionType, ErrorTypeEnum, } from 'quidproquo-core';
2
2
  import { stepCountIs, streamText } from 'ai';
3
3
  import { randomGuid } from '../../../awsLambdaUtils';
4
- import { mapAiStreamPart, prepareAiPromptCall, toSdkMessages } from './logic';
4
+ import { createDriveFileResolver, mapAiStreamPart, prepareAiPromptCall, toSdkMessages } from './logic';
5
5
  const getProcessAiPromptStream = (qpqConfig) => {
6
6
  return async (payload, session, actionProcessorList, logger, updateSession, dynamicModuleLoader, streamRegistry) => {
7
7
  const prepared = prepareAiPromptCall(qpqConfig, payload, session, actionProcessorList, logger, dynamicModuleLoader, streamRegistry);
8
8
  if ('error' in prepared) {
9
9
  return actionResultError(prepared.error.type, prepared.error.message);
10
10
  }
11
- const promptOrMessages = payload.messages
12
- ? { messages: toSdkMessages(payload.messages) }
13
- : { prompt: payload.prompt };
14
11
  try {
12
+ const promptOrMessages = payload.messages
13
+ ? {
14
+ messages: await toSdkMessages(payload.messages, createDriveFileResolver(qpqConfig, session, actionProcessorList, logger, dynamicModuleLoader, streamRegistry)),
15
+ }
16
+ : { prompt: payload.prompt };
17
+ // Extended thinking — thinking progress streams out as Reasoning* parts.
18
+ const providerOptions = payload.reasoning
19
+ ? { bedrock: { reasoningConfig: { type: 'enabled', budgetTokens: payload.reasoning.budgetTokens ?? 4096 } } }
20
+ : undefined;
15
21
  const { fullStream } = streamText({
16
22
  model: prepared.model,
17
23
  system: payload.system,
18
24
  ...promptOrMessages,
19
25
  tools: prepared.tools,
26
+ providerOptions,
20
27
  stopWhen: stepCountIs(10),
21
28
  // streamText swallows errors by default to keep the server alive — surface them to CloudWatch.
22
29
  onError: ({ error }) => {
@@ -25,17 +25,17 @@ import { mapAiStreamToolInputStart } from './mapAiStreamToolInputStart';
25
25
  import { mapAiStreamToolOutputDenied } from './mapAiStreamToolOutputDenied';
26
26
  import { mapAiStreamToolResult } from './mapAiStreamToolResult';
27
27
  export const aiStreamPartMappers = {
28
- 'start': mapAiStreamStart,
29
- 'finish': mapAiStreamFinish,
28
+ start: mapAiStreamStart,
29
+ finish: mapAiStreamFinish,
30
30
  'start-step': mapAiStreamStartStep,
31
31
  'finish-step': mapAiStreamFinishStep,
32
- 'abort': mapAiStreamAbort,
33
- 'error': mapAiStreamError,
34
- 'raw': mapAiStreamRaw,
35
- 'source': mapAiStreamSourcePart,
36
- 'file': mapAiStreamFilePart,
32
+ abort: mapAiStreamAbort,
33
+ error: mapAiStreamError,
34
+ raw: mapAiStreamRaw,
35
+ source: mapAiStreamSourcePart,
36
+ file: mapAiStreamFilePart,
37
37
  'reasoning-file': mapAiStreamReasoningFilePart,
38
- 'custom': mapAiStreamCustom,
38
+ custom: mapAiStreamCustom,
39
39
  'text-start': mapAiStreamTextStart,
40
40
  'text-end': mapAiStreamTextEnd,
41
41
  'text-delta': mapAiStreamTextDelta,
@@ -0,0 +1,3 @@
1
+ import { ActionProcessorList, DynamicModuleLoader, QPQConfig, QpqLogger, StorySession, StreamRegistry } from 'quidproquo-core';
2
+ import { AiDriveFileResolver } from './toSdkMessages';
3
+ export declare const createDriveFileResolver: (qpqConfig: QPQConfig, session: StorySession, actionProcessorList: ActionProcessorList, logger: QpqLogger, dynamicModuleLoader: DynamicModuleLoader, streamRegistry?: StreamRegistry) => AiDriveFileResolver;
@@ -0,0 +1,14 @@
1
+ import { askFileReadBinaryContents, createImplementationRuntime, } from 'quidproquo-core';
2
+ import { randomGuid } from '../../../../awsLambdaUtils';
3
+ // Reads drive-referenced files through the active file action processor, so the
4
+ // same message works against S3 in production and local disk under the dev server.
5
+ export const createDriveFileResolver = (qpqConfig, session, actionProcessorList, logger, dynamicModuleLoader, streamRegistry) => {
6
+ return async (drive, filepath) => {
7
+ const resolveStory = createImplementationRuntime(qpqConfig, [`AI File: ${drive}/${filepath}`], () => new Date().toISOString(), randomGuid, session, actionProcessorList, logger, dynamicModuleLoader, streamRegistry);
8
+ const storyResult = await resolveStory(askFileReadBinaryContents, [drive, filepath]);
9
+ if (storyResult.error) {
10
+ throw new Error(storyResult.error.errorText);
11
+ }
12
+ return storyResult.result;
13
+ };
14
+ };
@@ -1,4 +1,5 @@
1
1
  export * from './aiStreamPartMappers';
2
+ export * from './createDriveFileResolver';
2
3
  export * from './mapAiStreamAbort';
3
4
  export * from './mapAiStreamCustom';
4
5
  export * from './mapAiStreamError';
@@ -1,4 +1,5 @@
1
1
  export * from './aiStreamPartMappers';
2
+ export * from './createDriveFileResolver';
2
3
  export * from './mapAiStreamAbort';
3
4
  export * from './mapAiStreamCustom';
4
5
  export * from './mapAiStreamError';
@@ -1,3 +1,4 @@
1
- import { AiMessage } from 'quidproquo-core';
1
+ import { AiMessage, QPQBinaryData } from 'quidproquo-core';
2
2
  import type { ModelMessage } from 'ai';
3
- export declare const toSdkMessages: (messages: AiMessage[]) => ModelMessage[];
3
+ export type AiDriveFileResolver = (drive: string, filepath: string) => Promise<QPQBinaryData>;
4
+ export declare const toSdkMessages: (messages: AiMessage[], resolveDriveFile: AiDriveFileResolver) => Promise<ModelMessage[]>;
@@ -1,25 +1,32 @@
1
- const mapUserPart = (part) => {
2
- if (part.type === 'text') {
3
- return { type: 'text', text: part.text };
1
+ const mapFilePart = async (part, resolveDriveFile) => {
2
+ if ('url' in part) {
3
+ return {
4
+ type: 'file',
5
+ data: new URL(part.url),
6
+ mediaType: part.mediaType,
7
+ filename: part.filename,
8
+ };
4
9
  }
10
+ const binary = await resolveDriveFile(part.drive, part.filepath);
5
11
  return {
6
12
  type: 'file',
7
- data: new URL(part.url),
13
+ data: binary.base64Data,
8
14
  mediaType: part.mediaType,
9
- filename: part.filename,
15
+ filename: part.filename ?? binary.filename,
10
16
  };
11
17
  };
12
- const mapAssistantPart = (part) => {
18
+ const mapUserPart = async (part, resolveDriveFile) => {
19
+ if (part.type === 'text') {
20
+ return { type: 'text', text: part.text };
21
+ }
22
+ return mapFilePart(part, resolveDriveFile);
23
+ };
24
+ const mapAssistantPart = async (part, resolveDriveFile) => {
13
25
  switch (part.type) {
14
26
  case 'text':
15
27
  return { type: 'text', text: part.text };
16
28
  case 'file':
17
- return {
18
- type: 'file',
19
- data: new URL(part.url),
20
- mediaType: part.mediaType,
21
- filename: part.filename,
22
- };
29
+ return mapFilePart(part, resolveDriveFile);
23
30
  case 'tool-call':
24
31
  return {
25
32
  type: 'tool-call',
@@ -50,29 +57,29 @@ const mapToolResult = (part) => {
50
57
  output,
51
58
  };
52
59
  };
53
- const mapUserMessage = (message) => {
60
+ const mapUserMessage = async (message, resolveDriveFile) => {
54
61
  if (typeof message.content === 'string') {
55
62
  return { role: 'user', content: message.content };
56
63
  }
57
- return { role: 'user', content: message.content.map(mapUserPart) };
64
+ return { role: 'user', content: await Promise.all(message.content.map((part) => mapUserPart(part, resolveDriveFile))) };
58
65
  };
59
- const mapAssistantMessage = (message) => {
66
+ const mapAssistantMessage = async (message, resolveDriveFile) => {
60
67
  if (typeof message.content === 'string') {
61
68
  return { role: 'assistant', content: message.content };
62
69
  }
63
- return { role: 'assistant', content: message.content.map(mapAssistantPart) };
70
+ return { role: 'assistant', content: await Promise.all(message.content.map((part) => mapAssistantPart(part, resolveDriveFile))) };
64
71
  };
65
72
  const mapToolMessage = (message) => ({
66
73
  role: 'tool',
67
74
  content: message.content.map(mapToolResult),
68
75
  });
69
- export const toSdkMessages = (messages) => messages.map((message) => {
76
+ export const toSdkMessages = (messages, resolveDriveFile) => Promise.all(messages.map((message) => {
70
77
  switch (message.role) {
71
78
  case 'user':
72
- return mapUserMessage(message);
79
+ return mapUserMessage(message, resolveDriveFile);
73
80
  case 'assistant':
74
- return mapAssistantMessage(message);
81
+ return mapAssistantMessage(message, resolveDriveFile);
75
82
  case 'tool':
76
83
  return mapToolMessage(message);
77
84
  }
78
- });
85
+ }));
@@ -1,5 +1,5 @@
1
1
  import { qpqConfigAwsUtils } from 'quidproquo-config-aws';
2
- import { actionResult, qpqCoreUtils, } from 'quidproquo-core';
2
+ import { actionResult, qpqCoreUtils } from 'quidproquo-core';
3
3
  import { ApiKeyValidationActionType } from 'quidproquo-webserver';
4
4
  import { timingSafeEqual } from 'crypto';
5
5
  import { getConfigRuntimeResourceName } from '../../../awsNamingUtils';
@@ -3,7 +3,7 @@ import { actionResult, actionResultError, actionResultErrorFromCaughtError, Erro
3
3
  import { getEventBusSnsTopicArn } from '../../../awsNamingUtils';
4
4
  import { publishMessage } from '../../../logic/sns/publishMessage';
5
5
  const getProcessEventBusSendMessage = (qpqConfig) => {
6
- return async ({ eventBusName, eventBusMessages, }, session) => {
6
+ return async ({ eventBusName, eventBusMessages }, session) => {
7
7
  const region = qpqConfigAwsUtils.getApplicationModuleDeployRegion(qpqConfig);
8
8
  const eventBusConfig = qpqCoreUtils.getEventBusConfigByName(eventBusName, qpqConfig);
9
9
  if (!eventBusConfig) {
@@ -1,5 +1,5 @@
1
1
  import { qpqConfigAwsUtils } from 'quidproquo-config-aws';
2
- import { actionResult, actionResultError, actionResultErrorFromCaughtError, FileActionType, FileDeleteErrorTypeEnum } from 'quidproquo-core';
2
+ import { actionResult, actionResultError, actionResultErrorFromCaughtError, FileActionType, FileDeleteErrorTypeEnum, } from 'quidproquo-core';
3
3
  import { deleteFiles } from '../../../logic/s3/s3Utils';
4
4
  import { resolveStorageDriveBucketName } from './utils';
5
5
  const getProcessFileDelete = (qpqConfig) => {
@@ -1,6 +1,6 @@
1
1
  import { qpqConfigAwsUtils } from 'quidproquo-config-aws';
2
2
  import { actionResultError, ErrorTypeEnum } from 'quidproquo-core';
3
- import { actionResult, FileActionType, FileGenerateTemporarySecureUrlErrorTypeEnum } from 'quidproquo-core';
3
+ import { actionResult, FileActionType, FileGenerateTemporarySecureUrlErrorTypeEnum, } from 'quidproquo-core';
4
4
  import { generatePresignedUrl } from '../../../logic/s3/generatePresignedUrl';
5
5
  import { resolveStorageDriveBucketName } from './utils';
6
6
  // SigV4 presigned URLs cannot expire more than 7 days in the future
@@ -1,6 +1,6 @@
1
1
  import { qpqConfigAwsUtils } from 'quidproquo-config-aws';
2
2
  import { actionResultError, ErrorTypeEnum } from 'quidproquo-core';
3
- import { actionResult, FileActionType, FileGenerateTemporaryUploadSecureUrlErrorTypeEnum } from 'quidproquo-core';
3
+ import { actionResult, FileActionType, FileGenerateTemporaryUploadSecureUrlErrorTypeEnum, } from 'quidproquo-core';
4
4
  import { generatePresignedUploadUrl } from '../../../logic/s3/generatePresignedUploadUrl';
5
5
  import { resolveStorageDriveBucketName } from './utils';
6
6
  // SigV4 presigned URLs cannot expire more than 7 days in the future
@@ -34,9 +34,7 @@ const getProcessFileStreamOpen = (qpqConfig) => {
34
34
  const streamId = `s3-${randomGuid()}`;
35
35
  const isText = encoding === 'text';
36
36
  const size = chunkSize ?? 65536;
37
- const transform = isText
38
- ? (buf) => buf.toString('utf8')
39
- : (buf) => buf.toString('base64');
37
+ const transform = isText ? (buf) => buf.toString('utf8') : (buf) => buf.toString('base64');
40
38
  const iterator = chunkedReadableIterator(response.Body, size, transform);
41
39
  streamRegistry.register(streamId, iterator);
42
40
  return actionResult({ id: streamId, encoding });
@@ -1,6 +1,6 @@
1
1
  import { qpqConfigAwsUtils } from 'quidproquo-config-aws';
2
2
  import { qpqCoreUtils } from 'quidproquo-core';
3
- import { actionResult, actionResultError, actionResultErrorFromCaughtError, KeyValueStoreActionType, KeyValueStoreDeleteErrorTypeEnum } from 'quidproquo-core';
3
+ import { actionResult, actionResultError, actionResultErrorFromCaughtError, KeyValueStoreActionType, KeyValueStoreDeleteErrorTypeEnum, } from 'quidproquo-core';
4
4
  import { getKvsDynamoTableNameFromConfig } from '../../../awsNamingUtils';
5
5
  import { deleteItem } from '../../../logic/dynamo';
6
6
  const getProcessKeyValueStoreDelete = (qpqConfig) => {
@@ -1,5 +1,5 @@
1
1
  import { qpqConfigAwsUtils } from 'quidproquo-config-aws';
2
- import { actionResult, actionResultError, actionResultErrorFromCaughtError, KeyValueStoreActionType, KeyValueStoreGetErrorTypeEnum } from 'quidproquo-core';
2
+ import { actionResult, actionResultError, actionResultErrorFromCaughtError, KeyValueStoreActionType, KeyValueStoreGetErrorTypeEnum, } from 'quidproquo-core';
3
3
  import { getKvsDynamoTableNameFromConfig } from '../../../awsNamingUtils';
4
4
  import { getItem } from '../../../logic/dynamo';
5
5
  const getProcessKeyValueStoreGet = (qpqConfig) => {
@@ -1,5 +1,5 @@
1
1
  import { qpqConfigAwsUtils } from 'quidproquo-config-aws';
2
- import { actionResult, actionResultError, actionResultErrorFromCaughtError, KeyValueStoreActionType, KeyValueStoreGetAllErrorTypeEnum } from 'quidproquo-core';
2
+ import { actionResult, actionResultError, actionResultErrorFromCaughtError, KeyValueStoreActionType, KeyValueStoreGetAllErrorTypeEnum, } from 'quidproquo-core';
3
3
  import { getKvsDynamoTableNameFromConfig } from '../../../awsNamingUtils';
4
4
  import { getAllItems } from '../../../logic/dynamo';
5
5
  const getProcessKeyValueStoreGetAll = (qpqConfig) => {
@@ -1,5 +1,5 @@
1
1
  import { qpqConfigAwsUtils } from 'quidproquo-config-aws';
2
- import { actionResultError, actionResultErrorFromCaughtError, ErrorTypeEnum, qpqCoreUtils } from 'quidproquo-core';
2
+ import { actionResultError, actionResultErrorFromCaughtError, ErrorTypeEnum, qpqCoreUtils, } from 'quidproquo-core';
3
3
  import { actionResult, KeyValueStoreActionType, KeyValueStoreQueryErrorTypeEnum } from 'quidproquo-core';
4
4
  import { getKvsDynamoTableNameFromConfig } from '../../../awsNamingUtils';
5
5
  import { query } from '../../../logic/dynamo';
@@ -1,5 +1,5 @@
1
1
  import { qpqConfigAwsUtils } from 'quidproquo-config-aws';
2
- import { actionResultError, actionResultErrorFromCaughtError } from 'quidproquo-core';
2
+ import { actionResultError, actionResultErrorFromCaughtError, } from 'quidproquo-core';
3
3
  import { actionResult, KeyValueStoreActionType, KeyValueStoreScanErrorTypeEnum } from 'quidproquo-core';
4
4
  import { getKvsDynamoTableNameFromConfig } from '../../../awsNamingUtils';
5
5
  import { scan } from '../../../logic/dynamo/scan';
@@ -1,6 +1,6 @@
1
1
  import { qpqConfigAwsUtils } from 'quidproquo-config-aws';
2
2
  import { qpqCoreUtils } from 'quidproquo-core';
3
- import { actionResult, actionResultError, actionResultErrorFromCaughtError, KeyValueStoreActionType, KeyValueStoreUpdateErrorTypeEnum } from 'quidproquo-core';
3
+ import { actionResult, actionResultError, actionResultErrorFromCaughtError, KeyValueStoreActionType, KeyValueStoreUpdateErrorTypeEnum, } from 'quidproquo-core';
4
4
  import { getKvsDynamoTableNameFromConfig } from '../../../awsNamingUtils';
5
5
  import { updateItem } from '../../../logic/dynamo';
6
6
  const getProcessKeyValueStoreUpdate = (qpqConfig) => {
@@ -20,6 +20,7 @@ const getProcessKeyValueStoreUpsert = (qpqConfig) => {
20
20
  try {
21
21
  await putItem(dynamoTableName, item, keys, {
22
22
  expires: options?.ttlInSeconds,
23
+ ifNotExistsAttribute: options?.ifNotExists ? storeConfig.partitionKey.key : undefined,
23
24
  }, region);
24
25
  return actionResult(void 0);
25
26
  }
@@ -27,6 +28,7 @@ const getProcessKeyValueStoreUpsert = (qpqConfig) => {
27
28
  return actionResultErrorFromCaughtError(error, {
28
29
  InternalServerError: () => actionResultError(KeyValueStoreUpsertErrorTypeEnum.ServiceUnavailable, 'KVS Service Unavailable'),
29
30
  ResourceNotFoundException: () => actionResultError(KeyValueStoreUpsertErrorTypeEnum.ResourceNotFound, 'KVS Resource Not Found'),
31
+ ConditionalCheckFailedException: () => actionResultError(KeyValueStoreUpsertErrorTypeEnum.Conflict, 'KVS item already exists'),
30
32
  });
31
33
  }
32
34
  };
@@ -1,5 +1,5 @@
1
1
  import { qpqConfigAwsUtils } from 'quidproquo-config-aws';
2
- import { actionResult, actionResultError, actionResultErrorFromCaughtError, QueueActionType, QueueSendMessagesErrorTypeEnum, toCrossServiceSession } from 'quidproquo-core';
2
+ import { actionResult, actionResultError, actionResultErrorFromCaughtError, QueueActionType, QueueSendMessagesErrorTypeEnum, toCrossServiceSession, } from 'quidproquo-core';
3
3
  import { sendMessages } from '../../../logic/sqs/sendMessages';
4
4
  import { resolveResourceName } from '../../../runtimeConfig/qpqAwsLambdaRuntimeConfigUtils';
5
5
  const getProcessQueueSendMessage = (qpqConfig) => {
@@ -76,9 +76,7 @@ const createBufferedLogger = (bucketName, regionForBucket, ship) => {
76
76
  let disabledLogCorrelations = [];
77
77
  return {
78
78
  enableLogs: async (enable, reason, correlation) => {
79
- disabledLogCorrelations = enable
80
- ? disabledLogCorrelations.filter((dlc) => dlc !== correlation)
81
- : [...disabledLogCorrelations, correlation];
79
+ disabledLogCorrelations = enable ? disabledLogCorrelations.filter((dlc) => dlc !== correlation) : [...disabledLogCorrelations, correlation];
82
80
  },
83
81
  log: (result) => {
84
82
  // Defer all work (including JSON.stringify) to a microtask so the caller is not blocked.
@@ -1,10 +1,10 @@
1
- import { AuthenticateUserChallenge } from 'quidproquo-core';
1
+ import { AuthenticateUserChallenge, getQpqIsoDateTimeFromDate } from 'quidproquo-core';
2
2
  import { ChallengeNameType } from '@aws-sdk/client-cognito-identity-provider';
3
3
  export const cognitoAuthenticationResultTypeToQpqAuthenticationInfo = (authResult, issueDateTime) => {
4
4
  // Parse the issueDateTime and add the expiresIn to get the expiration date
5
5
  let issueDate = new Date(issueDateTime);
6
6
  issueDate.setSeconds(issueDate.getSeconds() + (authResult.ExpiresIn || 0));
7
- const expiresAt = issueDate.toISOString();
7
+ const expiresAt = getQpqIsoDateTimeFromDate(issueDate);
8
8
  return {
9
9
  accessToken: authResult.AccessToken,
10
10
  idToken: authResult.IdToken,
@@ -1,5 +1,6 @@
1
1
  import { KvsKey } from 'quidproquo-core';
2
2
  export interface PutItemOptions {
3
3
  expires?: number;
4
+ ifNotExistsAttribute?: string;
4
5
  }
5
6
  export declare function putItem<Item, T extends object = any>(tableName: string, item: Item, attributes: KvsKey[], options: PutItemOptions, region: string): Promise<void>;
@@ -29,5 +29,11 @@ export async function putItem(tableName, item, attributes, options, region) {
29
29
  await dynamoDBClient.send(new PutItemCommand({
30
30
  TableName: tableName,
31
31
  Item: convertObjectToDynamoItem(item),
32
+ ...(options.ifNotExistsAttribute
33
+ ? {
34
+ ConditionExpression: 'attribute_not_exists(#ineAttr)',
35
+ ExpressionAttributeNames: { '#ineAttr': options.ifNotExistsAttribute },
36
+ }
37
+ : {}),
32
38
  }));
33
39
  }
@@ -115,11 +115,7 @@ const buildDynamoUpdateExpressionPart = (update, updateIndex) => {
115
115
  }
116
116
  };
117
117
  // Types that generate SET expressions
118
- const SET_ACTION_TYPES = [
119
- KvsUpdateActionType.Set,
120
- KvsUpdateActionType.SetIfNotExists,
121
- KvsUpdateActionType.Increment
122
- ];
118
+ const SET_ACTION_TYPES = [KvsUpdateActionType.Set, KvsUpdateActionType.SetIfNotExists, KvsUpdateActionType.Increment];
123
119
  const buildDynamoUpdateExpressionForClause = (clause, actionTypes, kvsUpdate) => {
124
120
  const actions = kvsUpdate.filter((update) => actionTypes.includes(update.action));
125
121
  if (actions.length === 0) {
@@ -3,9 +3,9 @@ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
3
3
  // import { createPresignedPost } from '@aws-sdk/s3-presigned-post';
4
4
  import { createAwsClient } from '../createAwsClient';
5
5
  // export const generatePresignedUploadFormUrl = async (
6
- // bucketName: string,
7
- // objectKey: string,
8
- // region: string,
6
+ // bucketName: string,
7
+ // objectKey: string,
8
+ // region: string,
9
9
  // expirationMs: number,
10
10
  // contentType: string | undefined,
11
11
  // maxSizeBytes?: number | undefined
@@ -46,8 +46,8 @@ export const generatePresignedUploadUrl = async (bucketName, objectKey, region,
46
46
  ContentType: contentType,
47
47
  Metadata: {
48
48
  ...(correlationId && { 'correlation-id': correlationId }),
49
- 'upload-timestamp': Date.now().toString()
50
- }
49
+ 'upload-timestamp': Date.now().toString(),
50
+ },
51
51
  });
52
52
  const url = await getSignedUrl(s3Client, putObjectCommand, {
53
53
  expiresIn: expirationMs / 1000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quidproquo-actionprocessor-awslambda",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "",
5
5
  "main": "./lib/commonjs/index.js",
6
6
  "module": "./lib/esm/index.js",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "homepage": "https://github.com/joe-coady/quidproquo#readme",
35
35
  "dependencies": {
36
- "@ai-sdk/amazon-bedrock": "^4.0.83",
36
+ "@ai-sdk/amazon-bedrock": "^5.0.11",
37
37
  "@aws-sdk/client-acm": "^3.744.0",
38
38
  "@aws-sdk/client-api-gateway": "^3.744.0",
39
39
  "@aws-sdk/client-apigatewaymanagementapi": "^3.744.0",
@@ -59,10 +59,10 @@
59
59
  "lodash": "^4.17.21",
60
60
  "node-cache": "^5.1.2",
61
61
  "node-match-path": "^0.6.3",
62
- "quidproquo-actionprocessor-js": "0.1.1",
63
- "quidproquo-config-aws": "0.1.1",
64
- "quidproquo-core": "0.1.1",
65
- "quidproquo-webserver": "0.1.1"
62
+ "quidproquo-actionprocessor-js": "0.1.2",
63
+ "quidproquo-config-aws": "0.1.2",
64
+ "quidproquo-core": "0.1.2",
65
+ "quidproquo-webserver": "0.1.2"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/busboy": "^1.5.0",
@@ -70,7 +70,7 @@
70
70
  "@types/lodash": "^4.14.194",
71
71
  "@types/node": "^22.13.13",
72
72
  "esbuild": "^0.25.8",
73
- "quidproquo-tsconfig": "0.1.1",
73
+ "quidproquo-tsconfig": "0.1.2",
74
74
  "typescript": "^5.8.2"
75
75
  }
76
76
  }