ag-ai 0.1.1 → 0.1.2

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.
@@ -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;
@@ -22,7 +26,7 @@ const opencodeStack = (context) => {
22
26
  name: 'opencode',
23
27
  image: facilities.image('ag-ai-opencode'),
24
28
  imagePullPolicy: 'IfNotPresent',
25
- command: ['opencode', 'serve', '--hostname', '0.0.0.0', '--port', String(common_1.OPENCODE_PORT)],
29
+ command: [OPENCODE_BIN, 'serve', '--hostname', '0.0.0.0', '--port', String(common_1.OPENCODE_PORT)],
26
30
  ports: [{ name: 'opencode', containerPort: common_1.OPENCODE_PORT, protocol: 'TCP' }],
27
31
  env: [
28
32
  (0, ag_rpi_mega_1.secretEnv)('OPENCODE_SERVER_PASSWORD', password),
@@ -38,17 +42,56 @@ const opencodeStack = (context) => {
38
42
  requests: { cpu: '50m', memory: '256Mi' },
39
43
  limits: { cpu: '1000m', memory: '1Gi' },
40
44
  },
41
- volumeMounts: [...dataMounts, credentials.mount, { name: 'tmp', mountPath: '/tmp' }],
45
+ volumeMounts: [
46
+ ...dataMounts,
47
+ credentials.mount,
48
+ { name: 'opencode', mountPath: OPENCODE_PREFIX, readOnly: true },
49
+ { name: 'tmp', mountPath: '/tmp' },
50
+ ],
42
51
  };
43
- stack.addDeployment({
52
+ // The postinstall script selects and copies the platform binary, so this
53
+ // install must not be run with --ignore-scripts.
54
+ const installer = {
55
+ name: 'install-opencode',
56
+ image: facilities.image('ag-ai-opencode'),
57
+ command: ['npm'],
58
+ args: ['install', '--prefix', OPENCODE_PREFIX, '--no-audit', '--no-fund', OPENCODE_PACKAGE],
59
+ env: [(0, common_1.env)('HOME', '/tmp'), (0, common_1.env)('NPM_CONFIG_CACHE', '/tmp/npm-cache')],
60
+ volumeMounts: [
61
+ { name: 'opencode', mountPath: OPENCODE_PREFIX },
62
+ { name: 'tmp', mountPath: '/tmp' },
63
+ ],
64
+ resources: {
65
+ requests: { cpu: '100m', memory: '256Mi' },
66
+ limits: { cpu: '1000m', memory: '1Gi' },
67
+ },
68
+ };
69
+ const deployment = stack.addDeployment({
44
70
  name: 'server',
45
71
  containers: [container],
46
- volumes: [dataVolume, credentials.volume, { name: 'tmp', emptyDir: { sizeLimit: '256Mi' } }],
72
+ initContainers: [installer],
73
+ volumes: [
74
+ dataVolume,
75
+ credentials.volume,
76
+ { name: 'opencode', emptyDir: { sizeLimit: '1Gi' } },
77
+ { name: 'tmp', emptyDir: { sizeLimit: '1Gi' } },
78
+ ],
47
79
  replicas: 1,
48
80
  architecture: facilities.arch,
49
81
  securityMode: 'None',
50
82
  changeNFSFilePerms: true,
51
83
  });
84
+ const spec = deployment.spec;
85
+ // oxlint-disable-next-line typescript/no-unnecessary-condition -- the SDK class type hides runtime optionality.
86
+ const pod = spec?.template?.spec;
87
+ if (!spec || !pod)
88
+ throw new Error('opencode deployment is missing its pod specification');
89
+ // Group-own the emptyDirs for the non-root install and runtime containers.
90
+ pod.securityContext = {
91
+ fsGroup: 65534,
92
+ fsGroupChangePolicy: 'OnRootMismatch',
93
+ seccompProfile: { type: 'RuntimeDefault' },
94
+ };
52
95
  stack.addService({ name: common_1.OPENCODE_SERVICE, tcp: [common_1.OPENCODE_PORT], type: 'ClusterIP' });
53
96
  return stack;
54
97
  };
@@ -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', 'runtime/bridge.mjs'],
78
- workingDir: '/app',
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'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ag-ai",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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",