ag-ai 0.1.6 → 0.2.0
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 +5 -1
- package/dist/client/adapters/google.js +4 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/internal/apikey.js +2 -2
- package/dist/k8s/index.js +1 -1
- package/dist/k8s/runner.js +7 -4
- package/package.json +11 -7
- package/runtime/bridge.mjs +0 -7
- package/runtime/index.mjs +0 -1
- package/runtime/protocol.mjs +0 -26
- package/runtime/tests/bridge.test.mjs +0 -554
- package/runtime/tests/image-adapter.test.mjs +0 -148
- package/runtime/tests/protocol.test.mjs +0 -156
- package/runtime/tests/server.test.mjs +0 -231
- package/runtime/tests/store.test.mjs +0 -114
- package/runtime/tests/telemetry.test.mjs +0 -138
package/README.md
CHANGED
|
@@ -9,13 +9,17 @@ Provider-neutral AI client and cluster runtime for the
|
|
|
9
9
|
| --------------- | --------------------------------------------------------------------- |
|
|
10
10
|
| `ag-ai` | Client SDK: generate, generateText, promptDirect, promptImage, models |
|
|
11
11
|
| `ag-ai/client` | Client SDK entry point |
|
|
12
|
-
| `ag-ai/runtime` | Cluster runtime: generation protocol, store, server, bridge (ESM) |
|
|
13
12
|
| `ag-ai/k8s` | Cluster plugin descriptor (`ClusterPlugin`) |
|
|
13
|
+
| `ag-ai/runtime` | Cluster runtime: generation protocol, store, server, bridge (ESM) |
|
|
14
14
|
| `ag-ai/image` | Neutral image-output adapter (provider per implementation) |
|
|
15
15
|
|
|
16
16
|
The client compiles to CommonJS. The runtime is hand-authored ESM and shipped
|
|
17
17
|
as-is, because it runs inside the pod with no build step.
|
|
18
18
|
|
|
19
|
+
The client-only dependencies (`@google/genai`, `node-cache`) are declared as
|
|
20
|
+
`optionalDependencies`; the cluster runtime install omits them because the
|
|
21
|
+
runtime imports only Node builtins.
|
|
22
|
+
|
|
19
23
|
## Boundaries
|
|
20
24
|
|
|
21
25
|
- No prompts. Prompt templates belong to consumers (gec.dev, bolt.garden, …).
|
|
@@ -390,8 +390,7 @@ const generateGoogle = async (request, config = {}) => {
|
|
|
390
390
|
// Reactive per-model backoff: a 429 skips the model until its window
|
|
391
391
|
// elapses while every other model keeps serving.
|
|
392
392
|
if ((0, quota_1.isModelBackedOff)(selectedModel)) {
|
|
393
|
-
(0, log_1.debug)(
|
|
394
|
-
model: selectedModel,
|
|
393
|
+
(0, log_1.debug)(`[${selectedModel}] skipping rate-limited model`, {
|
|
395
394
|
remainingMs: (0, quota_1.modelBackoffRemainingMs)(selectedModel),
|
|
396
395
|
});
|
|
397
396
|
continue;
|
|
@@ -400,7 +399,7 @@ const generateGoogle = async (request, config = {}) => {
|
|
|
400
399
|
...requestConfig,
|
|
401
400
|
model: selectedModel,
|
|
402
401
|
};
|
|
403
|
-
(0, log_1.info)(
|
|
402
|
+
(0, log_1.info)(`[${selectedModel}] generation request`, { ident: request.ident });
|
|
404
403
|
try {
|
|
405
404
|
// oxlint-disable-next-line no-await-in-loop -- model fallback is sequential by design
|
|
406
405
|
const response = await awaitWithSignal(ai.models.generateContent(selectedRequest), controller.signal);
|
|
@@ -411,7 +410,7 @@ const generateGoogle = async (request, config = {}) => {
|
|
|
411
410
|
generatedAt: config.dependencies?.now?.() ?? Date.now(),
|
|
412
411
|
...(usage === undefined ? {} : { usage }),
|
|
413
412
|
};
|
|
414
|
-
(0, log_1.debug)(
|
|
413
|
+
(0, log_1.debug)(`[${result.model}] generation completed`, request.ident);
|
|
415
414
|
return result;
|
|
416
415
|
}
|
|
417
416
|
catch (error) {
|
|
@@ -424,8 +423,7 @@ const generateGoogle = async (request, config = {}) => {
|
|
|
424
423
|
const unavailableModel = failureStatus === 404 && message.includes(selectedModel);
|
|
425
424
|
// Fallback only; per-model backoff below is keyed by rate limiting.
|
|
426
425
|
if (rateLimited || overloadedModel || unavailableModel) {
|
|
427
|
-
(0, log_1.warn)(
|
|
428
|
-
model: selectedModel,
|
|
426
|
+
(0, log_1.warn)(`[${selectedModel}] generation attempt failed; trying next available combination`, {
|
|
429
427
|
status: failureStatus,
|
|
430
428
|
});
|
|
431
429
|
(0, apikey_1.blockKeyService)(key, `gemini-${selectedModel}`);
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./client"), exports);
|
|
18
18
|
__exportStar(require("./internal/apikey"), exports);
|
|
19
|
+
__exportStar(require("./internal/log"), exports);
|
package/dist/internal/apikey.js
CHANGED
|
@@ -33,7 +33,7 @@ const getAvailableCombinations = (service, resetIfExhausted = true) => {
|
|
|
33
33
|
}
|
|
34
34
|
// If all combinations are blocklisted, clear the blocklist and try again
|
|
35
35
|
if (combinations.length === 0 && resetIfExhausted) {
|
|
36
|
-
(0, log_1.warn)(`
|
|
36
|
+
(0, log_1.warn)(`[${service}] all API key + service combinations were blocklisted; clearing blocklist and trying again.`);
|
|
37
37
|
blocklist.flushAll();
|
|
38
38
|
for (const key of keys) {
|
|
39
39
|
combinations.push({ service, key });
|
|
@@ -45,7 +45,7 @@ const getAvailableCombinations = (service, resetIfExhausted = true) => {
|
|
|
45
45
|
exports.getAvailableCombinations = getAvailableCombinations;
|
|
46
46
|
// Block a key+service combination
|
|
47
47
|
const blockKeyService = (apiKey, service) => {
|
|
48
|
-
(0, log_1.warn)(`key+service blocklisted
|
|
48
|
+
(0, log_1.warn)(`[${service}] key+service blocklisted`);
|
|
49
49
|
addToBlocklist(apiKey, service);
|
|
50
50
|
};
|
|
51
51
|
exports.blockKeyService = blockKeyService;
|
package/dist/k8s/index.js
CHANGED
|
@@ -14,7 +14,7 @@ const t3code_1 = require("./t3code");
|
|
|
14
14
|
exports.plugin = {
|
|
15
15
|
name: 'ag-ai',
|
|
16
16
|
namespace: common_1.NAMESPACE,
|
|
17
|
-
requiredEnv: ['AG_AI_QUEUE_URL', 'AG_AI_GENERATION_CIDR'],
|
|
17
|
+
requiredEnv: ['AG_AI_QUEUE_URL', 'AG_AI_GENERATION_CIDR', 'AG_AI_PACKAGE'],
|
|
18
18
|
requiredSecrets: [
|
|
19
19
|
'runner-token',
|
|
20
20
|
'generation-token',
|
package/dist/k8s/runner.js
CHANGED
|
@@ -10,7 +10,6 @@ const ag_rpi_mega_1 = require("ag-rpi-mega");
|
|
|
10
10
|
const common_1 = require("./common");
|
|
11
11
|
const networkInitTemplate = (0, node_fs_1.readFileSync)(node_path_1.default.join(__dirname, 'assets', 'network-init.sh'), 'utf8');
|
|
12
12
|
/** The published runtime is installed into a writable emptyDir at pod start. */
|
|
13
|
-
const AG_AI_PACKAGE = 'ag-ai@^0.1.1';
|
|
14
13
|
const AG_AI_RUNTIME_DIR = '/opt/ag-ai';
|
|
15
14
|
const AG_AI_BRIDGE = `${AG_AI_RUNTIME_DIR}/node_modules/ag-ai/runtime/bridge.mjs`;
|
|
16
15
|
const networkInitScript = ({ generationCidr, generationPort, opencodePort, }) => networkInitTemplate
|
|
@@ -41,6 +40,9 @@ const runnerStack = (context) => {
|
|
|
41
40
|
throw new Error('AG_AI_GENERATION_CIDR is required');
|
|
42
41
|
const imageProvider = context.env.AG_AI_IMAGE_PROVIDER ?? 'openai';
|
|
43
42
|
const opencodeProvider = context.env.AG_AI_OPENCODE_PROVIDER;
|
|
43
|
+
const agAiPackage = context.env.AG_AI_PACKAGE;
|
|
44
|
+
if (!agAiPackage)
|
|
45
|
+
throw new Error('AG_AI_PACKAGE is required');
|
|
44
46
|
const metalLbAddress = facilities.metalLb(common_1.METAL_LB_KEY);
|
|
45
47
|
if (!metalLbAddress)
|
|
46
48
|
throw new Error(`no MetalLB address is configured for ${common_1.METAL_LB_KEY}`);
|
|
@@ -96,8 +98,6 @@ const runnerStack = (context) => {
|
|
|
96
98
|
(0, common_1.env)('AG_AI_RUNNER_ID', common_1.RUNNER_APP),
|
|
97
99
|
(0, common_1.env)('AG_AI_WORKSPACE', '/data/workspace'),
|
|
98
100
|
(0, common_1.env)('AG_AI_MAIN_WORKSPACE', '/data/workspace'),
|
|
99
|
-
(0, common_1.env)('AG_AI_GENERATED_IMAGES', '/data/generated-images'),
|
|
100
|
-
(0, common_1.env)('AG_AI_MAIN_GENERATED_IMAGES', '/data/generated-images'),
|
|
101
101
|
{ name: 'AG_AI_GENERATION_HOST', valueFrom: { fieldRef: { fieldPath: 'status.podIP' } } },
|
|
102
102
|
(0, common_1.env)('AG_AI_GENERATION_PORT', String(common_1.GENERATION_PORT)),
|
|
103
103
|
(0, common_1.env)('AG_AI_GENERATION_STORE', '/data/generation-jobs'),
|
|
@@ -168,6 +168,8 @@ const runnerStack = (context) => {
|
|
|
168
168
|
name: 'install-ag-ai',
|
|
169
169
|
image: facilities.image('ag-ai-runner'),
|
|
170
170
|
command: ['npm'],
|
|
171
|
+
// The runtime imports only Node builtins. Client-only dependencies are
|
|
172
|
+
// optional, so omitting them keeps the pod install lean.
|
|
171
173
|
args: [
|
|
172
174
|
'install',
|
|
173
175
|
'--prefix',
|
|
@@ -175,8 +177,9 @@ const runnerStack = (context) => {
|
|
|
175
177
|
'--no-audit',
|
|
176
178
|
'--no-fund',
|
|
177
179
|
'--omit=dev',
|
|
180
|
+
'--omit=optional',
|
|
178
181
|
'--ignore-scripts',
|
|
179
|
-
|
|
182
|
+
agAiPackage,
|
|
180
183
|
],
|
|
181
184
|
env: [(0, common_1.env)('HOME', '/tmp'), (0, common_1.env)('NPM_CONFIG_CACHE', '/tmp/npm-cache')],
|
|
182
185
|
volumeMounts: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ag-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Provider-neutral AI client and cluster runtime for the rpi-mega cluster.",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "admin@gec.dev",
|
|
@@ -11,20 +11,20 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"dist/**/*",
|
|
13
13
|
"runtime/**/*",
|
|
14
|
+
"!runtime/tests",
|
|
14
15
|
"README.md"
|
|
15
16
|
],
|
|
16
17
|
"main": "./dist/index.js",
|
|
17
18
|
"types": "./dist/index.d.ts",
|
|
18
19
|
"exports": {
|
|
19
20
|
".": "./dist/index.js",
|
|
20
|
-
"./client": "./dist/index.js",
|
|
21
|
+
"./client": "./dist/client/index.js",
|
|
21
22
|
"./k8s": "./dist/k8s/index.js",
|
|
22
|
-
"./runtime": "./runtime/index.mjs"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"@google/genai": "^2.19.0",
|
|
26
|
-
"node-cache": "^5.1.2"
|
|
23
|
+
"./runtime": "./runtime/index.mjs",
|
|
24
|
+
"./image": "./runtime/image/adapter.mjs",
|
|
25
|
+
"./package.json": "./package.json"
|
|
27
26
|
},
|
|
27
|
+
"dependencies": {},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@kubernetes/client-node": "2.0.0",
|
|
30
30
|
"@types/node": "^26.4.1",
|
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
"optional": true
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
|
+
"optionalDependencies": {
|
|
46
|
+
"@google/genai": "^2.19.0",
|
|
47
|
+
"node-cache": "^5.1.2"
|
|
48
|
+
},
|
|
45
49
|
"scripts": {
|
|
46
50
|
"preinstall": "npx only-allow pnpm",
|
|
47
51
|
"format": "e7-oxfmt",
|
package/runtime/bridge.mjs
CHANGED
|
@@ -19,8 +19,6 @@ const DEFAULT_QUEUE_URL = 'https://queue.invalid/api/ai/runner';
|
|
|
19
19
|
const DEFAULT_TOKEN_FILE = '/var/run/secrets/ag-ai-runner/token';
|
|
20
20
|
const DEFAULT_WORKSPACE = '/workspace';
|
|
21
21
|
const DEFAULT_MAIN_WORKSPACE = '/data/workspace';
|
|
22
|
-
const DEFAULT_GENERATED_IMAGES = '/generated-images';
|
|
23
|
-
const DEFAULT_MAIN_GENERATED_IMAGES = '/data/ag-ai/generated_images';
|
|
24
22
|
const DEFAULT_GENERATION_PORT = 4501;
|
|
25
23
|
const DEFAULT_GENERATION_STORE = '/generation-jobs';
|
|
26
24
|
const DEFAULT_GENERATION_TOKEN_FILE = '/var/run/secrets/ag-ai-generation/token';
|
|
@@ -824,10 +822,6 @@ const readConfig = async (env = process.env) => {
|
|
|
824
822
|
runnerId: env.AG_AI_RUNNER_ID ?? `ag-ai-${os.hostname()}-${randomUUID()}`,
|
|
825
823
|
workspace: path.resolve(env.AG_AI_WORKSPACE ?? DEFAULT_WORKSPACE),
|
|
826
824
|
mainWorkspace: path.resolve(env.AG_AI_MAIN_WORKSPACE ?? DEFAULT_MAIN_WORKSPACE),
|
|
827
|
-
generatedImages: path.resolve(env.AG_AI_GENERATED_IMAGES ?? DEFAULT_GENERATED_IMAGES),
|
|
828
|
-
mainGeneratedImages: path.resolve(
|
|
829
|
-
env.AG_AI_MAIN_GENERATED_IMAGES ?? DEFAULT_MAIN_GENERATED_IMAGES,
|
|
830
|
-
),
|
|
831
825
|
generationHost: env.AG_AI_GENERATION_HOST,
|
|
832
826
|
generationPeerPrefix: env.AG_AI_GENERATION_PEER_PREFIX,
|
|
833
827
|
generationPort: env.AG_AI_GENERATION_PORT ?? DEFAULT_GENERATION_PORT,
|
|
@@ -847,7 +841,6 @@ const main = async () => {
|
|
|
847
841
|
const analytics = new AnalyticaEvents({ token: config.analyticaToken, source: config.runnerId });
|
|
848
842
|
void analytics.emit('pod', 'started');
|
|
849
843
|
await safeRoot(config.workspace, 'workspace root');
|
|
850
|
-
await safeRoot(config.generatedImages, 'generated-image root');
|
|
851
844
|
|
|
852
845
|
const imageApiKey = await readOptionalToken(config.imageApiKeyFile);
|
|
853
846
|
let imageAdapter;
|
package/runtime/index.mjs
CHANGED
package/runtime/protocol.mjs
CHANGED
|
@@ -217,31 +217,6 @@ const normalizeOutputSchema = (value) => {
|
|
|
217
217
|
return { name, schema: object.schema };
|
|
218
218
|
};
|
|
219
219
|
|
|
220
|
-
const toRunnerInput = (request, runId, models) => {
|
|
221
|
-
const normalized =
|
|
222
|
-
request?.model && request?.reasoningEffort && request?.version === GENERATION_VERSION
|
|
223
|
-
? request
|
|
224
|
-
: normalizeGenerationRequest(request, models);
|
|
225
|
-
const input = normalized.input.map((message) => ({
|
|
226
|
-
role: message.role,
|
|
227
|
-
content: message.content.map((part) => {
|
|
228
|
-
if (part.type === 'text') return { type: 'text', text: part.text };
|
|
229
|
-
if (part.type === 'image') return { type: 'image', mimeType: part.mimeType, data: part.data };
|
|
230
|
-
throw unsupported(`input ${part.type}`);
|
|
231
|
-
}),
|
|
232
|
-
}));
|
|
233
|
-
return {
|
|
234
|
-
runId,
|
|
235
|
-
instructions: normalized.instructions,
|
|
236
|
-
messages: input,
|
|
237
|
-
output: normalized.output,
|
|
238
|
-
imageGeneration: normalized.output.includes('image'),
|
|
239
|
-
model: normalized.model,
|
|
240
|
-
reasoningEffort: normalized.reasoningEffort,
|
|
241
|
-
...(normalized.outputSchema === undefined ? {} : { outputSchema: normalized.outputSchema }),
|
|
242
|
-
};
|
|
243
|
-
};
|
|
244
|
-
|
|
245
220
|
const normalizeGenerationRequest = (value, models) => {
|
|
246
221
|
const request = asObject(value);
|
|
247
222
|
if (request?.version !== GENERATION_VERSION) {
|
|
@@ -380,5 +355,4 @@ export {
|
|
|
380
355
|
normalizeModelCatalog,
|
|
381
356
|
publicModelCatalog,
|
|
382
357
|
requestDigest,
|
|
383
|
-
toRunnerInput,
|
|
384
358
|
};
|