@jskit-ai/assistant-core 0.1.244 → 0.2.1
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/CHANGELOG.md +539 -0
- package/examples/conversation/package.json +5 -5
- package/locales/en.json +383 -0
- package/package.json +7 -6
- package/src/client/components/AssistantSettingsFormCard.vue +4 -1
- package/src/client/conversation/AssistantAttachmentPreview.vue +6 -3
- package/src/client/conversation/AssistantAttachmentQueue.vue +36 -37
- package/src/client/conversation/AssistantComposerActions.vue +6 -3
- package/src/client/conversation/AssistantComposerSupport.vue +8 -5
- package/src/client/conversation/AssistantConversationElement.vue +16 -13
- package/src/client/conversation/AssistantConversationStatus.vue +5 -2
- package/src/client/conversation/AssistantGoalControl.vue +22 -16
- package/src/client/conversation/AssistantMessageAttachments.vue +7 -4
- package/src/client/conversation/AssistantModelControl.vue +23 -20
- package/src/client/conversation/AssistantProgress.vue +6 -3
- package/src/client/conversation/AssistantQuestionInputs.vue +9 -6
- package/src/client/conversation/AssistantTranscript.vue +39 -28
- package/src/client/conversation/LongTextPreviewBlocks.vue +10 -2
- package/src/client/conversation/attachmentSize.js +38 -0
- package/src/client/conversation/messageDelivery.js +14 -5
- package/src/client/conversation/submitText.js +11 -4
- package/src/client/conversation/useAssistantAttachments.js +40 -30
- package/src/client/conversation/useAssistantSuggestions.js +4 -1
- package/src/client/i18n.js +6 -0
- package/src/client/lib/assistantApi.js +14 -2
- package/src/server/conversation/attachments.js +24 -14
- package/src/server/conversation/claudeProcess.js +4 -2
- package/src/server/conversation/claudeTurn.js +31 -12
- package/src/server/conversation/codexConversations.js +4 -0
- package/src/server/conversation/codexGoalCommands.js +9 -9
- package/src/server/conversation/codexHelperExecution.js +4 -0
- package/src/server/conversation/codexHelperThreads.js +2 -0
- package/src/server/conversation/codexMessageCommands.js +7 -1
- package/src/server/conversation/codexTurnControl.js +2 -1
- package/src/server/conversation/codexTurnState.js +2 -0
- package/src/server/conversation/continuity.js +16 -6
- package/src/server/conversation/runtime.js +193 -141
- package/src/server/lib/ndjson.js +19 -1
- package/src/server/lib/providers/common.js +7 -2
- package/src/shared/conversation/numberedQuestionSugar.js +7 -1
- package/src/shared/conversation/turns.js +10 -2
- package/test/claudeTurn.test.js +17 -0
- package/test/i18nCatalog.test.js +22 -0
package/src/server/lib/ndjson.js
CHANGED
|
@@ -27,6 +27,23 @@ function endNdjson(reply) {
|
|
|
27
27
|
reply.raw.end();
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function isRecord(value) {
|
|
31
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The client renders code, params or messageCode in the user's language and
|
|
35
|
+
// falls back to message, like an HTTP error response.
|
|
36
|
+
function streamMessageFields(error, hidden) {
|
|
37
|
+
if (hidden) {
|
|
38
|
+
return { params: {}, messageCode: "assistant.stream.failed", messageParams: {} };
|
|
39
|
+
}
|
|
40
|
+
const params = isRecord(error?.params) ? error.params : {};
|
|
41
|
+
if (error?.messageCode) {
|
|
42
|
+
return { params, messageCode: String(error.messageCode), messageParams: isRecord(error.messageParams) ? error.messageParams : {} };
|
|
43
|
+
}
|
|
44
|
+
return error?.message ? { params } : { params, messageCode: "assistant.stream.requestFailed", messageParams: {} };
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
function mapStreamError(error) {
|
|
31
48
|
const status = Number(error?.status || error?.statusCode || 500);
|
|
32
49
|
const safeStatus = Number.isInteger(status) && status >= 400 && status <= 599 ? status : 500;
|
|
@@ -34,7 +51,8 @@ function mapStreamError(error) {
|
|
|
34
51
|
return Object.freeze({
|
|
35
52
|
code: String(error?.code || "assistant_stream_failed").trim() || "assistant_stream_failed",
|
|
36
53
|
message: safeStatus >= 500 ? "Assistant stream failed." : String(error?.message || "Request failed."),
|
|
37
|
-
status: safeStatus
|
|
54
|
+
status: safeStatus,
|
|
55
|
+
...streamMessageFields(error, safeStatus >= 500)
|
|
38
56
|
});
|
|
39
57
|
}
|
|
40
58
|
|
|
@@ -58,7 +58,7 @@ function normalizeOptionalHttpUrl(value, { context = "assistant baseUrl" } = {})
|
|
|
58
58
|
|
|
59
59
|
function createDisabledClient({ provider = DEFAULT_AI_PROVIDER, model = "" } = {}) {
|
|
60
60
|
const disabledError = () => {
|
|
61
|
-
throw new AppError(503, "Assistant provider is not configured.");
|
|
61
|
+
throw new AppError(503, "Assistant provider is not configured.", { code: "assistant.provider.notConfigured" });
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
return Object.freeze({
|
|
@@ -80,9 +80,14 @@ function createProviderRequestError({ status = 500, code = "assistant_provider_f
|
|
|
80
80
|
const normalizedCode = normalizeText(code) || "assistant_provider_failed";
|
|
81
81
|
const normalizedMessage =
|
|
82
82
|
normalizeText(message) || (safeStatus >= 500 ? "Assistant provider request failed." : "Request failed.");
|
|
83
|
+
// A provider's own message is shown as sent; JSKIT's fallback sentences are named.
|
|
84
|
+
const fallbackMessage = normalizeText(message) ? {} : safeStatus >= 500
|
|
85
|
+
? { messageCode: "assistant.provider.requestFailed" }
|
|
86
|
+
: { messageCode: "assistant.provider.requestRejected" };
|
|
83
87
|
|
|
84
88
|
return new AppError(safeStatus, normalizedMessage, {
|
|
85
|
-
code: normalizedCode
|
|
89
|
+
code: normalizedCode,
|
|
90
|
+
...fallbackMessage
|
|
86
91
|
});
|
|
87
92
|
}
|
|
88
93
|
|
|
@@ -264,6 +264,10 @@ function numberedQuestionSugarForMessageInput({
|
|
|
264
264
|
return parseNumberedQuestionPrompt(message);
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
// requiredMessage stays English; a client shows it in the active language with
|
|
268
|
+
// serverMessage({ message: requiredMessage, messageCode: requiredMessageCode, messageParams: requiredMessageParams }).
|
|
269
|
+
const ANSWER_REQUIRED = Object.freeze({ messageCode: "assistant.questions.answerRequired" });
|
|
270
|
+
|
|
267
271
|
function numberedQuestionInputFields(questions = [], options = {}) {
|
|
268
272
|
const autocomplete = String(options?.autocomplete || "").trim();
|
|
269
273
|
const density = String(options?.density || "").trim();
|
|
@@ -274,7 +278,9 @@ function numberedQuestionInputFields(questions = [], options = {}) {
|
|
|
274
278
|
label: question.label,
|
|
275
279
|
name: question.name,
|
|
276
280
|
required: true,
|
|
277
|
-
requiredMessage: `Answer question ${question.number}
|
|
281
|
+
requiredMessage: `Answer question ${question.number}.`,
|
|
282
|
+
requiredMessageCode: ANSWER_REQUIRED.messageCode,
|
|
283
|
+
requiredMessageParams: { number: question.number }
|
|
278
284
|
}));
|
|
279
285
|
}
|
|
280
286
|
|
|
@@ -9,6 +9,13 @@ export function conversationTurnMessages(turn) {
|
|
|
9
9
|
].filter(Boolean);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
// A finished turn without text shows its status. The English stays in text; textCode
|
|
13
|
+
// lets a client show it in the user's language.
|
|
14
|
+
const STATUS_TEXTS = Object.freeze({
|
|
15
|
+
interrupted: Object.freeze({ text: "Stopped.", messageCode: "assistant.turn.stopped" }),
|
|
16
|
+
failed: Object.freeze({ text: "Assistant stopped with an error.", messageCode: "assistant.turn.failed" })
|
|
17
|
+
});
|
|
18
|
+
|
|
12
19
|
/** Adapt ordered messages to the transcript's turn model without changing their metadata. */
|
|
13
20
|
export function conversationTurnsFromMessages(messages = []) {
|
|
14
21
|
const turns = [];
|
|
@@ -30,8 +37,9 @@ export function conversationTurnsFromMessages(messages = []) {
|
|
|
30
37
|
for (const [progressIndex, progress] of (message.progressUpdates || []).entries()) {
|
|
31
38
|
current.messages.push({ ...progress, role: progress.role || "thinking", messageId: `${message.messageId}:progress:${progress.id || progressIndex}` });
|
|
32
39
|
}
|
|
33
|
-
if (!message.text &&
|
|
34
|
-
message.text = message.status
|
|
40
|
+
if (!message.text && Object.hasOwn(STATUS_TEXTS, message.status)) {
|
|
41
|
+
message.text = STATUS_TEXTS[message.status].text;
|
|
42
|
+
message.textCode = STATUS_TEXTS[message.status].messageCode;
|
|
35
43
|
}
|
|
36
44
|
current.assistant = message;
|
|
37
45
|
}
|
package/test/claudeTurn.test.js
CHANGED
|
@@ -916,6 +916,23 @@ test("Claude active status still rejects account replacement", async t => {
|
|
|
916
916
|
assert.equal(f.entry.process, null);
|
|
917
917
|
});
|
|
918
918
|
|
|
919
|
+
test("Claude account change stops running processes without reopening saved conversations", async t => {
|
|
920
|
+
const f = await publishedClaudeFixture(t);
|
|
921
|
+
f.context.sessionId = "session-a";
|
|
922
|
+
await f.send();
|
|
923
|
+
const closedTerminals = [];
|
|
924
|
+
// A saved conversation can belong to an archived session whose context no longer resolves.
|
|
925
|
+
const application = {
|
|
926
|
+
readContext: async () => { throw new Error("A saved conversation was reopened"); },
|
|
927
|
+
terminals: { close: async sessionId => { closedTerminals.push(sessionId); return { ok: true, closed: 0 }; } }
|
|
928
|
+
};
|
|
929
|
+
const result = await f.owner.invalidateRuntimes({ provider: "claude", reason: "claude-auth-change" }, application,
|
|
930
|
+
{ queries: { invalidate: async () => {} }, failure: error => { throw error; } });
|
|
931
|
+
assert.equal(result.ok, true);
|
|
932
|
+
assert.equal(f.entry.process, null, "the running process stopped");
|
|
933
|
+
assert.deepEqual(closedTerminals, ["session-a"], "the session's Claude terminals closed");
|
|
934
|
+
});
|
|
935
|
+
|
|
919
936
|
test("Claude continuation supersedes an older completion checkpoint", { timeout: 10_000 }, async t => {
|
|
920
937
|
const f = await publishedClaudeFixture(t);
|
|
921
938
|
await f.send();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { evaluateI18nCatalogContract } from "../../../tooling/test-support/i18nCatalogContract.mjs";
|
|
6
|
+
|
|
7
|
+
const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const REPO_ROOT = path.resolve(TEST_DIRECTORY, "..", "..", "..");
|
|
9
|
+
|
|
10
|
+
test("assistant-core English catalog matches its source", () => {
|
|
11
|
+
const { problems } = evaluateI18nCatalogContract({
|
|
12
|
+
repoRoot: REPO_ROOT,
|
|
13
|
+
packageDir: path.join(REPO_ROOT, "packages", "assistant-core"),
|
|
14
|
+
// runOpenCodeConversationTurn builds these codes as `assistant_opencode_turn_${reason}`.
|
|
15
|
+
externalCodes: [
|
|
16
|
+
"assistant_opencode_turn_thread_mismatch",
|
|
17
|
+
"assistant_opencode_turn_fresh_thread_required",
|
|
18
|
+
"assistant_opencode_turn_unrelated_history"
|
|
19
|
+
]
|
|
20
|
+
});
|
|
21
|
+
assert.deepEqual(problems, []);
|
|
22
|
+
});
|