ag-ai 0.1.1 → 0.1.3
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/k8s/common.d.ts +5 -0
- package/dist/k8s/common.js +23 -1
- package/dist/k8s/index.js +1 -0
- package/dist/k8s/opencode.js +51 -4
- package/dist/k8s/runner.js +41 -2
- package/package.json +1 -1
package/dist/k8s/common.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ export interface SecretMount {
|
|
|
24
24
|
file: string;
|
|
25
25
|
}
|
|
26
26
|
export declare const secretMount: (name: string, reference: SecretReference) => SecretMount;
|
|
27
|
+
/**
|
|
28
|
+
* Mount one Secret key as one concrete file path, for tools that read a config
|
|
29
|
+
* file rather than an environment variable (for example OpenCode's auth.json).
|
|
30
|
+
*/
|
|
31
|
+
export declare const secretFileMount: (name: string, reference: SecretReference, file: string) => SecretMount;
|
|
27
32
|
export declare const env: (name: string, value: string) => V1EnvVar;
|
|
28
33
|
/** OpenCode health probe with basic auth, evaluated inside the container. */
|
|
29
34
|
export declare const openCodeHealthProbe: (port: number) => {
|
package/dist/k8s/common.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.openCodeHealthProbe = exports.env = exports.secretMount = exports.stripClusterScopedNamespaces = exports.serviceUrl = exports.resourceName = exports.METAL_LB_KEY = exports.T3_SERVICE = exports.RUNNER_SERVICE = exports.OPENCODE_SERVICE = exports.T3_PORT = exports.GENERATION_PORT = exports.OPENCODE_PORT = exports.T3CODE_APP = exports.RUNNER_APP = exports.OPENCODE_APP = exports.NAMESPACE = void 0;
|
|
3
|
+
exports.openCodeHealthProbe = exports.env = exports.secretFileMount = exports.secretMount = exports.stripClusterScopedNamespaces = exports.serviceUrl = exports.resourceName = exports.METAL_LB_KEY = exports.T3_SERVICE = exports.RUNNER_SERVICE = exports.OPENCODE_SERVICE = exports.T3_PORT = exports.GENERATION_PORT = exports.OPENCODE_PORT = exports.T3CODE_APP = exports.RUNNER_APP = exports.OPENCODE_APP = exports.NAMESPACE = void 0;
|
|
4
4
|
const ag_rpi_mega_1 = require("ag-rpi-mega");
|
|
5
5
|
/** The plugin owns its identity; addresses, hosts, and secrets come from the host. */
|
|
6
6
|
exports.NAMESPACE = 'ag-ai';
|
|
@@ -39,6 +39,28 @@ const secretMount = (name, reference) => {
|
|
|
39
39
|
return { volume, mount, file: `${mountPath}/${reference.key}` };
|
|
40
40
|
};
|
|
41
41
|
exports.secretMount = secretMount;
|
|
42
|
+
/**
|
|
43
|
+
* Mount one Secret key as one concrete file path, for tools that read a config
|
|
44
|
+
* file rather than an environment variable (for example OpenCode's auth.json).
|
|
45
|
+
*/
|
|
46
|
+
const secretFileMount = (name, reference, file) => {
|
|
47
|
+
const volume = {
|
|
48
|
+
name: `${name}-file`,
|
|
49
|
+
secret: {
|
|
50
|
+
defaultMode: 0o400,
|
|
51
|
+
items: [{ key: reference.key, path: reference.key }],
|
|
52
|
+
secretName: reference.name,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const mount = {
|
|
56
|
+
mountPath: file,
|
|
57
|
+
name: volume.name,
|
|
58
|
+
readOnly: true,
|
|
59
|
+
subPath: reference.key,
|
|
60
|
+
};
|
|
61
|
+
return { volume, mount, file };
|
|
62
|
+
};
|
|
63
|
+
exports.secretFileMount = secretFileMount;
|
|
42
64
|
const env = (name, value) => ({ name, value });
|
|
43
65
|
exports.env = env;
|
|
44
66
|
/** OpenCode health probe with basic auth, evaluated inside the container. */
|
package/dist/k8s/index.js
CHANGED
package/dist/k8s/opencode.js
CHANGED
|
@@ -3,6 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.opencodeStack = void 0;
|
|
4
4
|
const ag_rpi_mega_1 = require("ag-rpi-mega");
|
|
5
5
|
const common_1 = require("./common");
|
|
6
|
+
/** The published OpenCode CLI is installed into a writable emptyDir at pod start. */
|
|
7
|
+
const OPENCODE_PACKAGE = 'opencode-ai@1.18.31';
|
|
8
|
+
const OPENCODE_PREFIX = '/opt/opencode';
|
|
9
|
+
const OPENCODE_BIN = `${OPENCODE_PREFIX}/node_modules/.bin/opencode`;
|
|
6
10
|
/** OpenCode server: durable state, basic auth from a mounted Secret, ClusterIP only. */
|
|
7
11
|
const opencodeStack = (context) => {
|
|
8
12
|
const { facilities } = context;
|
|
@@ -17,12 +21,14 @@ const opencodeStack = (context) => {
|
|
|
17
21
|
(0, common_1.stripClusterScopedNamespaces)(stack);
|
|
18
22
|
const password = context.secret('opencode-password');
|
|
19
23
|
const credentials = (0, common_1.secretMount)('opencode-password', password);
|
|
24
|
+
// Provider credentials live in OpenCode's auth.json under XDG_DATA_HOME.
|
|
25
|
+
const auth = (0, common_1.secretFileMount)('opencode-auth', context.secret('opencode-auth'), '/data/opencode/data/opencode/auth.json');
|
|
20
26
|
const probe = (0, common_1.openCodeHealthProbe)(common_1.OPENCODE_PORT);
|
|
21
27
|
const container = {
|
|
22
28
|
name: 'opencode',
|
|
23
29
|
image: facilities.image('ag-ai-opencode'),
|
|
24
30
|
imagePullPolicy: 'IfNotPresent',
|
|
25
|
-
command: [
|
|
31
|
+
command: [OPENCODE_BIN, 'serve', '--hostname', '0.0.0.0', '--port', String(common_1.OPENCODE_PORT)],
|
|
26
32
|
ports: [{ name: 'opencode', containerPort: common_1.OPENCODE_PORT, protocol: 'TCP' }],
|
|
27
33
|
env: [
|
|
28
34
|
(0, ag_rpi_mega_1.secretEnv)('OPENCODE_SERVER_PASSWORD', password),
|
|
@@ -38,17 +44,58 @@ const opencodeStack = (context) => {
|
|
|
38
44
|
requests: { cpu: '50m', memory: '256Mi' },
|
|
39
45
|
limits: { cpu: '1000m', memory: '1Gi' },
|
|
40
46
|
},
|
|
41
|
-
volumeMounts: [
|
|
47
|
+
volumeMounts: [
|
|
48
|
+
...dataMounts,
|
|
49
|
+
credentials.mount,
|
|
50
|
+
auth.mount,
|
|
51
|
+
{ name: 'opencode', mountPath: OPENCODE_PREFIX, readOnly: true },
|
|
52
|
+
{ name: 'tmp', mountPath: '/tmp' },
|
|
53
|
+
],
|
|
42
54
|
};
|
|
43
|
-
|
|
55
|
+
// The postinstall script selects and copies the platform binary, so this
|
|
56
|
+
// install must not be run with --ignore-scripts.
|
|
57
|
+
const installer = {
|
|
58
|
+
name: 'install-opencode',
|
|
59
|
+
image: facilities.image('ag-ai-opencode'),
|
|
60
|
+
command: ['npm'],
|
|
61
|
+
args: ['install', '--prefix', OPENCODE_PREFIX, '--no-audit', '--no-fund', OPENCODE_PACKAGE],
|
|
62
|
+
env: [(0, common_1.env)('HOME', '/tmp'), (0, common_1.env)('NPM_CONFIG_CACHE', '/tmp/npm-cache')],
|
|
63
|
+
volumeMounts: [
|
|
64
|
+
{ name: 'opencode', mountPath: OPENCODE_PREFIX },
|
|
65
|
+
{ name: 'tmp', mountPath: '/tmp' },
|
|
66
|
+
],
|
|
67
|
+
resources: {
|
|
68
|
+
requests: { cpu: '100m', memory: '256Mi' },
|
|
69
|
+
limits: { cpu: '1000m', memory: '1Gi' },
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
const deployment = stack.addDeployment({
|
|
44
73
|
name: 'server',
|
|
45
74
|
containers: [container],
|
|
46
|
-
|
|
75
|
+
initContainers: [installer],
|
|
76
|
+
volumes: [
|
|
77
|
+
dataVolume,
|
|
78
|
+
credentials.volume,
|
|
79
|
+
auth.volume,
|
|
80
|
+
{ name: 'opencode', emptyDir: { sizeLimit: '1Gi' } },
|
|
81
|
+
{ name: 'tmp', emptyDir: { sizeLimit: '1Gi' } },
|
|
82
|
+
],
|
|
47
83
|
replicas: 1,
|
|
48
84
|
architecture: facilities.arch,
|
|
49
85
|
securityMode: 'None',
|
|
50
86
|
changeNFSFilePerms: true,
|
|
51
87
|
});
|
|
88
|
+
const spec = deployment.spec;
|
|
89
|
+
// oxlint-disable-next-line typescript/no-unnecessary-condition -- the SDK class type hides runtime optionality.
|
|
90
|
+
const pod = spec?.template?.spec;
|
|
91
|
+
if (!spec || !pod)
|
|
92
|
+
throw new Error('opencode deployment is missing its pod specification');
|
|
93
|
+
// Group-own the emptyDirs for the non-root install and runtime containers.
|
|
94
|
+
pod.securityContext = {
|
|
95
|
+
fsGroup: 65534,
|
|
96
|
+
fsGroupChangePolicy: 'OnRootMismatch',
|
|
97
|
+
seccompProfile: { type: 'RuntimeDefault' },
|
|
98
|
+
};
|
|
52
99
|
stack.addService({ name: common_1.OPENCODE_SERVICE, tcp: [common_1.OPENCODE_PORT], type: 'ClusterIP' });
|
|
53
100
|
return stack;
|
|
54
101
|
};
|
package/dist/k8s/runner.js
CHANGED
|
@@ -9,6 +9,10 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
9
9
|
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
|
+
/** The published runtime is installed into a writable emptyDir at pod start. */
|
|
13
|
+
const AG_AI_PACKAGE = 'ag-ai@0.1.1';
|
|
14
|
+
const AG_AI_RUNTIME_DIR = '/opt/ag-ai';
|
|
15
|
+
const AG_AI_BRIDGE = `${AG_AI_RUNTIME_DIR}/node_modules/ag-ai/runtime/bridge.mjs`;
|
|
12
16
|
const networkInitScript = ({ generationCidr, generationPort, opencodePort, }) => networkInitTemplate
|
|
13
17
|
.replaceAll('__GENERATION_CIDR__', generationCidr)
|
|
14
18
|
.replaceAll('__GENERATION_PORT__', String(generationPort))
|
|
@@ -74,8 +78,8 @@ const runnerStack = (context) => {
|
|
|
74
78
|
name: 'runner',
|
|
75
79
|
image: facilities.image('ag-ai-runner'),
|
|
76
80
|
imagePullPolicy: 'IfNotPresent',
|
|
77
|
-
command: ['node',
|
|
78
|
-
workingDir: '/
|
|
81
|
+
command: ['node', AG_AI_BRIDGE],
|
|
82
|
+
workingDir: '/data',
|
|
79
83
|
ports: [{ name: 'generation', containerPort: common_1.GENERATION_PORT, protocol: 'TCP' }],
|
|
80
84
|
startupProbe: { ...healthProbe(false), failureThreshold: 30 },
|
|
81
85
|
livenessProbe: { ...healthProbe(false), failureThreshold: 3 },
|
|
@@ -106,6 +110,7 @@ const runnerStack = (context) => {
|
|
|
106
110
|
volumeMounts: [
|
|
107
111
|
...dataMounts,
|
|
108
112
|
{ name: 'runner-tmp', mountPath: '/tmp' },
|
|
113
|
+
{ name: 'runtime', mountPath: AG_AI_RUNTIME_DIR, readOnly: true },
|
|
109
114
|
secrets.runner.mount,
|
|
110
115
|
secrets.generation.mount,
|
|
111
116
|
secrets.analytica.mount,
|
|
@@ -119,6 +124,7 @@ const runnerStack = (context) => {
|
|
|
119
124
|
volumes: [
|
|
120
125
|
dataVolume,
|
|
121
126
|
scriptVolume,
|
|
127
|
+
{ name: 'runtime', emptyDir: { sizeLimit: '512Mi' } },
|
|
122
128
|
{ name: 'runner-tmp', emptyDir: { sizeLimit: '128Mi' } },
|
|
123
129
|
secrets.runner.volume,
|
|
124
130
|
secrets.generation.volume,
|
|
@@ -158,6 +164,39 @@ const runnerStack = (context) => {
|
|
|
158
164
|
limits: { cpu: '500m', memory: '128Mi' },
|
|
159
165
|
},
|
|
160
166
|
},
|
|
167
|
+
{
|
|
168
|
+
name: 'install-ag-ai',
|
|
169
|
+
image: facilities.image('ag-ai-runner'),
|
|
170
|
+
command: ['npm'],
|
|
171
|
+
args: [
|
|
172
|
+
'install',
|
|
173
|
+
'--prefix',
|
|
174
|
+
AG_AI_RUNTIME_DIR,
|
|
175
|
+
'--no-audit',
|
|
176
|
+
'--no-fund',
|
|
177
|
+
'--omit=dev',
|
|
178
|
+
'--ignore-scripts',
|
|
179
|
+
AG_AI_PACKAGE,
|
|
180
|
+
],
|
|
181
|
+
env: [(0, common_1.env)('HOME', '/tmp'), (0, common_1.env)('NPM_CONFIG_CACHE', '/tmp/npm-cache')],
|
|
182
|
+
volumeMounts: [
|
|
183
|
+
{ name: 'runtime', mountPath: AG_AI_RUNTIME_DIR },
|
|
184
|
+
{ name: 'runner-tmp', mountPath: '/tmp' },
|
|
185
|
+
],
|
|
186
|
+
securityContext: {
|
|
187
|
+
runAsNonRoot: true,
|
|
188
|
+
runAsUser: 65534,
|
|
189
|
+
runAsGroup: 65534,
|
|
190
|
+
readOnlyRootFilesystem: true,
|
|
191
|
+
allowPrivilegeEscalation: false,
|
|
192
|
+
capabilities: { drop: ['ALL'] },
|
|
193
|
+
seccompProfile: { type: 'RuntimeDefault' },
|
|
194
|
+
},
|
|
195
|
+
resources: {
|
|
196
|
+
requests: { cpu: '50m', memory: '64Mi' },
|
|
197
|
+
limits: { cpu: '500m', memory: '512Mi' },
|
|
198
|
+
},
|
|
199
|
+
},
|
|
161
200
|
{
|
|
162
201
|
name: 'prepare-generation-store',
|
|
163
202
|
image: facilities.image('ag-ai-runner'),
|