ag-ai 0.1.7 → 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/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/common.d.ts +2 -2
- package/dist/k8s/common.js +4 -4
- package/dist/k8s/opencode.js +34 -5
- package/package.json +1 -1
|
@@ -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/common.d.ts
CHANGED
|
@@ -30,8 +30,8 @@ export declare const secretMount: (name: string, reference: SecretReference) =>
|
|
|
30
30
|
*/
|
|
31
31
|
export declare const secretFileMount: (name: string, reference: SecretReference, file: string) => SecretMount;
|
|
32
32
|
export declare const env: (name: string, value: string) => V1EnvVar;
|
|
33
|
-
/**
|
|
34
|
-
export declare const openCodeHealthProbe: (port: number) => {
|
|
33
|
+
/** Readiness must consume the model response, not just see a listening HTTP server. */
|
|
34
|
+
export declare const openCodeHealthProbe: (port: number, target?: 'health' | 'models') => {
|
|
35
35
|
exec: {
|
|
36
36
|
command: string[];
|
|
37
37
|
};
|
package/dist/k8s/common.js
CHANGED
|
@@ -63,16 +63,16 @@ const secretFileMount = (name, reference, file) => {
|
|
|
63
63
|
exports.secretFileMount = secretFileMount;
|
|
64
64
|
const env = (name, value) => ({ name, value });
|
|
65
65
|
exports.env = env;
|
|
66
|
-
/**
|
|
67
|
-
const openCodeHealthProbe = (port) => ({
|
|
66
|
+
/** Readiness must consume the model response, not just see a listening HTTP server. */
|
|
67
|
+
const openCodeHealthProbe = (port, target = 'health') => ({
|
|
68
68
|
exec: {
|
|
69
69
|
command: [
|
|
70
70
|
'node',
|
|
71
71
|
'-e',
|
|
72
|
-
`fetch('http://127.0.0.1:${port}
|
|
72
|
+
`fetch('http://127.0.0.1:${port}/${target === 'models' ? 'provider' : 'global/health'}',{signal:AbortSignal.timeout(3000),headers:{authorization:'Basic '+Buffer.from('opencode:'+(process.env.OPENCODE_SERVER_PASSWORD??'')).toString('base64')}}).then(async r=>{if(!r.ok){process.exit(1);return;}const data=await r.json();process.exit(${target === 'models' ? 'Array.isArray(data.connected)&&data.connected.length>0' : 'data.healthy===true'}?0:1);}).catch(()=>process.exit(1))`,
|
|
73
73
|
],
|
|
74
74
|
},
|
|
75
|
-
timeoutSeconds:
|
|
75
|
+
timeoutSeconds: 3,
|
|
76
76
|
periodSeconds: 10,
|
|
77
77
|
});
|
|
78
78
|
exports.openCodeHealthProbe = openCodeHealthProbe;
|
package/dist/k8s/opencode.js
CHANGED
|
@@ -7,6 +7,8 @@ const common_1 = require("./common");
|
|
|
7
7
|
const OPENCODE_PACKAGE = 'opencode-ai@1.18.31';
|
|
8
8
|
const OPENCODE_PREFIX = '/opt/opencode';
|
|
9
9
|
const OPENCODE_BIN = `${OPENCODE_PREFIX}/node_modules/.bin/opencode`;
|
|
10
|
+
const OPENCODE_AUTH_PATH = '/data/opencode/data/opencode/auth.json';
|
|
11
|
+
const OPENCODE_AUTH_SEED = '/var/run/secrets/ag-ai/opencode-auth/auth.json';
|
|
10
12
|
/** OpenCode server: durable state, basic auth from a mounted Secret, ClusterIP only. */
|
|
11
13
|
const opencodeStack = (context) => {
|
|
12
14
|
const { facilities } = context;
|
|
@@ -21,8 +23,8 @@ const opencodeStack = (context) => {
|
|
|
21
23
|
(0, common_1.stripClusterScopedNamespaces)(stack);
|
|
22
24
|
const password = context.secret('opencode-password');
|
|
23
25
|
const credentials = (0, common_1.secretMount)('opencode-password', password);
|
|
24
|
-
//
|
|
25
|
-
const auth = (0, common_1.secretFileMount)('opencode-auth', context.secret('opencode-auth'),
|
|
26
|
+
// Seed only: OAuth credentials must remain writable for login and token refresh.
|
|
27
|
+
const auth = (0, common_1.secretFileMount)('opencode-auth', context.secret('opencode-auth'), OPENCODE_AUTH_SEED);
|
|
26
28
|
const probe = (0, common_1.openCodeHealthProbe)(common_1.OPENCODE_PORT);
|
|
27
29
|
const container = {
|
|
28
30
|
name: 'opencode',
|
|
@@ -36,9 +38,12 @@ const opencodeStack = (context) => {
|
|
|
36
38
|
(0, common_1.env)('XDG_DATA_HOME', '/data/opencode/data'),
|
|
37
39
|
(0, common_1.env)('XDG_CONFIG_HOME', '/data/opencode/config'),
|
|
38
40
|
(0, common_1.env)('XDG_CACHE_HOME', '/data/opencode/cache'),
|
|
41
|
+
// The unrestricted /provider response processes thousands of models from
|
|
42
|
+
// unconfigured providers on every request, exceeding the runner's budget.
|
|
43
|
+
(0, common_1.env)('OPENCODE_CONFIG_CONTENT', JSON.stringify({ enabled_providers: ['openai', 'google'] })),
|
|
39
44
|
],
|
|
40
45
|
startupProbe: { ...probe, failureThreshold: 30 },
|
|
41
|
-
readinessProbe: { ...
|
|
46
|
+
readinessProbe: { ...(0, common_1.openCodeHealthProbe)(common_1.OPENCODE_PORT, 'models'), initialDelaySeconds: 5 },
|
|
42
47
|
livenessProbe: { ...probe, initialDelaySeconds: 15, periodSeconds: 30 },
|
|
43
48
|
resources: {
|
|
44
49
|
requests: { cpu: '50m', memory: '256Mi' },
|
|
@@ -47,7 +52,6 @@ const opencodeStack = (context) => {
|
|
|
47
52
|
volumeMounts: [
|
|
48
53
|
...dataMounts,
|
|
49
54
|
credentials.mount,
|
|
50
|
-
auth.mount,
|
|
51
55
|
{ name: 'opencode', mountPath: OPENCODE_PREFIX, readOnly: true },
|
|
52
56
|
{ name: 'tmp', mountPath: '/tmp' },
|
|
53
57
|
],
|
|
@@ -72,7 +76,32 @@ const opencodeStack = (context) => {
|
|
|
72
76
|
const deployment = stack.addDeployment({
|
|
73
77
|
name: 'server',
|
|
74
78
|
containers: [container],
|
|
75
|
-
initContainers: [
|
|
79
|
+
initContainers: [
|
|
80
|
+
installer,
|
|
81
|
+
{
|
|
82
|
+
name: 'initialize-opencode-auth',
|
|
83
|
+
image: facilities.image('ag-ai-opencode'),
|
|
84
|
+
command: [
|
|
85
|
+
'node',
|
|
86
|
+
'-e',
|
|
87
|
+
[
|
|
88
|
+
"const fs = require('node:fs');",
|
|
89
|
+
`const target = '${OPENCODE_AUTH_PATH}';`,
|
|
90
|
+
"fs.mkdirSync(require('node:path').dirname(target), { recursive: true, mode: 0o700 });",
|
|
91
|
+
`try { fs.copyFileSync('${OPENCODE_AUTH_SEED}', target, fs.constants.COPYFILE_EXCL); }`,
|
|
92
|
+
"catch (error) { if (error.code !== 'EEXIST') throw error; }",
|
|
93
|
+
// Kubernetes leaves an empty mount-point file behind when migrating off subPath.
|
|
94
|
+
`if (fs.statSync(target).size === 0) fs.copyFileSync('${OPENCODE_AUTH_SEED}', target);`,
|
|
95
|
+
'fs.chmodSync(target, 0o600);',
|
|
96
|
+
].join('\n'),
|
|
97
|
+
],
|
|
98
|
+
volumeMounts: [...dataMounts, auth.mount],
|
|
99
|
+
resources: {
|
|
100
|
+
requests: { cpu: '50m', memory: '64Mi' },
|
|
101
|
+
limits: { cpu: '100m', memory: '128Mi' },
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
],
|
|
76
105
|
volumes: [
|
|
77
106
|
dataVolume,
|
|
78
107
|
credentials.volume,
|