@jskit-ai/assistant-core 0.2.7 → 0.2.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 (214) hide show
  1. package/examples/conversation/package.json +5 -5
  2. package/package.json +18 -7
  3. package/src/client/browser.js +21 -0
  4. package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
  5. package/src/client/conversation/AssistantComposerActions.vue +1 -1
  6. package/src/client/conversation/AssistantConversationElement.vue +14 -52
  7. package/src/client/conversation/AssistantGoalControl.vue +13 -10
  8. package/src/client/conversation/AssistantPromptInput.vue +6 -91
  9. package/src/client/conversation/AssistantTranscript.css +505 -0
  10. package/src/client/conversation/AssistantTranscript.vue +24 -1197
  11. package/src/client/conversation/attachmentFiles.js +158 -0
  12. package/src/client/conversation/attachmentQueueItems.js +121 -0
  13. package/src/client/conversation/attachmentUploadRows.js +265 -0
  14. package/src/client/conversation/conversationDisplay.js +54 -0
  15. package/src/client/conversation/messageDelivery.js +13 -9
  16. package/src/client/conversation/retainedConversation.js +14 -3
  17. package/src/client/conversation/submitText.js +13 -2
  18. package/src/client/conversation/transcriptEntries.js +225 -0
  19. package/src/client/conversation/useAssistantAttachments.js +86 -388
  20. package/src/client/conversation/useAssistantQuestions.js +16 -3
  21. package/src/client/conversation/useAssistantSuggestions.js +16 -2
  22. package/src/client/conversation/useScrollToBottom.js +17 -19
  23. package/src/client/conversation/useTextareaAutoGrow.js +115 -0
  24. package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
  25. package/src/client/lib/assistantApi.js +192 -161
  26. package/src/client/types.js +110 -0
  27. package/src/server/conversation/attachments.js +21 -6
  28. package/src/server/conversation/boundedToolLoop.js +1 -0
  29. package/src/server/conversation/claudeClient.js +71 -20
  30. package/src/server/conversation/claudeCommandRun.js +191 -0
  31. package/src/server/conversation/claudeHistory.js +7 -0
  32. package/src/server/conversation/claudeNativeTurn.js +708 -0
  33. package/src/server/conversation/claudeProcess.js +186 -112
  34. package/src/server/conversation/claudeProcessStart.js +214 -0
  35. package/src/server/conversation/claudePromptDelivery.js +183 -0
  36. package/src/server/conversation/claudeSessionCleanup.js +248 -0
  37. package/src/server/conversation/claudeTools.js +15 -3
  38. package/src/server/conversation/claudeTurn.js +212 -1388
  39. package/src/server/conversation/codexAccountReader.js +168 -0
  40. package/src/server/conversation/codexAuthPreflight.js +5 -4
  41. package/src/server/conversation/codexClient.js +18 -6
  42. package/src/server/conversation/codexConfiguration.js +8 -844
  43. package/src/server/conversation/codexConversationStops.js +377 -0
  44. package/src/server/conversation/codexConversationStore.js +119 -85
  45. package/src/server/conversation/codexConversationTurns.js +378 -0
  46. package/src/server/conversation/codexConversations.js +134 -866
  47. package/src/server/conversation/codexDetachedConversation.js +203 -0
  48. package/src/server/conversation/codexDetachedTurn.js +145 -104
  49. package/src/server/conversation/codexEvents.js +5 -200
  50. package/src/server/conversation/codexGoalCommands.js +14 -4
  51. package/src/server/conversation/codexHelperExecution.js +57 -57
  52. package/src/server/conversation/codexHelperThreadLedger.js +30 -288
  53. package/src/server/conversation/codexHelperThreadRecord.js +304 -0
  54. package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
  55. package/src/server/conversation/codexHelperThreads.js +94 -93
  56. package/src/server/conversation/codexHistoryAdapter.js +13 -8
  57. package/src/server/conversation/codexIsolation.js +672 -0
  58. package/src/server/conversation/codexLiveItems.js +379 -0
  59. package/src/server/conversation/codexMessageCommands.js +68 -286
  60. package/src/server/conversation/codexMessageDelivery.js +46 -37
  61. package/src/server/conversation/codexModelCatalog.js +86 -7
  62. package/src/server/conversation/codexNotificationObserver.js +81 -88
  63. package/src/server/conversation/codexNotificationQueue.js +21 -10
  64. package/src/server/conversation/codexObservationControl.js +105 -89
  65. package/src/server/conversation/codexOutput.js +84 -419
  66. package/src/server/conversation/codexProcess.js +29 -1053
  67. package/src/server/conversation/codexProcessLeader.js +1 -0
  68. package/src/server/conversation/codexPromptDispatch.js +241 -0
  69. package/src/server/conversation/codexProvider.js +147 -1807
  70. package/src/server/conversation/codexProviderAuth.js +348 -0
  71. package/src/server/conversation/codexProviderInvalidation.js +371 -0
  72. package/src/server/conversation/codexProviderInventories.js +437 -0
  73. package/src/server/conversation/codexProviderOwner.js +149 -957
  74. package/src/server/conversation/codexProviderRelease.js +465 -0
  75. package/src/server/conversation/codexProviderSelection.js +26 -15
  76. package/src/server/conversation/codexProviderThreads.js +698 -0
  77. package/src/server/conversation/codexProviderTurns.js +453 -0
  78. package/src/server/conversation/codexReasoning.js +32 -23
  79. package/src/server/conversation/codexRenewalCommands.js +18 -6
  80. package/src/server/conversation/codexRenewalThreads.js +7 -22
  81. package/src/server/conversation/codexResultSettlement.js +70 -88
  82. package/src/server/conversation/codexRunJournal.js +91 -97
  83. package/src/server/conversation/codexRuntime.js +426 -0
  84. package/src/server/conversation/codexRuntimeInventory.js +113 -0
  85. package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
  86. package/src/server/conversation/codexRuntimeStart.js +431 -0
  87. package/src/server/conversation/codexRuntimeStop.js +324 -0
  88. package/src/server/conversation/codexSessionCleanup.js +25 -10
  89. package/src/server/conversation/codexSessionReconciliation.js +21 -7
  90. package/src/server/conversation/codexThreadReadiness.js +49 -52
  91. package/src/server/conversation/codexThreadSnapshot.js +210 -0
  92. package/src/server/conversation/codexThreadStatus.js +48 -62
  93. package/src/server/conversation/codexTurn.js +171 -55
  94. package/src/server/conversation/codexTurnActivity.js +210 -0
  95. package/src/server/conversation/codexTurnControl.js +44 -48
  96. package/src/server/conversation/codexTurnEndings.js +610 -0
  97. package/src/server/conversation/codexTurnRecovery.js +97 -848
  98. package/src/server/conversation/codexTurnState.js +138 -111
  99. package/src/server/conversation/completedResponses.js +227 -0
  100. package/src/server/conversation/configuration.js +2 -2
  101. package/src/server/conversation/continuity.js +43 -6
  102. package/src/server/conversation/conversationDelivery.js +394 -0
  103. package/src/server/conversation/conversationEntries.js +365 -0
  104. package/src/server/conversation/conversationEvents.js +192 -0
  105. package/src/server/conversation/conversationGoals.js +187 -0
  106. package/src/server/conversation/conversationHandles.js +237 -0
  107. package/src/server/conversation/conversationReads.js +206 -0
  108. package/src/server/conversation/conversationRefusals.js +58 -0
  109. package/src/server/conversation/conversationReplacement.js +254 -0
  110. package/src/server/conversation/conversationSending.js +273 -0
  111. package/src/server/conversation/conversationStopping.js +121 -0
  112. package/src/server/conversation/conversationTurnDelivery.js +413 -0
  113. package/src/server/conversation/conversationTurns.js +511 -0
  114. package/src/server/conversation/deliveryTiming.js +25 -3
  115. package/src/server/conversation/fileStorage.js +11 -6
  116. package/src/server/conversation/hookBridge.js +14 -3
  117. package/src/server/conversation/index.js +1 -1
  118. package/src/server/conversation/localExecution.js +82 -625
  119. package/src/server/conversation/nativeCommandCallbacks.js +10 -0
  120. package/src/server/conversation/nativeConversationLifecycle.js +379 -0
  121. package/src/server/conversation/nativeHistoryExport.js +27 -18
  122. package/src/server/conversation/normalize.js +9 -0
  123. package/src/server/conversation/openCodeClient.js +295 -366
  124. package/src/server/conversation/openCodeNativeHistory.js +141 -0
  125. package/src/server/conversation/openCodeNativeSession.js +79 -0
  126. package/src/server/conversation/openCodeProcess.js +109 -53
  127. package/src/server/conversation/openCodeProcessOwner.js +565 -0
  128. package/src/server/conversation/openCodeRuntime.js +111 -1814
  129. package/src/server/conversation/openCodeSessionTurns.js +547 -0
  130. package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
  131. package/src/server/conversation/openCodeTools.js +28 -14
  132. package/src/server/conversation/openCodeTurn.js +80 -74
  133. package/src/server/conversation/openCodeTurnMonitor.js +388 -0
  134. package/src/server/conversation/processIdentity.js +582 -0
  135. package/src/server/conversation/provider.js +21 -6
  136. package/src/server/conversation/providers/api.js +268 -107
  137. package/src/server/conversation/providers/claude.js +29 -6
  138. package/src/server/conversation/providers/claudeDriver.js +325 -250
  139. package/src/server/conversation/providers/codex.js +69 -33
  140. package/src/server/conversation/providers/codexDriver.js +503 -1081
  141. package/src/server/conversation/providers/codexDriverConnection.js +331 -0
  142. package/src/server/conversation/providers/codexDriverTurns.js +464 -0
  143. package/src/server/conversation/providers/opencode.js +30 -4
  144. package/src/server/conversation/providers/opencodeDriver.js +321 -659
  145. package/src/server/conversation/providers/opencodeStandalone.js +475 -0
  146. package/src/server/conversation/providers/scoped.js +64 -19
  147. package/src/server/conversation/runtime.js +171 -2863
  148. package/src/server/conversation/storage.js +91 -50
  149. package/src/server/conversation/streams.js +15 -2
  150. package/src/server/conversation/structuredOutput.js +7 -11
  151. package/src/server/conversation/systemPrompt.js +13 -1
  152. package/src/server/conversation/tools.js +47 -15
  153. package/src/server/conversation/transcript.js +49 -20
  154. package/src/server/hosts/claudeTurn.js +2 -4
  155. package/src/server/hosts/codexConfiguration.js +6 -8
  156. package/src/server/hosts/codexEvents.js +4 -2
  157. package/src/server/hosts/codexProcess.js +1 -1
  158. package/src/server/hosts/codexProvider.js +11 -9
  159. package/src/server/hosts/codexTurn.js +3 -3
  160. package/src/server/hosts/openCodeClient.js +4 -2
  161. package/src/server/index.js +0 -2
  162. package/src/server/lib/aiConnectionClient.js +35 -25
  163. package/src/server/lib/ndjson.js +1 -0
  164. package/src/server/lib/providers/anthropicClient.js +15 -6
  165. package/src/server/lib/providers/common.js +10 -6
  166. package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
  167. package/src/server/lib/serviceToolCatalog.js +29 -283
  168. package/src/server/lib/serviceToolEntries.js +270 -0
  169. package/src/server/types.js +78 -0
  170. package/src/shared/assistantResource.js +0 -23
  171. package/src/shared/conversation/answerChoiceSugar.js +2 -14
  172. package/src/shared/conversation/messageInputSugar.js +18 -0
  173. package/src/shared/conversation/numberedQuestionSugar.js +6 -16
  174. package/src/shared/conversation/pagination.js +1 -1
  175. package/src/shared/conversation/questions.js +1 -0
  176. package/src/shared/conversation/streaming.js +1 -0
  177. package/src/shared/index.js +0 -1
  178. package/src/testing/conversationStorageContract.js +20 -0
  179. package/test/aiConnectionClient.test.js +42 -1
  180. package/test/apiConversationDriver.test.js +217 -38
  181. package/test/assistantBoundedToolLoop.test.js +1 -1
  182. package/test/claudeConversationSession.test.js +15 -11
  183. package/test/claudeInstructionResume.test.js +1 -1
  184. package/test/claudeProcess.test.js +9 -9
  185. package/test/claudeTurn.test.js +21 -8
  186. package/test/codexAccountChange.test.js +5 -3
  187. package/test/codexConversationSession.test.js +22 -21
  188. package/test/codexEvents.test.js +3 -1
  189. package/test/codexHelperDeletion.test.js +3 -7
  190. package/test/codexIsolation.test.js +6 -6
  191. package/test/codexModelCatalog.test.js +6 -7
  192. package/test/codexProcess.test.js +3 -3
  193. package/test/codexThreadSelection.test.js +4 -1
  194. package/test/codexTurn.test.js +29 -20
  195. package/test/conversationApiFacilities.test.js +11 -9
  196. package/test/conversationAttachments.test.js +3 -3
  197. package/test/conversationConfiguration.test.js +4 -4
  198. package/test/conversationNativeStorage.test.js +11 -10
  199. package/test/conversationReplacement.test.js +13 -13
  200. package/test/conversationRuntime.test.js +9 -5
  201. package/test/conversationSession.test.js +78 -22
  202. package/test/conversationTools.test.js +47 -26
  203. package/test/fileConversationStorage.test.js +10 -3
  204. package/test/fixtures/codexHelperThreadLedger.js +3 -3
  205. package/test/frameworkActions.test.js +33 -18
  206. package/test/localConversationExecution.test.js +4 -4
  207. package/test/localExecution.test.js +1 -1
  208. package/test/openCodeConnectionChange.test.js +3 -2
  209. package/test/openCodeRuntime.test.js +8 -7
  210. package/test/openCodeTools.test.js +1 -1
  211. package/test/opencodeConversationSession.test.js +4 -3
  212. package/test/serviceToolCatalog.test.js +3 -8
  213. package/src/server/lib/assistantCompletion.js +0 -190
  214. package/src/server/lib/assistantToolLoop.js +0 -259
@@ -1,4 +1,5 @@
1
1
  import { appendQueryString } from "@jskit-ai/kernel/shared/support";
2
+ import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
2
3
  import { encodeJsonApiResourceQueryObject } from "@jskit-ai/http-runtime/shared";
3
4
  import {
4
5
  ASSISTANT_CONVERSATIONS_TRANSPORT,
@@ -9,8 +10,6 @@ import {
9
10
  normalizeAssistantStreamEventType
10
11
  } from "../../shared/index.js";
11
12
 
12
- const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
13
-
14
13
  // A stream error event keeps its code and English message, plus the params and
15
14
  // messageCode useJskitText().serverMessage(error) needs to show it in the active language.
16
15
  function buildStreamEventError(event) {
@@ -60,8 +59,8 @@ function normalizeSurfaceHeaderValue(value) {
60
59
  return String(value || "").trim().toLowerCase();
61
60
  }
62
61
 
63
- function resolveAssistantRequestHeaders(resolveSurfaceId) {
64
- if (typeof resolveSurfaceId !== "function") {
62
+ function resolveAssistantRequestHeaders(resolveSurfaceId, sendsSurface) {
63
+ if (!sendsSurface) {
65
64
  return null;
66
65
  }
67
66
 
@@ -88,183 +87,215 @@ function resolveRequiredBasePath(resolveBasePath) {
88
87
  return resolved;
89
88
  }
90
89
 
91
- function createAssistantApi({ request, requestStream, resolveBasePath, resolveSurfaceId = null } = {}) {
90
+ /** Ignores a stream event when the caller passed no onEvent. */
91
+ function ignoreStreamEvent() {}
92
+
93
+ /**
94
+ * The assistant's HTTP API for one application: its conversations, their messages and goals, its chat stream and its
95
+ * settings, all under the base path `resolveBasePath()` returns.
96
+ * @param {{
97
+ * request: (url: string, options: object) => Promise<any>,
98
+ * requestStream?: (url: string, options: object, handlers: object) => Promise<void>,
99
+ * resolveBasePath: () => string,
100
+ * resolveSurfaceId?: (() => string) | null
101
+ * }} transport
102
+ */
103
+ function createAssistantApi({ request, requestStream, resolveBasePath, resolveSurfaceId }) {
104
+ // readable-check: allow method-probe - input validation: an application builds its API with the request() it sends through
92
105
  if (typeof request !== "function") {
93
106
  throw new Error("createAssistantApi requires request().");
94
107
  }
108
+ // readable-check: allow method-probe - optional operation: an API built without requestStream has no chat stream, so streamChat() rejects
109
+ const streams = typeof requestStream === "function";
110
+ // readable-check: allow method-probe - optional operation: an API built without resolveSurfaceId sends no surface header
111
+ const sendsSurface = typeof resolveSurfaceId === "function";
95
112
 
96
113
  function requestConversation(conversationId, suffix, options) {
97
114
  const basePath = resolveRequiredBasePath(resolveBasePath);
98
115
  const encodedId = encodeURIComponent(String(conversationId || "").trim());
99
- const headers = resolveAssistantRequestHeaders(resolveSurfaceId);
116
+ const headers = resolveAssistantRequestHeaders(resolveSurfaceId, sendsSurface);
100
117
  return request(`${basePath}/conversations/${encodedId}${suffix}`, {
101
118
  ...options,
102
119
  ...(headers ? { headers } : {})
103
120
  });
104
121
  }
105
122
 
106
- return Object.freeze({
107
- readConversation(conversationId, { signal, beforeTurnId, limit } = {}) {
108
- const params = new URLSearchParams();
109
- appendQueryParam(params, "beforeTurnId", beforeTurnId);
110
- appendQueryParam(params, "limit", limit);
111
- return requestConversation(conversationId, params.size ? `?${params}` : "", { method: "GET", signal });
112
- },
113
-
114
- sendConversationMessage(conversationId, payload, { signal } = {}) {
115
- return requestConversation(conversationId, "/messages", {
116
- method: "POST", signal,
117
- body: {
118
- messageId: payload.messageId,
119
- text: payload.text,
120
- ...(Object.hasOwn(payload, "data") ? { data: payload.data } : {}),
121
- ...(Object.hasOwn(payload, "attachmentIds") ? { attachmentIds: payload.attachmentIds } : {}),
122
- ...(Object.hasOwn(payload, "steer") ? { steer: payload.steer } : {})
123
- }
124
- });
125
- },
126
-
127
- cancelConversation(conversationId, { signal } = {}) {
128
- return requestConversation(conversationId, "/cancel", { method: "POST", signal });
129
- },
130
-
131
- inspectConversationDelivery(conversationId, messageId, { signal } = {}) {
132
- const encodedMessageId = encodeURIComponent(String(messageId || "").trim());
133
- return requestConversation(conversationId, `/deliveries/${encodedMessageId}/inspect`, { method: "POST", signal });
134
- },
135
-
136
- cancelConversationDelivery(conversationId, messageId, { signal } = {}) {
137
- const encodedMessageId = encodeURIComponent(String(messageId || "").trim());
138
- return requestConversation(conversationId, `/deliveries/${encodedMessageId}/cancel`, { method: "POST", signal });
139
- },
140
-
141
- readConversationGoal(conversationId, { signal } = {}) {
142
- return requestConversation(conversationId, "/goal", { method: "GET", signal });
143
- },
144
-
145
- updateConversationGoal(conversationId, payload, { signal } = {}) {
146
- const body = {};
147
- for (const key of ["action", "expectedSegmentId", "expectedGoalId", "messageId", "objective", "tokenBudget", "attachmentIds"]) {
148
- if (Object.hasOwn(payload, key)) body[key] = payload[key];
123
+ function readConversation(conversationId, { signal, beforeTurnId, limit } = {}) {
124
+ const params = new URLSearchParams();
125
+ appendQueryParam(params, "beforeTurnId", beforeTurnId);
126
+ appendQueryParam(params, "limit", limit);
127
+ return requestConversation(conversationId, params.size ? `?${params}` : "", { method: "GET", signal });
128
+ }
129
+
130
+ function sendConversationMessage(conversationId, payload, { signal } = {}) {
131
+ return requestConversation(conversationId, "/messages", {
132
+ method: "POST", signal,
133
+ body: {
134
+ messageId: payload.messageId,
135
+ text: payload.text,
136
+ ...(Object.hasOwn(payload, "data") ? { data: payload.data } : {}),
137
+ ...(Object.hasOwn(payload, "attachmentIds") ? { attachmentIds: payload.attachmentIds } : {}),
138
+ ...(Object.hasOwn(payload, "steer") ? { steer: payload.steer } : {})
149
139
  }
150
- return requestConversation(conversationId, "/goal", { method: "POST", signal, body });
151
- },
152
-
153
- configureConversation(conversationId, configuration, { signal } = {}) {
154
- return requestConversation(conversationId, "/configuration", {
155
- method: "PATCH", signal, body: { configuration }
156
- });
157
- },
158
-
159
- selectConversation(conversationId, selection, { signal } = {}) {
160
- return requestConversation(conversationId, "/selection", {
161
- method: "POST", signal, body: { selection }
162
- });
163
- },
164
-
165
- replaceConversation(conversationId, replacement, { signal } = {}) {
166
- return requestConversation(conversationId, "/replacement", {
167
- method: "POST", signal, body: { replacement }
168
- });
169
- },
170
-
171
- async streamChat(payload, { signal, onEvent, onMalformedLine, rejectOnErrorEvent = true } = {}) {
172
- if (typeof requestStream !== "function") throw new TypeError("streamChat requires requestStream().");
173
- const basePath = resolveRequiredBasePath(resolveBasePath);
174
- let streamEventError = null;
175
- const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId);
176
-
177
- const streamHandlers = {
178
- onEvent(event) {
179
- const eventType = normalizeAssistantStreamEventType(event?.type, "");
180
- if (rejectOnErrorEvent && eventType === ASSISTANT_STREAM_EVENT_TYPES.ERROR && !streamEventError) {
181
- streamEventError = buildStreamEventError(event);
182
- }
183
-
184
- if (typeof onEvent === "function") {
185
- onEvent(event);
186
- }
140
+ });
141
+ }
142
+
143
+ function cancelConversation(conversationId, { signal } = {}) {
144
+ return requestConversation(conversationId, "/cancel", { method: "POST", signal });
145
+ }
146
+
147
+ function inspectConversationDelivery(conversationId, messageId, { signal } = {}) {
148
+ const encodedMessageId = encodeURIComponent(String(messageId || "").trim());
149
+ return requestConversation(conversationId, `/deliveries/${encodedMessageId}/inspect`, { method: "POST", signal });
150
+ }
151
+
152
+ function cancelConversationDelivery(conversationId, messageId, { signal } = {}) {
153
+ const encodedMessageId = encodeURIComponent(String(messageId || "").trim());
154
+ return requestConversation(conversationId, `/deliveries/${encodedMessageId}/cancel`, { method: "POST", signal });
155
+ }
156
+
157
+ function readConversationGoal(conversationId, { signal } = {}) {
158
+ return requestConversation(conversationId, "/goal", { method: "GET", signal });
159
+ }
160
+
161
+ function updateConversationGoal(conversationId, payload, { signal } = {}) {
162
+ const body = {};
163
+ for (const key of ["action", "expectedSegmentId", "expectedGoalId", "messageId", "objective", "tokenBudget", "attachmentIds"]) {
164
+ if (Object.hasOwn(payload, key)) body[key] = payload[key];
165
+ }
166
+ return requestConversation(conversationId, "/goal", { method: "POST", signal, body });
167
+ }
168
+
169
+ function configureConversation(conversationId, configuration, { signal } = {}) {
170
+ return requestConversation(conversationId, "/configuration", {
171
+ method: "PATCH", signal, body: { configuration }
172
+ });
173
+ }
174
+
175
+ function selectConversation(conversationId, selection, { signal } = {}) {
176
+ return requestConversation(conversationId, "/selection", {
177
+ method: "POST", signal, body: { selection }
178
+ });
179
+ }
180
+
181
+ function replaceConversation(conversationId, replacement, { signal } = {}) {
182
+ return requestConversation(conversationId, "/replacement", {
183
+ method: "POST", signal, body: { replacement }
184
+ });
185
+ }
186
+
187
+ async function streamChat(payload, { signal, onEvent = ignoreStreamEvent, onMalformedLine, rejectOnErrorEvent = true } = {}) {
188
+ if (!streams) throw new TypeError("streamChat requires requestStream().");
189
+ const basePath = resolveRequiredBasePath(resolveBasePath);
190
+ let streamEventError = null;
191
+ const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId, sendsSurface);
192
+
193
+ const streamHandlers = {
194
+ onEvent(event) {
195
+ const eventType = normalizeAssistantStreamEventType(event?.type, "");
196
+ if (rejectOnErrorEvent && eventType === ASSISTANT_STREAM_EVENT_TYPES.ERROR && !streamEventError) {
197
+ streamEventError = buildStreamEventError(event);
187
198
  }
199
+
200
+ onEvent(event);
201
+ }
202
+ };
203
+
204
+ if (typeof onMalformedLine === "function") {
205
+ streamHandlers.onMalformedLine = (line, parseError) => {
206
+ onMalformedLine(line, parseError);
188
207
  };
208
+ }
209
+
210
+ await requestStream(
211
+ `${basePath}/chat/stream`,
212
+ {
213
+ method: "POST",
214
+ ...(requestHeaders ? { headers: requestHeaders } : {}),
215
+ body: payload,
216
+ signal
217
+ },
218
+ streamHandlers
219
+ );
220
+
221
+ if (streamEventError) {
222
+ throw streamEventError;
223
+ }
224
+ }
189
225
 
190
- if (typeof onMalformedLine === "function") {
191
- streamHandlers.onMalformedLine = (line, parseError) => {
192
- onMalformedLine(line, parseError);
193
- };
226
+ function listConversations(query = {}) {
227
+ const basePath = resolveRequiredBasePath(resolveBasePath);
228
+ const params = createAssistantQueryParams(query, ASSISTANT_CONVERSATIONS_TRANSPORT);
229
+ const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId, sendsSurface);
230
+
231
+ return request(
232
+ appendQueryString(`${basePath}/conversations`, params.toString()),
233
+ {
234
+ ...(requestHeaders ? { headers: requestHeaders } : {}),
235
+ transport: ASSISTANT_CONVERSATIONS_TRANSPORT
194
236
  }
237
+ );
238
+ }
195
239
 
196
- await requestStream(
197
- `${basePath}/chat/stream`,
198
- {
199
- method: "POST",
200
- ...(requestHeaders ? { headers: requestHeaders } : {}),
201
- body: payload,
202
- signal
203
- },
204
- streamHandlers
205
- );
206
-
207
- if (streamEventError) {
208
- throw streamEventError;
240
+ function getConversationMessages(conversationId, query = {}) {
241
+ const basePath = resolveRequiredBasePath(resolveBasePath);
242
+ const encodedConversationId = encodeURIComponent(String(conversationId || "").trim());
243
+ const params = createAssistantQueryParams(query, ASSISTANT_CONVERSATION_MESSAGES_TRANSPORT);
244
+ const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId, sendsSurface);
245
+
246
+ return request(
247
+ appendQueryString(`${basePath}/conversations/${encodedConversationId}/messages`, params.toString()),
248
+ {
249
+ ...(requestHeaders ? { headers: requestHeaders } : {}),
250
+ transport: ASSISTANT_CONVERSATION_MESSAGES_TRANSPORT
209
251
  }
210
- },
211
-
212
- listConversations(query = {}) {
213
- const basePath = resolveRequiredBasePath(resolveBasePath);
214
- const params = createAssistantQueryParams(query, ASSISTANT_CONVERSATIONS_TRANSPORT);
215
- const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId);
216
-
217
- return request(
218
- appendQueryString(`${basePath}/conversations`, params.toString()),
219
- {
220
- ...(requestHeaders ? { headers: requestHeaders } : {}),
221
- transport: ASSISTANT_CONVERSATIONS_TRANSPORT
222
- }
223
- );
224
- },
225
-
226
- getConversationMessages(conversationId, query = {}) {
227
- const basePath = resolveRequiredBasePath(resolveBasePath);
228
- const encodedConversationId = encodeURIComponent(String(conversationId || "").trim());
229
- const params = createAssistantQueryParams(query, ASSISTANT_CONVERSATION_MESSAGES_TRANSPORT);
230
- const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId);
231
-
232
- return request(
233
- appendQueryString(`${basePath}/conversations/${encodedConversationId}/messages`, params.toString()),
234
- {
235
- ...(requestHeaders ? { headers: requestHeaders } : {}),
236
- transport: ASSISTANT_CONVERSATION_MESSAGES_TRANSPORT
237
- }
238
- );
239
- },
240
-
241
- getSettings() {
242
- const basePath = resolveRequiredBasePath(resolveBasePath);
243
- const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId);
244
-
245
- return request(
246
- `${basePath}/settings`,
247
- {
248
- ...(requestHeaders ? { headers: requestHeaders } : {}),
249
- transport: ASSISTANT_SETTINGS_TRANSPORT
250
- }
251
- );
252
- },
253
-
254
- updateSettings(payload = {}) {
255
- const basePath = resolveRequiredBasePath(resolveBasePath);
256
- const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId);
257
-
258
- return request(
259
- `${basePath}/settings`,
260
- {
261
- method: "PATCH",
262
- ...(requestHeaders ? { headers: requestHeaders } : {}),
263
- body: payload,
264
- transport: ASSISTANT_SETTINGS_UPDATE_TRANSPORT
265
- }
266
- );
267
- }
252
+ );
253
+ }
254
+
255
+ function getSettings() {
256
+ const basePath = resolveRequiredBasePath(resolveBasePath);
257
+ const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId, sendsSurface);
258
+
259
+ return request(
260
+ `${basePath}/settings`,
261
+ {
262
+ ...(requestHeaders ? { headers: requestHeaders } : {}),
263
+ transport: ASSISTANT_SETTINGS_TRANSPORT
264
+ }
265
+ );
266
+ }
267
+
268
+ function updateSettings(payload = {}) {
269
+ const basePath = resolveRequiredBasePath(resolveBasePath);
270
+ const requestHeaders = resolveAssistantRequestHeaders(resolveSurfaceId, sendsSurface);
271
+
272
+ return request(
273
+ `${basePath}/settings`,
274
+ {
275
+ method: "PATCH",
276
+ ...(requestHeaders ? { headers: requestHeaders } : {}),
277
+ body: payload,
278
+ transport: ASSISTANT_SETTINGS_UPDATE_TRANSPORT
279
+ }
280
+ );
281
+ }
282
+
283
+ return Object.freeze({
284
+ readConversation,
285
+ sendConversationMessage,
286
+ cancelConversation,
287
+ inspectConversationDelivery,
288
+ cancelConversationDelivery,
289
+ readConversationGoal,
290
+ updateConversationGoal,
291
+ configureConversation,
292
+ selectConversation,
293
+ replaceConversation,
294
+ streamChat,
295
+ listConversations,
296
+ getConversationMessages,
297
+ getSettings,
298
+ updateSettings
268
299
  });
269
300
  }
270
301
 
@@ -0,0 +1,110 @@
1
+ // The values the assistant's client code hands out and receives across files: what its composables and factories
2
+ // return, and the contracts an application implements to supply uploads, a goal and a whole conversation.
3
+
4
+ /** @typedef {ReturnType<typeof import("./conversation/useAssistantAttachments.js").useAssistantAttachments>} AssistantAttachments */
5
+ /** @typedef {ReturnType<typeof import("./conversation/useAssistantSuggestions.js").useAssistantSuggestions>} AssistantSuggestions */
6
+ /** @typedef {ReturnType<typeof import("./conversation/useAssistantQuestions.js").useAssistantQuestions>} AssistantQuestions */
7
+ /** @typedef {ReturnType<typeof import("./conversation/messageDelivery.js").createAssistantMessageDelivery>} AssistantMessageDelivery */
8
+ /** @typedef {ReturnType<typeof import("./lib/assistantApi.js").createAssistantApi>} AssistantApi */
9
+
10
+ /**
11
+ * What the application's storage returns for one uploaded file, completed by the queue with its client id, session
12
+ * and size.
13
+ * @typedef {object} AssistantAttachmentReceipt
14
+ * @property {string} attachmentId
15
+ * @property {string} clientId
16
+ * @property {string} fileName
17
+ * @property {string} sessionId
18
+ * @property {number} size
19
+ */
20
+
21
+ /**
22
+ * Uploads one file for a session. It returns the stored file's receipt, as a promise, or as `{ promise }` with the
23
+ * `abort()` or `cancel()` that stops the upload.
24
+ * @typedef {(
25
+ * sessionId: string,
26
+ * file: File,
27
+ * options: { onProgress: (progress: unknown, total?: number) => void, signal: AbortSignal }
28
+ * ) => unknown} AssistantAttachmentUpload
29
+ */
30
+
31
+ /**
32
+ * Deletes an uploaded file no message will send. A response with `ok: false` reports a failed deletion.
33
+ * @typedef {(sessionId: string, attachmentId: string) => unknown} AssistantAttachmentDeletion
34
+ */
35
+
36
+ /**
37
+ * The goal a conversation is working towards, as AssistantGoalControl shows and changes it.
38
+ * @typedef {object} AssistantGoalState
39
+ * @property {boolean} [enabled] False hides the control's menu and stops its clock.
40
+ * @property {boolean} [loading]
41
+ * @property {boolean} [available] False while the conversation offers no goals.
42
+ * @property {string} [action] "set" while a new goal is starting.
43
+ * @property {boolean} [working]
44
+ * @property {boolean} [pending]
45
+ * @property {boolean} [stopPending]
46
+ * @property {boolean} [pauseBlocked] True when a blocked goal can still be paused.
47
+ * @property {boolean} [pauseInterruptsTurn]
48
+ * @property {boolean} [cancelInterruptsTurn]
49
+ * @property {boolean} [tokenBudgetSupported]
50
+ * @property {string} [error]
51
+ * @property {{ status: string, objective?: string, elapsedSeconds?: number, sampledAt?: number, createdAt?: string } | null} [goal]
52
+ * @property {((input: { objective: string, tokenBudget?: number }) => unknown) | null} [set]
53
+ * Starts a goal; absent while starting one is not allowed, so Start stays disabled.
54
+ * @property {() => unknown} [pause]
55
+ * @property {() => unknown} [resume]
56
+ * @property {() => unknown} [cancel]
57
+ */
58
+
59
+ /**
60
+ * What an application supplies to AssistantConversationElement. Only `conversation` is required; each other part
61
+ * adds its controls when present.
62
+ * @typedef {object} AssistantConversationAdapter
63
+ * @property {Record<string, any> & { turns: any[] }} conversation The transcript's props, plus its preview messages
64
+ * (`previewMessage`, `previewMessages`) and the reply still streaming (`interimReply`).
65
+ * @property {Record<string, any>} [composer] The prompt input's props, plus `draft`, `canSend`, `canStop`,
66
+ * `stopPending`, `queueWhileSending` and the `payload` every message carries.
67
+ * @property {AssistantConversationActions} [actions]
68
+ * @property {AssistantMessageDelivery} [delivery] Present when the element sends and tracks messages itself.
69
+ * @property {import("vue").UnwrapNestedRefs<AssistantAttachments> & {
70
+ * enabled?: boolean,
71
+ * accept?: string,
72
+ * open?: (attachment: unknown) => void
73
+ * }} [attachments]
74
+ * @property {import("vue").UnwrapNestedRefs<AssistantSuggestions>} [suggestions]
75
+ * @property {import("vue").UnwrapNestedRefs<AssistantQuestions>} [questions]
76
+ * @property {AssistantModelChoices} [models]
77
+ * @property {AssistantGoalState} [goal]
78
+ * @property {{ label: string }} [activity] Replaces the sending and working status line.
79
+ */
80
+
81
+ /**
82
+ * What the application does for the conversation. The element sends, resends, edits and cancels through its
83
+ * delivery when the application supplies no action of its own; it leaves out the controls of the others.
84
+ * @typedef {object} AssistantConversationActions
85
+ * @property {(draft: string) => void} [setDraft]
86
+ * @property {(input: { configuration: object, attachments: object[] }) => unknown} [submit]
87
+ * @property {(messageId: string) => unknown} [resend]
88
+ * @property {(messageId: string) => unknown} [cancel]
89
+ * @property {(messageId: string) => unknown} [edit]
90
+ * @property {() => unknown} [stop]
91
+ * @property {(messageId: string) => unknown} [checkDelivery]
92
+ * @property {(request: { complete: (result?: { changed?: boolean }) => void }) => unknown} [loadMore]
93
+ * @property {() => unknown} [reload]
94
+ * @property {(link: unknown) => unknown} [openLink]
95
+ * @property {(configuration: object) => unknown} [updateConfiguration]
96
+ */
97
+
98
+ /**
99
+ * The model menu's rows and choices: AssistantModelControl's props, and what applying or changing them does.
100
+ * @typedef {Record<string, any> & {
101
+ * enabled?: boolean,
102
+ * apply: () => unknown,
103
+ * selectModel?: (modelId: string) => unknown,
104
+ * selectProvider?: (providerId: string) => unknown,
105
+ * selectVariant?: (variantId: string) => unknown,
106
+ * reload?: () => unknown
107
+ * }} AssistantModelChoices
108
+ */
109
+
110
+ export {};
@@ -1,11 +1,20 @@
1
+ import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
1
2
  import path from "node:path";
2
3
  import { ASSISTANT_ATTACHMENT_ID_MAX_LENGTH, ASSISTANT_ATTACHMENT_MAX_ITEMS, conversationAttachmentManifest } from "../../shared/conversation/attachments.js";
3
4
 
4
5
  // messageCode names the sentence; the shared code stays for callers.
5
6
  function invalid(message, { messageCode, messageParams = {} }) {
6
- return Object.assign(new Error(message), { code: "conversation_invalid_attachments", statusCode: 400, messageCode, messageParams });
7
+ return new AppError(400, message, { code: "conversation_invalid_attachments", messageCode, messageParams });
7
8
  }
8
9
 
10
+ /** The attachments of an application that offers none: every file it is asked to resolve is refused. */
11
+ export const NO_ATTACHMENTS = Object.freeze({
12
+ resolve() {
13
+ throw new AppError(400, "File attachments are not configured for this conversation.", { code: "conversation_attachments_not_configured",
14
+ messageCode: "assistant.conversation.attachmentsNotConfigured" });
15
+ }
16
+ });
17
+
9
18
  export function conversationAttachmentIds(value = []) {
10
19
  if (!Array.isArray(value) || value.length > ASSISTANT_ATTACHMENT_MAX_ITEMS ||
11
20
  value.some(id => typeof id !== "string" || !id.trim() || id.length > ASSISTANT_ATTACHMENT_ID_MAX_LENGTH) ||
@@ -17,15 +26,21 @@ export function conversationAttachmentIds(value = []) {
17
26
  return [...value];
18
27
  }
19
28
 
20
- /** Resolve app-owned files for one dispatch. Only receipts enter the transcript. */
29
+ /**
30
+ * Resolve app-owned files for one dispatch. Only receipts enter the transcript.
31
+ * @param {object} options
32
+ * @param {import("../types.js").ConversationAttachments} options.attachments The application's resolver, or NO_ATTACHMENTS.
33
+ * @param {object} options.context
34
+ * @param {string} options.conversationId
35
+ * @param {AbortSignal} options.signal
36
+ * @param {() => Promise<void>} options.authorize Rechecks admission before each resolver call.
37
+ * @param {readonly string[]} options.types The file types the engine accepts.
38
+ * @param {number} options.maximumBytes
39
+ */
21
40
  export function createConversationAttachmentReader({ attachments, context, conversationId, signal, authorize, types, maximumBytes }) {
22
41
  let bytes = 0;
23
42
  return async references => {
24
43
  if (!references?.length) return { attachments: [], content: [], localFiles: [], attachmentManifest: "" };
25
- if (typeof attachments?.resolve !== "function") {
26
- throw Object.assign(new Error("File attachments are not configured for this conversation."), { code: "conversation_unsupported", statusCode: 400,
27
- messageCode: "assistant.conversation.attachmentsNotConfigured", messageParams: {} });
28
- }
29
44
  const receipts = [];
30
45
  const content = [];
31
46
  const localFiles = [];
@@ -109,6 +109,7 @@ async function runBoundedAssistantToolLoop({ prompt, signal, policy, complete, o
109
109
  if (decision !== "end") throw new TypeError("Invalid completed-response settlement decision.");
110
110
  return response.text;
111
111
  }
112
+ // readable-check: allow method-probe - input validation: an application's completed response carries its durable tools
112
113
  if (typeof result?.tools?.execute !== "function") {
113
114
  throw new TypeError("Completed application operations require the current durable tools.");
114
115
  }