@undefineds.co/linx 0.2.17 → 0.2.18
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/README.md +3 -6
- package/dist/generated/version.js +1 -1
- package/dist/generated/version.js.map +1 -1
- package/dist/index.js +256 -143
- package/dist/index.js.map +1 -1
- package/dist/lib/account-api.js +1 -1
- package/dist/lib/account-api.js.map +1 -1
- package/dist/lib/account-session.js +1 -1
- package/dist/lib/account-session.js.map +1 -1
- package/dist/lib/ai-command.js +186 -367
- package/dist/lib/ai-command.js.map +1 -1
- package/dist/lib/chat-api.js +177 -19
- package/dist/lib/chat-api.js.map +1 -1
- package/dist/lib/codex-plugin/bridge.js.map +1 -1
- package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
- package/dist/lib/codex-plugin/index.js.map +1 -1
- package/dist/lib/codex-plugin/runner.js.map +1 -1
- package/dist/lib/credentials-store.js +7 -1
- package/dist/lib/credentials-store.js.map +1 -1
- package/dist/lib/default-model.js +1 -0
- package/dist/lib/default-model.js.map +1 -1
- package/dist/lib/login-command.js +33 -10
- package/dist/lib/login-command.js.map +1 -1
- package/dist/lib/models.js +2 -27
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/node-warning-filter.js +34 -0
- package/dist/lib/node-warning-filter.js.map +1 -0
- package/dist/lib/oidc-auth.js +130 -18
- package/dist/lib/oidc-auth.js.map +1 -1
- package/dist/lib/oidc-session-storage.js.map +1 -1
- package/dist/lib/pi-adapter/auth.js +47 -11
- package/dist/lib/pi-adapter/auth.js.map +1 -1
- package/dist/lib/pi-adapter/branding.js +764 -76
- package/dist/lib/pi-adapter/branding.js.map +1 -1
- package/dist/lib/pi-adapter/index.js.map +1 -1
- package/dist/lib/pi-adapter/interactive.js +179 -5
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-approval.js +8 -0
- package/dist/lib/pi-adapter/pod-approval.js.map +1 -0
- package/dist/lib/pi-adapter/pod-mirror-mapping.js +189 -0
- package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +1 -0
- package/dist/lib/pi-adapter/pod-mirror.js +416 -0
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -0
- package/dist/lib/pi-adapter/pod-tools.js +104 -0
- package/dist/lib/pi-adapter/pod-tools.js.map +1 -0
- package/dist/lib/pi-adapter/runtime.js +327 -28
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/session.js +608 -0
- package/dist/lib/pi-adapter/session.js.map +1 -0
- package/dist/lib/pi-adapter/stream.js +154 -4
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/pi-adapter/theme.js.map +1 -1
- package/dist/lib/pi-adapter/web-fetch.js +154 -0
- package/dist/lib/pi-adapter/web-fetch.js.map +1 -0
- package/dist/lib/pod-chat-store.js +40 -30
- package/dist/lib/pod-chat-store.js.map +1 -1
- package/dist/lib/pod-data-session.js +110 -0
- package/dist/lib/pod-data-session.js.map +1 -0
- package/dist/lib/profile-identity.js +16 -60
- package/dist/lib/profile-identity.js.map +1 -1
- package/dist/lib/prompt.js.map +1 -1
- package/dist/lib/runtime-target.js +1 -1
- package/dist/lib/runtime-target.js.map +1 -1
- package/dist/lib/solid-auth.js.map +1 -1
- package/dist/lib/thread-utils.js.map +1 -1
- package/dist/lib/watch/archive.js +1 -1
- package/dist/lib/watch/archive.js.map +1 -1
- package/dist/lib/watch/auth.js +1 -1
- package/dist/lib/watch/auth.js.map +1 -1
- package/dist/lib/watch/codex-composer.js.map +1 -1
- package/dist/lib/watch/codex-footer.js.map +1 -1
- package/dist/lib/watch/codex-overlay.js.map +1 -1
- package/dist/lib/watch/codex-request-form.js +1 -1
- package/dist/lib/watch/codex-request-form.js.map +1 -1
- package/dist/lib/watch/codex-request-input.js.map +1 -1
- package/dist/lib/watch/display.js.map +1 -1
- package/dist/lib/watch/format.js.map +1 -1
- package/dist/lib/watch/hooks/claude.js +4 -0
- package/dist/lib/watch/hooks/claude.js.map +1 -1
- package/dist/lib/watch/hooks/codebuddy.js +4 -0
- package/dist/lib/watch/hooks/codebuddy.js.map +1 -1
- package/dist/lib/watch/hooks/codex.js +4 -0
- package/dist/lib/watch/hooks/codex.js.map +1 -1
- package/dist/lib/watch/hooks/index.js.map +1 -1
- package/dist/lib/watch/hooks/shared.js +0 -1
- package/dist/lib/watch/hooks/shared.js.map +1 -1
- package/dist/lib/watch/index.js.map +1 -1
- package/dist/lib/watch/pod-ai.js +29 -37
- package/dist/lib/watch/pod-ai.js.map +1 -1
- package/dist/lib/watch/pod-approval.js +822 -220
- package/dist/lib/watch/pod-approval.js.map +1 -1
- package/dist/lib/watch/pod-persistence.js +214 -106
- package/dist/lib/watch/pod-persistence.js.map +1 -1
- package/dist/lib/watch/runner.js +243 -38
- package/dist/lib/watch/runner.js.map +1 -1
- package/dist/lib/watch/secretary.js +238 -0
- package/dist/lib/watch/secretary.js.map +1 -0
- package/dist/lib/watch/types.js.map +1 -1
- package/dist/watch-cli.js +8 -34
- package/dist/watch-cli.js.map +1 -1
- package/package.json +3 -9
- package/vendor/agent-runtime/dist/acp.d.ts +27 -0
- package/vendor/agent-runtime/dist/acp.js +86 -0
- package/vendor/agent-runtime/dist/companion-model.d.ts +7 -0
- package/vendor/agent-runtime/dist/companion-model.js +12 -0
- package/vendor/agent-runtime/dist/index.d.ts +3 -0
- package/vendor/agent-runtime/dist/index.js +3 -0
- package/vendor/agent-runtime/dist/turn-controller.d.ts +69 -0
- package/vendor/agent-runtime/dist/turn-controller.js +129 -0
- package/vendor/agent-runtime/package.json +11 -0
- package/vendor/client/dist/client/index.d.ts +0 -118
- package/vendor/client/dist/client/index.js +0 -260
- package/vendor/client/dist/index.d.ts +0 -1
- package/vendor/client/dist/index.js +0 -1
- package/vendor/client/dist/watch/index.d.ts +0 -226
- package/vendor/client/dist/watch/index.js +0 -1114
- package/vendor/client/package.json +0 -9
package/dist/lib/ai-command.js
CHANGED
|
@@ -1,27 +1,20 @@
|
|
|
1
|
-
import { resolveLinxPodUrl } from '
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { getClientCredentials, loadCredentials } from './credentials-store.js';
|
|
1
|
+
import { resolveLinxPodUrl } from '@undefineds.co/models/client';
|
|
2
|
+
import { aiModelTable, aiProviderTable, buildAIConfigMutationPlan, buildAIConfigProviderStateMap, credentialTable, drizzle, getAIConfigProviderMetadata, initSolidTables, normalizeAIConfigProviderId, normalizeAIConfigResourceId, sameAIConfigProviderFamily, solidSchema, } from './models.js';
|
|
3
|
+
import { getClientCredentialId, getClientCredentialKey, getClientCredentials, loadCredentials } from './credentials-store.js';
|
|
5
4
|
import { loadAccountSession } from './account-session.js';
|
|
6
5
|
import { authenticatedFetch, getAccessToken } from './solid-auth.js';
|
|
6
|
+
import { getOidcAccessToken } from './oidc-auth.js';
|
|
7
7
|
import { promptPassword } from './prompt.js';
|
|
8
|
-
const XSD_DATE_TIME = 'http://www.w3.org/2001/XMLSchema#dateTime';
|
|
9
8
|
let aiRuntimePromise = null;
|
|
10
9
|
async function loadAiRuntime() {
|
|
11
10
|
if (!aiRuntimePromise) {
|
|
12
11
|
aiRuntimePromise = Promise.resolve({
|
|
13
|
-
DCTerms,
|
|
14
|
-
RDFS,
|
|
15
|
-
UDFS,
|
|
16
|
-
XPOD_AI,
|
|
17
|
-
XPOD_CREDENTIAL,
|
|
18
|
-
aiConfigProviderUri,
|
|
19
12
|
buildAIConfigMutationPlan,
|
|
20
13
|
buildAIConfigProviderStateMap,
|
|
21
|
-
extractAIConfigProviderId,
|
|
22
|
-
extractAIConfigResourceId,
|
|
23
14
|
getAIConfigProviderMetadata,
|
|
24
|
-
|
|
15
|
+
normalizeAIConfigProviderId,
|
|
16
|
+
normalizeAIConfigResourceId,
|
|
17
|
+
sameAIConfigProviderFamily,
|
|
25
18
|
});
|
|
26
19
|
}
|
|
27
20
|
return aiRuntimePromise;
|
|
@@ -31,104 +24,6 @@ function maskSecret(value) {
|
|
|
31
24
|
return '****';
|
|
32
25
|
return `${value.slice(0, 4)}****${value.slice(-4)}`;
|
|
33
26
|
}
|
|
34
|
-
function absolutePodUri(podUrl, relativePath) {
|
|
35
|
-
return `${podUrl.replace(/\/?$/, '/')}${relativePath.replace(/^\/+/, '')}`;
|
|
36
|
-
}
|
|
37
|
-
function escapeRegex(value) {
|
|
38
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
39
|
-
}
|
|
40
|
-
function quoteLiteral(value) {
|
|
41
|
-
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`;
|
|
42
|
-
}
|
|
43
|
-
function dateTimeLiteral(value) {
|
|
44
|
-
return `"${value.toISOString()}"^^<${XSD_DATE_TIME}>`;
|
|
45
|
-
}
|
|
46
|
-
function splitResourceBlocks(turtle, resourceUrl) {
|
|
47
|
-
if (!turtle.trim())
|
|
48
|
-
return [];
|
|
49
|
-
const pattern = new RegExp(`(?=<${escapeRegex(resourceUrl)}#)`);
|
|
50
|
-
return turtle
|
|
51
|
-
.split(pattern)
|
|
52
|
-
.map((block) => block.trim())
|
|
53
|
-
.filter(Boolean);
|
|
54
|
-
}
|
|
55
|
-
function matchLiteral(block, predicateUri) {
|
|
56
|
-
const match = block.match(new RegExp(`<${escapeRegex(predicateUri)}>\\s+"([^"]*)"(?:\\^\\^<[^>]+>)?`));
|
|
57
|
-
return match?.[1];
|
|
58
|
-
}
|
|
59
|
-
function matchIri(block, predicateUri) {
|
|
60
|
-
const match = block.match(new RegExp(`<${escapeRegex(predicateUri)}>\\s+<([^>]+)>`));
|
|
61
|
-
return match?.[1];
|
|
62
|
-
}
|
|
63
|
-
function matchFirstLiteral(block, predicateUris) {
|
|
64
|
-
for (const predicateUri of predicateUris) {
|
|
65
|
-
if (!predicateUri)
|
|
66
|
-
continue;
|
|
67
|
-
const value = matchLiteral(block, predicateUri);
|
|
68
|
-
if (value !== undefined)
|
|
69
|
-
return value;
|
|
70
|
-
}
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
function matchFirstIri(block, predicateUris) {
|
|
74
|
-
for (const predicateUri of predicateUris) {
|
|
75
|
-
if (!predicateUri)
|
|
76
|
-
continue;
|
|
77
|
-
const value = matchIri(block, predicateUri);
|
|
78
|
-
if (value !== undefined)
|
|
79
|
-
return value;
|
|
80
|
-
}
|
|
81
|
-
return undefined;
|
|
82
|
-
}
|
|
83
|
-
function parseCredentialRows(turtle, resourceUrl, runtime) {
|
|
84
|
-
return splitResourceBlocks(turtle, resourceUrl)
|
|
85
|
-
.map((block) => {
|
|
86
|
-
const subject = block.match(new RegExp(`^<${escapeRegex(resourceUrl)}#([^>]+)>`));
|
|
87
|
-
if (!subject?.[1])
|
|
88
|
-
return null;
|
|
89
|
-
return {
|
|
90
|
-
id: subject[1],
|
|
91
|
-
provider: matchFirstIri(block, [runtime.XPOD_CREDENTIAL.provider, runtime.UDFS.provider]),
|
|
92
|
-
service: matchFirstLiteral(block, [runtime.XPOD_CREDENTIAL.service, runtime.UDFS.service]),
|
|
93
|
-
status: matchFirstLiteral(block, [runtime.XPOD_CREDENTIAL.status, runtime.UDFS.status]),
|
|
94
|
-
apiKey: matchFirstLiteral(block, [runtime.XPOD_CREDENTIAL.apiKey, runtime.UDFS.apiKey]),
|
|
95
|
-
baseUrl: matchFirstLiteral(block, [runtime.XPOD_CREDENTIAL.baseUrl, runtime.UDFS.baseUrl]),
|
|
96
|
-
label: matchFirstLiteral(block, [runtime.XPOD_CREDENTIAL.label, runtime.RDFS.label]),
|
|
97
|
-
};
|
|
98
|
-
})
|
|
99
|
-
.filter(Boolean);
|
|
100
|
-
}
|
|
101
|
-
function parseProviderRows(turtle, resourceUrl, runtime) {
|
|
102
|
-
return splitResourceBlocks(turtle, resourceUrl)
|
|
103
|
-
.map((block) => {
|
|
104
|
-
const subject = block.match(new RegExp(`^<${escapeRegex(resourceUrl)}#([^>]+)>`));
|
|
105
|
-
if (!subject?.[1])
|
|
106
|
-
return null;
|
|
107
|
-
return {
|
|
108
|
-
id: subject[1],
|
|
109
|
-
baseUrl: matchFirstLiteral(block, [runtime.XPOD_AI.baseUrl, runtime.UDFS.baseUrl]),
|
|
110
|
-
proxyUrl: matchFirstLiteral(block, [runtime.XPOD_AI.proxyUrl, runtime.UDFS.proxyUrl]),
|
|
111
|
-
hasModel: matchFirstIri(block, [runtime.XPOD_AI.hasModel, runtime.UDFS.hasModel]),
|
|
112
|
-
};
|
|
113
|
-
})
|
|
114
|
-
.filter(Boolean);
|
|
115
|
-
}
|
|
116
|
-
function parseModelRows(turtle, resourceUrl, runtime) {
|
|
117
|
-
return splitResourceBlocks(turtle, resourceUrl)
|
|
118
|
-
.map((block) => {
|
|
119
|
-
const subject = block.match(new RegExp(`^<${escapeRegex(resourceUrl)}#([^>]+)>`));
|
|
120
|
-
if (!subject?.[1])
|
|
121
|
-
return null;
|
|
122
|
-
return {
|
|
123
|
-
id: subject[1],
|
|
124
|
-
displayName: matchFirstLiteral(block, [runtime.XPOD_AI.displayName, runtime.RDFS.label]),
|
|
125
|
-
modelType: matchFirstLiteral(block, [runtime.XPOD_AI.modelType, runtime.UDFS.modelType]),
|
|
126
|
-
isProvidedBy: matchFirstIri(block, [runtime.XPOD_AI.isProvidedBy, runtime.UDFS.isProvidedBy]),
|
|
127
|
-
status: matchFirstLiteral(block, [runtime.XPOD_AI.status, runtime.UDFS.status]),
|
|
128
|
-
};
|
|
129
|
-
})
|
|
130
|
-
.filter(Boolean);
|
|
131
|
-
}
|
|
132
27
|
async function requireApiKey(argv) {
|
|
133
28
|
if (argv['api-key']?.trim()) {
|
|
134
29
|
return argv['api-key'].trim();
|
|
@@ -144,160 +39,202 @@ async function resolvePodWriteContext(urlOverride) {
|
|
|
144
39
|
if (!creds) {
|
|
145
40
|
throw new Error('No local client credentials found. Run `linx login` first.');
|
|
146
41
|
}
|
|
42
|
+
if (!creds.webId) {
|
|
43
|
+
throw new Error('No WebID found in local credentials. Run `linx login` again.');
|
|
44
|
+
}
|
|
147
45
|
const clientCreds = getClientCredentials(creds);
|
|
148
|
-
|
|
149
|
-
|
|
46
|
+
let accessToken = null;
|
|
47
|
+
if (clientCreds) {
|
|
48
|
+
const baseUrl = (urlOverride ?? creds.url).replace(/\/?$/, '/');
|
|
49
|
+
const tokenResult = await getAccessToken(getClientCredentialId(clientCreds), getClientCredentialKey(clientCreds), baseUrl);
|
|
50
|
+
accessToken = tokenResult?.accessToken ?? null;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
accessToken = await getOidcAccessToken(creds);
|
|
150
54
|
}
|
|
151
|
-
|
|
152
|
-
const tokenResult = await getAccessToken(clientCreds.clientId, clientCreds.clientSecret, baseUrl);
|
|
153
|
-
if (!tokenResult) {
|
|
55
|
+
if (!accessToken) {
|
|
154
56
|
throw new Error('Failed to obtain Pod access token. Run `linx login` again.');
|
|
155
57
|
}
|
|
156
58
|
return {
|
|
157
|
-
accessToken
|
|
59
|
+
accessToken,
|
|
60
|
+
webId: creds.webId,
|
|
158
61
|
podUrl: loadAccountSession()?.podUrl || resolveLinxPodUrl(creds.webId),
|
|
159
62
|
};
|
|
160
63
|
}
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
64
|
+
function createAiConfigDb(context) {
|
|
65
|
+
const solidSession = {
|
|
66
|
+
info: {
|
|
67
|
+
isLoggedIn: true,
|
|
68
|
+
webId: context.webId,
|
|
69
|
+
podUrl: context.podUrl,
|
|
70
|
+
},
|
|
71
|
+
fetch: (input, init) => (authenticatedFetch(requestInputToUrl(input), context.accessToken, init)),
|
|
72
|
+
logout: async () => { },
|
|
73
|
+
};
|
|
74
|
+
return drizzle(solidSession, {
|
|
75
|
+
logger: false,
|
|
76
|
+
disableInteropDiscovery: true,
|
|
77
|
+
schema: solidSchema,
|
|
166
78
|
});
|
|
167
|
-
if (!res.ok) {
|
|
168
|
-
throw new Error(`Failed to update ${resourceUrl}.`);
|
|
169
|
-
}
|
|
170
79
|
}
|
|
171
|
-
async function
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
80
|
+
async function loadAiConfigRows(db) {
|
|
81
|
+
await initSolidTables(db, [credentialTable, aiProviderTable, aiModelTable]);
|
|
82
|
+
const [credentialRows, providerRows, modelRows] = await Promise.all([
|
|
83
|
+
db.select().from(credentialTable).execute(),
|
|
84
|
+
db.select().from(aiProviderTable).execute(),
|
|
85
|
+
db.select().from(aiModelTable).execute(),
|
|
86
|
+
]);
|
|
87
|
+
return { credentialRows, providerRows, modelRows };
|
|
88
|
+
}
|
|
89
|
+
async function upsertByLocator(db, table, locator, insert, update) {
|
|
90
|
+
const existing = await db.findByLocator(table, locator);
|
|
91
|
+
if (!existing) {
|
|
92
|
+
await db.insert(table).values(insert).execute();
|
|
93
|
+
return;
|
|
177
94
|
}
|
|
178
|
-
|
|
179
|
-
|
|
95
|
+
await db.updateByLocator(table, locator, update);
|
|
96
|
+
}
|
|
97
|
+
async function deleteByLocatorIfExists(db, table, locator) {
|
|
98
|
+
const existing = await db.findByLocator(table, locator);
|
|
99
|
+
if (existing) {
|
|
100
|
+
await db.deleteByLocator(table, locator);
|
|
180
101
|
}
|
|
181
|
-
return res.text();
|
|
182
102
|
}
|
|
183
|
-
function
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
103
|
+
function requestInputToUrl(input) {
|
|
104
|
+
if (typeof input === 'string')
|
|
105
|
+
return input;
|
|
106
|
+
if (input instanceof URL)
|
|
107
|
+
return input.toString();
|
|
108
|
+
if (typeof Request !== 'undefined' && input instanceof Request)
|
|
109
|
+
return input.url;
|
|
110
|
+
return String(input);
|
|
111
|
+
}
|
|
112
|
+
export async function runAiCommand(argv, dependencies = {}) {
|
|
113
|
+
const action = argv.action;
|
|
114
|
+
const aiRuntime = dependencies.aiRuntime ?? await loadAiRuntime();
|
|
115
|
+
const podContext = await (dependencies.resolvePodWriteContext ?? resolvePodWriteContext)(argv.url);
|
|
116
|
+
const db = (dependencies.createDb ?? createAiConfigDb)(podContext);
|
|
117
|
+
const { credentialRows, providerRows, modelRows } = await (dependencies.loadAiConfigRows ?? loadAiConfigRows)(db);
|
|
118
|
+
const write = dependencies.write ?? ((chunk) => process.stdout.write(chunk));
|
|
119
|
+
const upsert = dependencies.upsertByLocator ?? upsertByLocator;
|
|
120
|
+
const deleteIfExists = dependencies.deleteByLocatorIfExists ?? deleteByLocatorIfExists;
|
|
121
|
+
const getApiKey = dependencies.requireApiKey ?? requireApiKey;
|
|
122
|
+
if (action === 'status') {
|
|
123
|
+
const states = aiRuntime.buildAIConfigProviderStateMap({
|
|
124
|
+
fallbackToCatalogModels: false,
|
|
125
|
+
credentialRows,
|
|
126
|
+
providerRows,
|
|
127
|
+
modelRows,
|
|
128
|
+
});
|
|
129
|
+
const filtered = Object.values(states).filter((state) => {
|
|
130
|
+
if (!state.apiKey)
|
|
131
|
+
return false;
|
|
132
|
+
if (!argv.provider)
|
|
133
|
+
return true;
|
|
134
|
+
return aiRuntime.sameAIConfigProviderFamily(argv.provider, state.id);
|
|
135
|
+
});
|
|
136
|
+
if (filtered.length === 0) {
|
|
137
|
+
write('No LinX cloud AI credentials found.\n');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const lines = [];
|
|
141
|
+
for (const state of filtered) {
|
|
142
|
+
lines.push(`provider: ${state.id}`);
|
|
143
|
+
if (state.selectedModelId) {
|
|
144
|
+
lines.push(`model: ${state.selectedModelId}`);
|
|
145
|
+
}
|
|
146
|
+
lines.push(`api-key: ${maskSecret(state.apiKey)}`);
|
|
147
|
+
lines.push('');
|
|
148
|
+
}
|
|
149
|
+
write(`${lines.join('\n').trimEnd()}\n`);
|
|
150
|
+
return;
|
|
193
151
|
}
|
|
194
|
-
|
|
195
|
-
|
|
152
|
+
const providerArg = argv.provider?.trim();
|
|
153
|
+
if (!providerArg) {
|
|
154
|
+
throw new Error(action === 'disconnect'
|
|
155
|
+
? 'Usage: linx ai disconnect <provider>'
|
|
156
|
+
: 'Usage: linx ai connect <provider> --api-key <key>');
|
|
196
157
|
}
|
|
197
|
-
|
|
198
|
-
|
|
158
|
+
const provider = aiRuntime.normalizeAIConfigProviderId(providerArg);
|
|
159
|
+
if (action === 'disconnect') {
|
|
160
|
+
for (const row of credentialRows) {
|
|
161
|
+
const rowProvider = typeof row.provider === 'string' ? row.provider : row.id;
|
|
162
|
+
if (!aiRuntime.sameAIConfigProviderFamily(rowProvider, provider)) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
await deleteIfExists(db, credentialTable, { id: row.id });
|
|
166
|
+
}
|
|
167
|
+
write(`Disconnected AI provider: ${provider}\n`);
|
|
168
|
+
return;
|
|
199
169
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
];
|
|
228
|
-
if (payload.apiKey) {
|
|
229
|
-
inserts.push(`${subject} <${runtime.XPOD_CREDENTIAL.apiKey}> ${quoteLiteral(payload.apiKey)} .`);
|
|
170
|
+
const apiKey = await getApiKey(argv);
|
|
171
|
+
const modelId = argv.model?.trim();
|
|
172
|
+
const plan = aiRuntime.buildAIConfigMutationPlan({
|
|
173
|
+
providerId: provider,
|
|
174
|
+
currentProviderRows: providerRows,
|
|
175
|
+
currentCredentialRows: credentialRows,
|
|
176
|
+
currentModelRows: modelRows,
|
|
177
|
+
updates: {
|
|
178
|
+
enabled: true,
|
|
179
|
+
apiKey,
|
|
180
|
+
baseUrl: argv['base-url']?.trim() || undefined,
|
|
181
|
+
models: modelId
|
|
182
|
+
? [{
|
|
183
|
+
id: modelId,
|
|
184
|
+
name: modelId,
|
|
185
|
+
enabled: true,
|
|
186
|
+
capabilities: [],
|
|
187
|
+
}]
|
|
188
|
+
: undefined,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
if (plan.providerPayload?.id) {
|
|
192
|
+
await upsert(db, aiProviderTable, { id: plan.providerPayload.id }, plan.providerPayload, {
|
|
193
|
+
baseUrl: plan.providerPayload.baseUrl,
|
|
194
|
+
proxyUrl: plan.providerPayload.proxyUrl,
|
|
195
|
+
hasModel: plan.providerPayload.hasModel,
|
|
196
|
+
});
|
|
230
197
|
}
|
|
231
|
-
if (
|
|
232
|
-
|
|
198
|
+
if (plan.credentialPayload?.id
|
|
199
|
+
&& plan.credentialPayload.provider
|
|
200
|
+
&& plan.credentialPayload.service
|
|
201
|
+
&& plan.credentialPayload.status) {
|
|
202
|
+
await upsert(db, credentialTable, { id: plan.credentialPayload.id }, plan.credentialPayload, {
|
|
203
|
+
provider: plan.credentialPayload.provider,
|
|
204
|
+
service: plan.credentialPayload.service,
|
|
205
|
+
status: plan.credentialPayload.status,
|
|
206
|
+
apiKey: plan.credentialPayload.apiKey,
|
|
207
|
+
baseUrl: plan.credentialPayload.baseUrl,
|
|
208
|
+
label: plan.credentialPayload.label,
|
|
209
|
+
});
|
|
233
210
|
}
|
|
234
|
-
|
|
235
|
-
|
|
211
|
+
for (const modelPayload of plan.modelUpserts) {
|
|
212
|
+
if (!modelPayload.id || !modelPayload.isProvidedBy || !modelPayload.createdAt || !modelPayload.updatedAt) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
await upsert(db, aiModelTable, {
|
|
216
|
+
id: modelPayload.id,
|
|
217
|
+
isProvidedBy: modelPayload.isProvidedBy,
|
|
218
|
+
}, modelPayload, {
|
|
219
|
+
displayName: modelPayload.displayName,
|
|
220
|
+
modelType: modelPayload.modelType,
|
|
221
|
+
isProvidedBy: modelPayload.isProvidedBy,
|
|
222
|
+
status: modelPayload.status,
|
|
223
|
+
updatedAt: modelPayload.updatedAt,
|
|
224
|
+
});
|
|
236
225
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
${deletes.join('\n ')}
|
|
250
|
-
}`;
|
|
251
|
-
}
|
|
252
|
-
function buildModelSparql(runtime, resourceUrl, podUrl, payload) {
|
|
253
|
-
const subject = `<${resourceUrl}#${payload.id}>`;
|
|
254
|
-
const deletes = [
|
|
255
|
-
`OPTIONAL { ${subject} <${runtime.XPOD_AI.displayName}> ?oldDisplayName }`,
|
|
256
|
-
`OPTIONAL { ${subject} <${runtime.XPOD_AI.modelType}> ?oldModelType }`,
|
|
257
|
-
`OPTIONAL { ${subject} <${runtime.XPOD_AI.isProvidedBy}> ?oldIsProvidedBy }`,
|
|
258
|
-
`OPTIONAL { ${subject} <${runtime.XPOD_AI.status}> ?oldStatus }`,
|
|
259
|
-
`OPTIONAL { ${subject} <${runtime.XPOD_AI.createdAt}> ?oldCreatedAt }`,
|
|
260
|
-
`OPTIONAL { ${subject} <${runtime.XPOD_AI.updatedAt}> ?oldUpdatedAt }`,
|
|
261
|
-
];
|
|
262
|
-
const inserts = [
|
|
263
|
-
`${subject} a <${runtime.XPOD_AI.Model}> .`,
|
|
264
|
-
`${subject} <${runtime.XPOD_AI.displayName}> ${quoteLiteral(payload.displayName || payload.id)} .`,
|
|
265
|
-
`${subject} <${runtime.XPOD_AI.modelType}> ${quoteLiteral(payload.modelType || 'chat')} .`,
|
|
266
|
-
`${subject} <${runtime.XPOD_AI.isProvidedBy}> <${absolutePodUri(podUrl, payload.isProvidedBy)}> .`,
|
|
267
|
-
`${subject} <${runtime.XPOD_AI.status}> ${quoteLiteral(payload.status || 'active')} .`,
|
|
268
|
-
`${subject} <${runtime.XPOD_AI.createdAt}> ${dateTimeLiteral(payload.createdAt)} .`,
|
|
269
|
-
`${subject} <${runtime.XPOD_AI.updatedAt}> ${dateTimeLiteral(payload.updatedAt)} .`,
|
|
270
|
-
];
|
|
271
|
-
return `DELETE {
|
|
272
|
-
${subject} <${runtime.XPOD_AI.displayName}> ?oldDisplayName .
|
|
273
|
-
${subject} <${runtime.XPOD_AI.modelType}> ?oldModelType .
|
|
274
|
-
${subject} <${runtime.XPOD_AI.isProvidedBy}> ?oldIsProvidedBy .
|
|
275
|
-
${subject} <${runtime.XPOD_AI.status}> ?oldStatus .
|
|
276
|
-
${subject} <${runtime.XPOD_AI.createdAt}> ?oldCreatedAt .
|
|
277
|
-
${subject} <${runtime.XPOD_AI.updatedAt}> ?oldUpdatedAt .
|
|
278
|
-
}
|
|
279
|
-
INSERT {
|
|
280
|
-
${inserts.join('\n ')}
|
|
281
|
-
}
|
|
282
|
-
WHERE {
|
|
283
|
-
${deletes.join('\n ')}
|
|
284
|
-
}`;
|
|
285
|
-
}
|
|
286
|
-
function buildCredentialDeleteSparql(runtime, providerRef) {
|
|
287
|
-
return `DELETE {
|
|
288
|
-
?credential ?predicate ?object .
|
|
289
|
-
}
|
|
290
|
-
WHERE {
|
|
291
|
-
{
|
|
292
|
-
?credential <${runtime.XPOD_CREDENTIAL.provider}> <${providerRef}> ;
|
|
293
|
-
?predicate ?object .
|
|
294
|
-
}
|
|
295
|
-
UNION
|
|
296
|
-
{
|
|
297
|
-
?credential <${runtime.UDFS.provider}> <${providerRef}> ;
|
|
298
|
-
?predicate ?object .
|
|
299
|
-
}
|
|
300
|
-
}`;
|
|
226
|
+
for (const modelIdToDelete of plan.modelDeleteIds) {
|
|
227
|
+
await deleteIfExists(db, aiModelTable, {
|
|
228
|
+
id: modelIdToDelete,
|
|
229
|
+
isProvidedBy: plan.providerId,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
const metadata = aiRuntime.getAIConfigProviderMetadata(provider);
|
|
233
|
+
write(`Connected AI provider: ${metadata.id}\n`);
|
|
234
|
+
if (modelId) {
|
|
235
|
+
write(`model: ${aiRuntime.normalizeAIConfigResourceId(modelId)}\n`);
|
|
236
|
+
}
|
|
237
|
+
write(`api-key: ${maskSecret(apiKey)}\n`);
|
|
301
238
|
}
|
|
302
239
|
export const aiCommand = {
|
|
303
240
|
command: 'ai <action> [provider]',
|
|
@@ -309,130 +246,12 @@ export const aiCommand = {
|
|
|
309
246
|
})
|
|
310
247
|
.positional('provider', {
|
|
311
248
|
type: 'string',
|
|
312
|
-
description: 'Provider/backend id, for example anthropic,
|
|
249
|
+
description: 'Provider/backend id, for example claude, anthropic, codebuddy, codex',
|
|
313
250
|
})
|
|
314
251
|
.option('url', { type: 'string', description: 'Server base URL override' })
|
|
315
252
|
.option('api-key', { type: 'string', description: 'Provider API key' })
|
|
316
253
|
.option('model', { type: 'string', description: 'Default model id' })
|
|
317
254
|
.option('base-url', { type: 'string', description: 'Provider API base URL override' }),
|
|
318
|
-
handler: async (argv) =>
|
|
319
|
-
const action = argv.action;
|
|
320
|
-
const aiRuntime = await loadAiRuntime();
|
|
321
|
-
const { accessToken, podUrl } = await resolvePodWriteContext(argv.url);
|
|
322
|
-
const providerResource = absolutePodUri(podUrl, '/settings/ai/providers.ttl');
|
|
323
|
-
const credentialResource = absolutePodUri(podUrl, '/settings/credentials.ttl');
|
|
324
|
-
const modelResource = absolutePodUri(podUrl, '/settings/ai/models.ttl');
|
|
325
|
-
if (action === 'status') {
|
|
326
|
-
const [credentialTurtle, providerTurtle, modelTurtle] = await Promise.all([
|
|
327
|
-
fetchResourceText(credentialResource, accessToken),
|
|
328
|
-
fetchResourceText(providerResource, accessToken),
|
|
329
|
-
fetchResourceText(modelResource, accessToken),
|
|
330
|
-
]);
|
|
331
|
-
const states = aiRuntime.buildAIConfigProviderStateMap({
|
|
332
|
-
fallbackToCatalogModels: false,
|
|
333
|
-
credentialRows: parseCredentialRows(credentialTurtle, credentialResource, aiRuntime),
|
|
334
|
-
providerRows: parseProviderRows(providerTurtle, providerResource, aiRuntime),
|
|
335
|
-
modelRows: parseModelRows(modelTurtle, modelResource, aiRuntime),
|
|
336
|
-
});
|
|
337
|
-
const filtered = Object.values(states).filter((state) => {
|
|
338
|
-
if (!state.apiKey)
|
|
339
|
-
return false;
|
|
340
|
-
if (!argv.provider)
|
|
341
|
-
return true;
|
|
342
|
-
return aiRuntime.sameAIConfigProviderId(argv.provider, state.id);
|
|
343
|
-
});
|
|
344
|
-
if (filtered.length === 0) {
|
|
345
|
-
process.stdout.write('No LinX cloud AI credentials found.\n');
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
const lines = [];
|
|
349
|
-
for (const state of filtered) {
|
|
350
|
-
lines.push(`provider: ${state.id}`);
|
|
351
|
-
if (state.selectedModelId) {
|
|
352
|
-
lines.push(`model: ${state.selectedModelId}`);
|
|
353
|
-
}
|
|
354
|
-
lines.push(`api-key: ${maskSecret(state.apiKey)}`);
|
|
355
|
-
lines.push('');
|
|
356
|
-
}
|
|
357
|
-
process.stdout.write(`${lines.join('\n').trimEnd()}\n`);
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
const providerArg = argv.provider?.trim();
|
|
361
|
-
if (!providerArg) {
|
|
362
|
-
throw new Error(action === 'disconnect'
|
|
363
|
-
? 'Usage: linx ai disconnect <provider>'
|
|
364
|
-
: 'Usage: linx ai connect <provider> --api-key <key>');
|
|
365
|
-
}
|
|
366
|
-
const provider = aiRuntime.extractAIConfigProviderId(providerArg);
|
|
367
|
-
if (action === 'disconnect') {
|
|
368
|
-
const providerRef = absolutePodUri(podUrl, `/settings/ai/providers.ttl#${provider}`);
|
|
369
|
-
await patchResource(credentialResource, accessToken, buildCredentialDeleteSparql(aiRuntime, providerRef));
|
|
370
|
-
process.stdout.write(`Disconnected AI provider: ${provider}\n`);
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
const apiKey = await requireApiKey(argv);
|
|
374
|
-
const modelId = argv.model?.trim();
|
|
375
|
-
const plan = aiRuntime.buildAIConfigMutationPlan({
|
|
376
|
-
providerId: provider,
|
|
377
|
-
currentProviderRows: [],
|
|
378
|
-
currentCredentialRows: [],
|
|
379
|
-
currentModelRows: [],
|
|
380
|
-
updates: {
|
|
381
|
-
enabled: true,
|
|
382
|
-
apiKey,
|
|
383
|
-
baseUrl: argv['base-url']?.trim() || undefined,
|
|
384
|
-
models: modelId
|
|
385
|
-
? [{
|
|
386
|
-
id: modelId,
|
|
387
|
-
name: modelId,
|
|
388
|
-
enabled: true,
|
|
389
|
-
capabilities: [],
|
|
390
|
-
}]
|
|
391
|
-
: undefined,
|
|
392
|
-
},
|
|
393
|
-
});
|
|
394
|
-
if (plan.providerPayload?.id) {
|
|
395
|
-
await patchResource(providerResource, accessToken, buildProviderSparql(aiRuntime, providerResource, podUrl, {
|
|
396
|
-
id: plan.providerPayload.id,
|
|
397
|
-
baseUrl: plan.providerPayload.baseUrl,
|
|
398
|
-
proxyUrl: plan.providerPayload.proxyUrl,
|
|
399
|
-
hasModel: plan.providerPayload.hasModel,
|
|
400
|
-
}));
|
|
401
|
-
}
|
|
402
|
-
if (plan.credentialPayload?.id
|
|
403
|
-
&& plan.credentialPayload.provider
|
|
404
|
-
&& plan.credentialPayload.service
|
|
405
|
-
&& plan.credentialPayload.status) {
|
|
406
|
-
await patchResource(credentialResource, accessToken, buildCredentialSparql(aiRuntime, credentialResource, podUrl, {
|
|
407
|
-
id: plan.credentialPayload.id,
|
|
408
|
-
provider: plan.credentialPayload.provider,
|
|
409
|
-
service: plan.credentialPayload.service,
|
|
410
|
-
status: plan.credentialPayload.status,
|
|
411
|
-
apiKey: plan.credentialPayload.apiKey,
|
|
412
|
-
baseUrl: plan.credentialPayload.baseUrl,
|
|
413
|
-
label: plan.credentialPayload.label,
|
|
414
|
-
}));
|
|
415
|
-
}
|
|
416
|
-
for (const modelPayload of plan.modelUpserts) {
|
|
417
|
-
if (!modelPayload.id || !modelPayload.isProvidedBy || !modelPayload.createdAt || !modelPayload.updatedAt) {
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
await patchResource(modelResource, accessToken, buildModelSparql(aiRuntime, modelResource, podUrl, {
|
|
421
|
-
id: modelPayload.id,
|
|
422
|
-
displayName: modelPayload.displayName,
|
|
423
|
-
modelType: modelPayload.modelType,
|
|
424
|
-
isProvidedBy: modelPayload.isProvidedBy,
|
|
425
|
-
status: modelPayload.status,
|
|
426
|
-
createdAt: modelPayload.createdAt,
|
|
427
|
-
updatedAt: modelPayload.updatedAt,
|
|
428
|
-
}));
|
|
429
|
-
}
|
|
430
|
-
const metadata = aiRuntime.getAIConfigProviderMetadata(provider);
|
|
431
|
-
process.stdout.write(`Connected AI provider: ${metadata.id}\n`);
|
|
432
|
-
if (modelId) {
|
|
433
|
-
process.stdout.write(`model: ${aiRuntime.extractAIConfigResourceId(modelId)}\n`);
|
|
434
|
-
}
|
|
435
|
-
process.stdout.write(`api-key: ${maskSecret(apiKey)}\n`);
|
|
436
|
-
},
|
|
255
|
+
handler: async (argv) => runAiCommand(argv),
|
|
437
256
|
};
|
|
438
257
|
//# sourceMappingURL=ai-command.js.map
|