@jskit-ai/assistant-core 0.2.28 → 0.2.30
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
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Claude's model list is kept once per account in the server process instead of once per account-query owner, as plan usage already is: an owner the host creates again (a restarted service, a reopened project) answers `readCatalogue` from the list another owner read, without starting a tool-free Claude, while the account's credentials and Claude Code are unchanged. A credentials change (sign-in, sign-out, token refresh) or a Claude Code update still reads it again, and an owner's `invalidate()` drops the list of every account it used, for every owner.
|
|
5
6
|
- Test fixture only: the responsive assistant fixture's avatar is drawn in the theme's `primary` and `on-primary` roles instead of a raw `#6750a4` and `white`, so it follows the dark scheme like the rest of the conversation. A package test runs `jskit check m3`'s raw-colour rule over the fixture, which the check itself skips with every `fixtures/` folder.
|
|
6
7
|
- The assistant settings resource (`assistantConfigResource`) has a required boolean `canUpdate` attribute: whether the signed-in person may change the settings, as the server decides (`@jskit-ai/assistant-runtime`'s settings pane shows Save only when it is true).
|
|
7
8
|
- **Breaking (data-access redesign).** The assistant operations are API contracts: `assistantContract` and `assistantConfigContract` (view and patch, the two its routes serve), which replace `assistantResource` and `assistantConfigResource`.
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"build": "vite build"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@jskit-ai/assistant-core": "0.2.
|
|
12
|
-
"@jskit-ai/http-runtime": "0.2.
|
|
13
|
-
"@jskit-ai/kernel": "0.2.
|
|
11
|
+
"@jskit-ai/assistant-core": "0.2.30",
|
|
12
|
+
"@jskit-ai/http-runtime": "0.2.15",
|
|
13
|
+
"@jskit-ai/kernel": "0.2.15",
|
|
14
14
|
"@jskit-ai/material-web": "0.1.3",
|
|
15
15
|
"vue": "^3.5.13",
|
|
16
16
|
"vuetify": "^4.0.0",
|
|
17
|
-
"@jskit-ai/connectors-core": "0.2.
|
|
18
|
-
"@jskit-ai/connectors-catalog": "0.2.
|
|
17
|
+
"@jskit-ai/connectors-core": "0.2.17",
|
|
18
|
+
"@jskit-ai/connectors-catalog": "0.2.17"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@vitejs/plugin-vue": "^6.0.7",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/assistant-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.30",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "jskit test"
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@ai-sdk/togetherai": "^3.0.49",
|
|
53
53
|
"@ai-sdk/xai": "^4.0.58",
|
|
54
54
|
"@jskit-ai/agent": "0.1.2",
|
|
55
|
-
"@jskit-ai/resource-core": "0.1.
|
|
55
|
+
"@jskit-ai/resource-core": "0.1.206",
|
|
56
56
|
"@openrouter/ai-sdk-provider": "^3.0.0",
|
|
57
57
|
"ai": "^7.0.152",
|
|
58
58
|
"json-rest-schema": "^1.0.17",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"ws": "^8.20.1"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
|
-
"@jskit-ai/http-runtime": "0.2.
|
|
64
|
-
"@jskit-ai/kernel": "0.2.
|
|
63
|
+
"@jskit-ai/http-runtime": "0.2.15",
|
|
64
|
+
"@jskit-ai/kernel": "0.2.15",
|
|
65
65
|
"vue": "^3.5.13",
|
|
66
66
|
"vuetify": "^4.3.0"
|
|
67
67
|
},
|
|
@@ -290,6 +290,10 @@ const PLAN_USAGE_CURRENT_MS = 420_000;
|
|
|
290
290
|
// for the account shares it, so however many ask, the account's one-shot Claude
|
|
291
291
|
// starts at most once per idle interval.
|
|
292
292
|
const planUsageReads = new Map();
|
|
293
|
+
// One model list per Claude account in this server process, with the credentials
|
|
294
|
+
// and Claude Code revision it answered for. An owner the host creates again (a
|
|
295
|
+
// restarted service, a reopened project) reads it without starting a Claude.
|
|
296
|
+
const catalogueReads = new Map();
|
|
293
297
|
|
|
294
298
|
/**
|
|
295
299
|
* Claude Code answers `get_usage` from the usage endpoint, or from its saved copy
|
|
@@ -320,9 +324,10 @@ export function claudeCatalogueModels(initialization) {
|
|
|
320
324
|
|
|
321
325
|
/**
|
|
322
326
|
* Native account queries share process reuse, caches and retryable cleanup. Plan
|
|
323
|
-
* usage
|
|
324
|
-
* that identity names exactly one Claude account. `configRoot`
|
|
325
|
-
* Claude configuration directory, whose credentials the model
|
|
327
|
+
* usage and the model list are shared by `accountIdentity` with every other owner
|
|
328
|
+
* in the process, so that identity names exactly one Claude account. `configRoot`
|
|
329
|
+
* is the account's Claude configuration directory, whose credentials the model
|
|
330
|
+
* list follows.
|
|
326
331
|
* @param {object} options
|
|
327
332
|
* @param {(request: { signal: AbortSignal, purpose: string }) => Promise<ClaudeCodeProcess>} options.createProcess
|
|
328
333
|
* @param {(context: Record<string, any>) => Promise<string>} options.accountIdentity
|
|
@@ -342,7 +347,8 @@ export function createClaudeAccountQueries({
|
|
|
342
347
|
});
|
|
343
348
|
const accountProcessStops = new Set();
|
|
344
349
|
const accountReads = new Set();
|
|
345
|
-
|
|
350
|
+
// Accounts whose shared model list this owner used, and the reads it started.
|
|
351
|
+
const catalogueAccounts = new Set();
|
|
346
352
|
const catalogStarts = new Map();
|
|
347
353
|
// Accounts whose shared plan-usage reading this owner used, and the reads it started.
|
|
348
354
|
const usageAccounts = new Set();
|
|
@@ -409,8 +415,10 @@ export function createClaudeAccountQueries({
|
|
|
409
415
|
const identity = await accountIdentity(context);
|
|
410
416
|
assertCurrent(expectedRevision);
|
|
411
417
|
// An answer is kept until its account, credentials or executable change, so
|
|
412
|
-
// reading it again starts no Claude.
|
|
413
|
-
const
|
|
418
|
+
// reading it again, from this owner or any other, starts no Claude.
|
|
419
|
+
const catalogueKey = JSON.stringify([configRoot, identity]);
|
|
420
|
+
catalogueAccounts.add(catalogueKey);
|
|
421
|
+
const kept = catalogueReads.get(catalogueKey);
|
|
414
422
|
if (kept && kept.revision === await catalogueRevision(kept.command)) {
|
|
415
423
|
assertCurrent(expectedRevision);
|
|
416
424
|
return kept.value;
|
|
@@ -425,7 +433,7 @@ export function createClaudeAccountQueries({
|
|
|
425
433
|
messageCode: "assistant.claude.accountChangedRefreshModels"
|
|
426
434
|
});
|
|
427
435
|
assertCurrent(expectedRevision);
|
|
428
|
-
|
|
436
|
+
catalogueReads.set(catalogueKey, { command, revision: answered, value });
|
|
429
437
|
return value;
|
|
430
438
|
})().finally(() => { catalogStarts.delete(identity); }));
|
|
431
439
|
return catalogStarts.get(identity);
|
|
@@ -495,8 +503,10 @@ export function createClaudeAccountQueries({
|
|
|
495
503
|
|
|
496
504
|
async function invalidate() {
|
|
497
505
|
revision++;
|
|
498
|
-
// Dropped at once: a read started before this cannot store its answer
|
|
499
|
-
|
|
506
|
+
// Dropped at once: a read started before this cannot store its answer, and
|
|
507
|
+
// the next request for an account this owner used reads its model list again.
|
|
508
|
+
for (const catalogueKey of catalogueAccounts) catalogueReads.delete(catalogueKey);
|
|
509
|
+
catalogueAccounts.clear();
|
|
500
510
|
for (const controller of accountReads) {
|
|
501
511
|
controller.abort(error("Claude account query was cancelled for reconnection.", { messageCode: "assistant.claude.accountQueryCancelled" }));
|
|
502
512
|
}
|
|
@@ -814,6 +814,45 @@ test("Claude model list starts a Claude only after its account, credentials or C
|
|
|
814
814
|
assert.equal(await model(), "read-5");
|
|
815
815
|
});
|
|
816
816
|
|
|
817
|
+
test("Claude model list is read once per account for every owner the host creates again", async (t) => {
|
|
818
|
+
const root = await mkdtemp(join(tmpdir(), "jskit-claude-catalogue-owners-"));
|
|
819
|
+
t.after(() => rm(root, { recursive: true, force: true }));
|
|
820
|
+
const configRoot = join(root, ".claude");
|
|
821
|
+
const otherConfigRoot = join(root, "other", ".claude");
|
|
822
|
+
await mkdir(otherConfigRoot, { recursive: true });
|
|
823
|
+
await mkdir(configRoot);
|
|
824
|
+
const credentials = join(configRoot, ".credentials.json");
|
|
825
|
+
const command = join(root, "claude");
|
|
826
|
+
await writeFile(credentials, "signed in");
|
|
827
|
+
await writeFile(command, "2.1.1");
|
|
828
|
+
const starts = [];
|
|
829
|
+
// A restarted service or a reopened project creates its account queries again.
|
|
830
|
+
const owner = (account = "account", accountConfigRoot = configRoot) => createClaudeAccountQueries({
|
|
831
|
+
accountIdentity: async () => account, configRoot: accountConfigRoot,
|
|
832
|
+
createProcess({ purpose }) {
|
|
833
|
+
starts.push(purpose);
|
|
834
|
+
return { command, initialization: { models: [{ value: `read-${starts.length}` }] }, async stop() { return { scopeEmpty: true }; } };
|
|
835
|
+
} });
|
|
836
|
+
const model = async (queries) => (await queries.readCatalogue({})).models[0].value;
|
|
837
|
+
const first = owner();
|
|
838
|
+
assert.equal(await model(first), "read-1");
|
|
839
|
+
const restarted = owner();
|
|
840
|
+
assert.equal(await model(restarted), "read-1");
|
|
841
|
+
// Another account, or the same name in another Claude configuration, reads its own.
|
|
842
|
+
assert.equal(await model(owner("other account")), "read-2");
|
|
843
|
+
assert.equal(await model(owner("account", otherConfigRoot)), "read-3");
|
|
844
|
+
// A token refresh replaces the credentials, so the next read of any owner starts a Claude.
|
|
845
|
+
await writeFile(`${credentials}.next`, "refreshed");
|
|
846
|
+
await rename(`${credentials}.next`, credentials);
|
|
847
|
+
assert.equal(await model(owner()), "read-4");
|
|
848
|
+
assert.equal(await model(restarted), "read-4");
|
|
849
|
+
// One owner's reconnection drops the account's list for every owner.
|
|
850
|
+
await restarted.invalidate();
|
|
851
|
+
assert.equal(await model(first), "read-5");
|
|
852
|
+
assert.deepEqual(starts, Array(5).fill("catalogue"));
|
|
853
|
+
await first.invalidate();
|
|
854
|
+
});
|
|
855
|
+
|
|
817
856
|
test("Claude account queries label temporary startup and reuse each account's recent reading", async () => {
|
|
818
857
|
const starts = [];
|
|
819
858
|
let stops = 0;
|