@jskit-ai/assistant-runtime 0.1.168 → 0.1.170
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.
- package/fixtures/conversation/vite.config.mjs +3 -0
- package/migrations/assistant_turn_requests.cjs +20 -0
- package/package.json +10 -7
- package/src/client/components/AssistantSurfaceClientElement.vue +14 -4
- package/src/client/composables/useAssistantRuntime.js +62 -37
- package/src/server/AssistantProvider.js +5 -1
- package/src/server/repositories/turnRequestsRepository.js +35 -0
- package/src/server/services/chatService.js +415 -359
- package/src/shared/assistantRuntimeConfig.js +2 -1
- package/test/chatServiceLifecycle.test.js +4 -2
- package/test/conversation.browser.test.js +50 -0
- package/test/lazyAppConfig.test.js +3 -0
- package/test/packageMetadata.test.js +2 -1
- package/test/support/memoryTurnRequests.js +19 -0
- package/test/turnRequests.test.js +122 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createMemoryTurnRequests } from "../../test/support/memoryTurnRequests.js";
|
|
1
2
|
import { createChatService } from "../../src/server/services/chatService.js";
|
|
2
3
|
import { defineConfig } from "vite";
|
|
3
4
|
import vue from "@vitejs/plugin-vue";
|
|
@@ -12,6 +13,7 @@ export default defineConfig({
|
|
|
12
13
|
const streams = [];
|
|
13
14
|
const requests = [];
|
|
14
15
|
const transcript = [];
|
|
16
|
+
const turnRequests = createMemoryTurnRequests();
|
|
15
17
|
let realService = false;
|
|
16
18
|
server.middlewares.use(async (req, res, next) => {
|
|
17
19
|
const url = new URL(req.url, "http://fixture");
|
|
@@ -58,6 +60,7 @@ export default defineConfig({
|
|
|
58
60
|
});
|
|
59
61
|
stream.advance = parameters => release(parameters);
|
|
60
62
|
const service = createChatService({
|
|
63
|
+
turnRequests,
|
|
61
64
|
aiClientFactory: { async resolveClient(_surface, { integrationId }) {
|
|
62
65
|
return { enabled: true, provider: "fixture", defaultModel: integrationId, supportsAttachments: true,
|
|
63
66
|
async *createChatCompletionStream({ messages }) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
exports.up = async function up(knex) {
|
|
2
|
+
if (await knex.schema.hasTable("assistant_turn_requests")) return;
|
|
3
|
+
await knex.schema.createTable("assistant_turn_requests", table => {
|
|
4
|
+
table.bigIncrements("id").primary();
|
|
5
|
+
table.bigInteger("actor_user_id").unsigned().notNullable().references("id").inTable("users").onDelete("CASCADE");
|
|
6
|
+
table.string("scope_key", 128).notNullable();
|
|
7
|
+
table.string("message_sid", 128).notNullable();
|
|
8
|
+
table.string("claim_token", 36).notNullable();
|
|
9
|
+
table.text("request_json", "longtext").notNullable();
|
|
10
|
+
table.text("response_json", "longtext").nullable();
|
|
11
|
+
table.string("status", 32).notNullable().defaultTo("running");
|
|
12
|
+
table.timestamp("created_at").notNullable().defaultTo(knex.fn.now());
|
|
13
|
+
table.timestamp("updated_at").notNullable().defaultTo(knex.fn.now());
|
|
14
|
+
table.unique(["actor_user_id", "scope_key", "message_sid"], "uq_assistant_turn_request");
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
exports.down = async function down(knex) {
|
|
19
|
+
await knex.schema.dropTableIfExists("assistant_turn_requests");
|
|
20
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/assistant-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.170",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
"./server/actionIds": "./src/server/actionIds.js"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@jskit-ai/assistant-core": "0.1.
|
|
15
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
14
|
+
"@jskit-ai/assistant-core": "0.1.177",
|
|
15
|
+
"@jskit-ai/database-runtime": "0.1.199",
|
|
16
16
|
"json-rest-schema": "^1.0.17"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
20
|
-
"@jskit-ai/http-web": "0.1.
|
|
21
|
-
"@jskit-ai/kernel": "0.1.
|
|
22
|
-
"@jskit-ai/shell-web": "0.1.
|
|
19
|
+
"@jskit-ai/http-runtime": "0.1.197",
|
|
20
|
+
"@jskit-ai/http-web": "0.1.44",
|
|
21
|
+
"@jskit-ai/kernel": "0.1.199",
|
|
22
|
+
"@jskit-ai/shell-web": "0.1.203",
|
|
23
23
|
"@tanstack/vue-query": "^5.90.5",
|
|
24
24
|
"vue": "^3.5.13",
|
|
25
25
|
"vuetify": "^4.0.0"
|
|
@@ -71,6 +71,9 @@
|
|
|
71
71
|
},
|
|
72
72
|
{
|
|
73
73
|
"tableName": "assistant_messages"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"tableName": "assistant_turn_requests"
|
|
74
77
|
}
|
|
75
78
|
]
|
|
76
79
|
}
|
|
@@ -43,6 +43,7 @@ onMounted(() => {
|
|
|
43
43
|
const selectedConversation = computed(() => state.conversationHistory.find((entry) => String(entry.id) === state.activeConversationId));
|
|
44
44
|
const turns = computed(() => mapAssistantConversationTurns(state.messages, state.pendingToolEvents, props));
|
|
45
45
|
const adapter = computed(() => ({
|
|
46
|
+
delivery: runtime.delivery,
|
|
46
47
|
attachments: props.attachments,
|
|
47
48
|
suggestions: props.suggestions,
|
|
48
49
|
goal: props.goal,
|
|
@@ -61,6 +62,7 @@ const adapter = computed(() => ({
|
|
|
61
62
|
turns: turns.value,
|
|
62
63
|
scrollKey: `${state.scopeKey}:${state.activeConversationId || "new"}`,
|
|
63
64
|
loading: state.isRestoringConversation,
|
|
65
|
+
working: state.isStreaming && !runtime.delivery.state.sending,
|
|
64
66
|
assistantLabel: props.assistantLabel,
|
|
65
67
|
welcomeMessage: state.messages.length || state.isRestoringConversation ? "" : props.welcomeMessage
|
|
66
68
|
},
|
|
@@ -79,14 +81,22 @@ const adapter = computed(() => ({
|
|
|
79
81
|
},
|
|
80
82
|
actions: {
|
|
81
83
|
setDraft(value) { state.input = value; },
|
|
82
|
-
submit: ({ attachments }) =>
|
|
83
|
-
|
|
84
|
-
onAccepted: () => props.attachments?.clearAttachments({ accepted: true, attachmentIds: attachments.map(file => file.attachmentId) })
|
|
85
|
-
}),
|
|
84
|
+
submit: ({ attachments }) => submitMessage({ attachments }),
|
|
85
|
+
resend: (messageId) => submitMessage({ retryMessageId: messageId }),
|
|
86
86
|
stop: runtime.actions.cancelStream
|
|
87
87
|
}
|
|
88
88
|
}));
|
|
89
89
|
|
|
90
|
+
function submitMessage({ attachments = [], retryMessageId = "" }) {
|
|
91
|
+
const attachmentOwner = props.attachments;
|
|
92
|
+
const acceptedFiles = retryMessageId ? runtime.delivery.find(retryMessageId)?.payload.displayAttachments || [] : attachments;
|
|
93
|
+
return runtime.actions.sendMessage({
|
|
94
|
+
attachments,
|
|
95
|
+
retryMessageId,
|
|
96
|
+
onAccepted: () => attachmentOwner?.clearAttachments({ accepted: true, attachmentIds: acceptedFiles.map(file => file.attachmentId) })
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
90
100
|
function conversationSubtitle(entry) {
|
|
91
101
|
const details = [runtime.meta.normalizeConversationStatus(entry.status), runtime.meta.formatConversationStartedAt(entry.startedAt), `${Number(entry.messageCount || 0)} messages`];
|
|
92
102
|
if (state.isAdminSurface) {
|
|
@@ -2,6 +2,7 @@ import { computed, onScopeDispose, ref, shallowRef, toValue, watch } from "vue";
|
|
|
2
2
|
import { useQueryClient } from "@tanstack/vue-query";
|
|
3
3
|
import { getClientAppConfig } from "@jskit-ai/kernel/client";
|
|
4
4
|
import { normalizeObject, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
|
+
import { createAssistantMessageDelivery } from "@jskit-ai/assistant-core/client/conversation-delivery";
|
|
5
6
|
import { buildAssistantApiPath } from "@jskit-ai/assistant-core/shared";
|
|
6
7
|
import {
|
|
7
8
|
ASSISTANT_STREAM_EVENT_TYPES,
|
|
@@ -153,6 +154,7 @@ function useAssistantRuntime({ api = null, surfaceId = "", integrationId = "" }
|
|
|
153
154
|
const conversationId = ref(null);
|
|
154
155
|
const abortController = shallowRef(null);
|
|
155
156
|
const isCanceling = ref(false);
|
|
157
|
+
const delivery = createAssistantMessageDelivery();
|
|
156
158
|
let restoreVersion = 0;
|
|
157
159
|
|
|
158
160
|
const placementSnapshot = computed(() => normalizeObject(placementContext.value));
|
|
@@ -378,6 +380,7 @@ function useAssistantRuntime({ api = null, surfaceId = "", integrationId = "" }
|
|
|
378
380
|
const restored = mapTranscriptEntriesToAssistantState(transcript.entries);
|
|
379
381
|
conversationId.value = normalizedConversationId;
|
|
380
382
|
writeStoredActiveConversationId(scope, normalizedConversationId);
|
|
383
|
+
delivery.reset();
|
|
381
384
|
messages.value = restored.messages;
|
|
382
385
|
pendingToolEvents.value = restored.pendingToolEvents;
|
|
383
386
|
} catch (loadError) {
|
|
@@ -398,6 +401,7 @@ function useAssistantRuntime({ api = null, surfaceId = "", integrationId = "" }
|
|
|
398
401
|
|
|
399
402
|
function clearView() {
|
|
400
403
|
restoreVersion += 1;
|
|
404
|
+
delivery.reset();
|
|
401
405
|
abortController.value?.abort();
|
|
402
406
|
abortController.value = null;
|
|
403
407
|
messages.value = [];
|
|
@@ -425,35 +429,47 @@ function useAssistantRuntime({ api = null, surfaceId = "", integrationId = "" }
|
|
|
425
429
|
abortController.value.abort();
|
|
426
430
|
}
|
|
427
431
|
|
|
428
|
-
async function sendMessage({ attachments = [], onAccepted } = {}) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
+
async function sendMessage({ attachments = [], onAccepted, retryMessageId = "" } = {}) {
|
|
433
|
+
if (isStreaming.value || isRestoringConversation.value || !hasRuntimeScope.value) return false;
|
|
434
|
+
const retry = retryMessageId ? delivery.find(retryMessageId) : null;
|
|
435
|
+
if (retryMessageId && retry?.status !== "failed") return false;
|
|
436
|
+
const message = normalizeText(input.value).slice(0, MAX_INPUT_CHARS) || (attachments.length ? "Please review the attached files." : "");
|
|
437
|
+
const parsedConversationId = normalizeRecordId(conversationId.value, { fallback: null });
|
|
438
|
+
const payload = retry?.payload || {
|
|
439
|
+
message,
|
|
440
|
+
displayAttachments: attachments,
|
|
441
|
+
request: {
|
|
442
|
+
input: message,
|
|
443
|
+
...(parsedConversationId ? { conversationId: parsedConversationId } : {}),
|
|
444
|
+
...(attachments.length ? { attachmentIds: attachments.map(file => file.attachmentId) } : {}),
|
|
445
|
+
...(toValue(integrationId) ? { integrationId: toValue(integrationId) } : {}),
|
|
446
|
+
history: buildHistory(messages.value)
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
if (!payload.message) return false;
|
|
450
|
+
if (!retry) input.value = "";
|
|
451
|
+
try {
|
|
452
|
+
return await delivery.send(payload, {
|
|
453
|
+
messageId: retryMessageId || buildId("message"),
|
|
454
|
+
deliver(submission) {
|
|
455
|
+
const admission = Promise.withResolvers();
|
|
456
|
+
runStream(submission, { onAccepted, admission }).then(
|
|
457
|
+
() => admission.reject(new Error("Assistant did not confirm this message.")),
|
|
458
|
+
admission.reject
|
|
459
|
+
);
|
|
460
|
+
return admission.promise;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
} catch {
|
|
464
|
+
// The shared delivery state owns failures before the server accepts a turn.
|
|
465
|
+
return false;
|
|
432
466
|
}
|
|
467
|
+
}
|
|
433
468
|
|
|
434
|
-
|
|
469
|
+
async function runStream({ messageId, message: normalizedInput, displayAttachments: attachments, request }, { onAccepted, admission }) {
|
|
435
470
|
const assistantMessageId = buildId("assistant");
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
appendMessage({
|
|
440
|
-
id: buildId("user"),
|
|
441
|
-
role: "user",
|
|
442
|
-
kind: "chat",
|
|
443
|
-
text: normalizedInput,
|
|
444
|
-
attachments,
|
|
445
|
-
status: "done"
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
appendMessage({
|
|
449
|
-
id: assistantMessageId,
|
|
450
|
-
role: "assistant",
|
|
451
|
-
kind: "chat",
|
|
452
|
-
text: "",
|
|
453
|
-
status: "streaming"
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
input.value = "";
|
|
471
|
+
let accepted = false;
|
|
472
|
+
let admissionError = "";
|
|
457
473
|
setRuntimeError("");
|
|
458
474
|
isStreaming.value = true;
|
|
459
475
|
|
|
@@ -469,14 +485,7 @@ function useAssistantRuntime({ api = null, surfaceId = "", integrationId = "" }
|
|
|
469
485
|
|
|
470
486
|
try {
|
|
471
487
|
await runtimeApi.streamChat(
|
|
472
|
-
{
|
|
473
|
-
messageId,
|
|
474
|
-
...(parsedConversationId ? { conversationId: parsedConversationId } : {}),
|
|
475
|
-
input: normalizedInput,
|
|
476
|
-
...(attachments.length ? { attachmentIds: attachments.map(file => file.attachmentId) } : {}),
|
|
477
|
-
...(toValue(integrationId) ? { integrationId: toValue(integrationId) } : {}),
|
|
478
|
-
history
|
|
479
|
-
},
|
|
488
|
+
{ ...request, messageId },
|
|
480
489
|
{
|
|
481
490
|
signal: streamAbortController.signal,
|
|
482
491
|
onEvent(event) {
|
|
@@ -484,7 +493,14 @@ function useAssistantRuntime({ api = null, surfaceId = "", integrationId = "" }
|
|
|
484
493
|
const eventType = normalizeAssistantStreamEventType(event?.type, "");
|
|
485
494
|
|
|
486
495
|
if (eventType === ASSISTANT_STREAM_EVENT_TYPES.META && Object.hasOwn(event || {}, "conversationId")) {
|
|
487
|
-
|
|
496
|
+
if (!accepted) {
|
|
497
|
+
accepted = true;
|
|
498
|
+
appendMessage({ id: messageId, role: "user", kind: "chat", text: normalizedInput, attachments, status: "done" });
|
|
499
|
+
appendMessage({ id: assistantMessageId, role: "assistant", kind: "chat", text: "", status: "streaming" });
|
|
500
|
+
delivery.reconcile([{ user: { messageId } }]);
|
|
501
|
+
onAccepted?.();
|
|
502
|
+
admission.resolve(true);
|
|
503
|
+
}
|
|
488
504
|
conversationId.value = normalizeRecordId(event?.conversationId, { fallback: null });
|
|
489
505
|
writeStoredActiveConversationId(runtimeScope.value, conversationId.value);
|
|
490
506
|
return;
|
|
@@ -550,6 +566,10 @@ function useAssistantRuntime({ api = null, surfaceId = "", integrationId = "" }
|
|
|
550
566
|
}
|
|
551
567
|
|
|
552
568
|
if (eventType === ASSISTANT_STREAM_EVENT_TYPES.ERROR) {
|
|
569
|
+
if (!accepted) {
|
|
570
|
+
admissionError = normalizeText(event?.message) || "Assistant request failed.";
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
553
573
|
setRuntimeError(
|
|
554
574
|
normalizeText(event?.message) || "Assistant request failed.",
|
|
555
575
|
"assistant.runtime:stream-event-error"
|
|
@@ -568,6 +588,7 @@ function useAssistantRuntime({ api = null, surfaceId = "", integrationId = "" }
|
|
|
568
588
|
);
|
|
569
589
|
|
|
570
590
|
if (!ownsStream()) return;
|
|
591
|
+
if (!accepted) throw new Error(admissionError || "Assistant did not confirm this message.");
|
|
571
592
|
if (streamAbortController.signal.aborted) streamDoneStatus = "aborted";
|
|
572
593
|
const assistantMessage = findMessage(assistantMessageId);
|
|
573
594
|
const assistantMessageText = normalizeText(assistantMessage?.text);
|
|
@@ -589,6 +610,7 @@ function useAssistantRuntime({ api = null, surfaceId = "", integrationId = "" }
|
|
|
589
610
|
}
|
|
590
611
|
} catch (streamError) {
|
|
591
612
|
if (!ownsStream()) return;
|
|
613
|
+
if (!accepted) throw streamError;
|
|
592
614
|
if (String(streamError?.name || "") === "AbortError") {
|
|
593
615
|
updateMessage(assistantMessageId, {
|
|
594
616
|
status: "canceled"
|
|
@@ -611,8 +633,10 @@ function useAssistantRuntime({ api = null, surfaceId = "", integrationId = "" }
|
|
|
611
633
|
abortController.value = null;
|
|
612
634
|
isStreaming.value = false;
|
|
613
635
|
isCanceling.value = false;
|
|
614
|
-
|
|
615
|
-
|
|
636
|
+
if (accepted) {
|
|
637
|
+
await invalidateConversationScope();
|
|
638
|
+
await refreshConversationHistory();
|
|
639
|
+
}
|
|
616
640
|
}
|
|
617
641
|
}
|
|
618
642
|
}
|
|
@@ -631,6 +655,7 @@ function useAssistantRuntime({ api = null, surfaceId = "", integrationId = "" }
|
|
|
631
655
|
normalizeConversationStatus,
|
|
632
656
|
formatConversationStartedAt
|
|
633
657
|
},
|
|
658
|
+
delivery,
|
|
634
659
|
state: {
|
|
635
660
|
messages,
|
|
636
661
|
input,
|
|
@@ -7,6 +7,7 @@ import { registerRoutes } from "./registerRoutes.js";
|
|
|
7
7
|
import { createRepository as createAssistantConfigRepository } from "./repositories/assistantConfigRepository.js";
|
|
8
8
|
import { createRepository as createConversationsRepository } from "./repositories/conversationsRepository.js";
|
|
9
9
|
import { createRepository as createMessagesRepository } from "./repositories/messagesRepository.js";
|
|
10
|
+
import { createRepository as createTurnRequestsRepository } from "./repositories/turnRequestsRepository.js";
|
|
10
11
|
import { createService as createAssistantConfigService } from "./services/assistantConfigService.js";
|
|
11
12
|
import { createChatService } from "./services/chatService.js";
|
|
12
13
|
import { createTranscriptService } from "./services/transcriptService.js";
|
|
@@ -68,6 +69,7 @@ function createAssistantRuntime({
|
|
|
68
69
|
const assistantConfigRepository = createAssistantConfigRepository(database.knex);
|
|
69
70
|
const conversationsRepository = createConversationsRepository(database.knex);
|
|
70
71
|
const messagesRepository = createMessagesRepository(database.knex);
|
|
72
|
+
const turnRequests = createTurnRequestsRepository(database.knex);
|
|
71
73
|
const workspaceScopeSupport = workspaces?.scope || null;
|
|
72
74
|
const aiClientFactory = createAssistantAiClientFactory({ appConfig: config, env, aiConnections });
|
|
73
75
|
const toolCatalog = createSurfaceAwareToolCatalog(actionCatalogue, { appConfig: config });
|
|
@@ -79,6 +81,7 @@ function createAssistantRuntime({
|
|
|
79
81
|
});
|
|
80
82
|
const transcriptService = createTranscriptService({ conversationsRepository, messagesRepository });
|
|
81
83
|
const chatService = createChatService({
|
|
84
|
+
turnRequests,
|
|
82
85
|
attachments,
|
|
83
86
|
aiClientFactory,
|
|
84
87
|
transcriptService,
|
|
@@ -92,7 +95,8 @@ function createAssistantRuntime({
|
|
|
92
95
|
repositories: Object.freeze({
|
|
93
96
|
config: assistantConfigRepository,
|
|
94
97
|
conversations: conversationsRepository,
|
|
95
|
-
messages: messagesRepository
|
|
98
|
+
messages: messagesRepository,
|
|
99
|
+
turnRequests
|
|
96
100
|
}),
|
|
97
101
|
services: Object.freeze({ chat: chatService, config: configService, transcript: transcriptService }),
|
|
98
102
|
aiClientFactory,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { assistantRuntimeConfig } from "../../shared/assistantRuntimeConfig.js";
|
|
3
|
+
|
|
4
|
+
function createRepository(knex) {
|
|
5
|
+
const table = assistantRuntimeConfig.turnRequestsTable;
|
|
6
|
+
|
|
7
|
+
function key(scope, messageId) {
|
|
8
|
+
return { actor_user_id: scope.actorUserId, scope_key: JSON.stringify([scope.surfaceId, scope.workspaceId]), message_sid: messageId };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function find(scope, messageId) {
|
|
12
|
+
const row = await knex(table).where(key(scope, messageId)).first();
|
|
13
|
+
return row ? { id: row.id, token: row.claim_token, request: JSON.parse(row.request_json),
|
|
14
|
+
response: row.response_json ? JSON.parse(row.response_json) : null, status: row.status } : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function claim(scope, request) {
|
|
18
|
+
const token = randomUUID();
|
|
19
|
+
await knex(table).insert({ ...key(scope, request.messageId), claim_token: token, request_json: JSON.stringify(request) })
|
|
20
|
+
.onConflict(["actor_user_id", "scope_key", "message_sid"]).ignore();
|
|
21
|
+
const record = await find(scope, request.messageId);
|
|
22
|
+
if (!record) throw new Error("Assistant request could not be recorded.");
|
|
23
|
+
return { ...record, acquired: record.token === token };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function update(claim, response, status = "running") {
|
|
27
|
+
const changed = await knex(table).where({ id: claim.id, claim_token: claim.token, status: "running" })
|
|
28
|
+
.update({ response_json: JSON.stringify(response), status, updated_at: new Date() });
|
|
29
|
+
if (!changed) throw new Error("Assistant request ownership was lost.");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return Object.freeze({ find, claim, update });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { createRepository };
|