ag-ai 0.2.0 → 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.
@@ -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
- /** OpenCode health probe with basic auth, evaluated inside the container. */
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
  };
@@ -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
- /** OpenCode health probe with basic auth, evaluated inside the container. */
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}/global/health',{headers:{authorization:'Basic '+Buffer.from('opencode:'+(process.env.OPENCODE_SERVER_PASSWORD??'')).toString('base64')}}).then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))`,
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: 5,
75
+ timeoutSeconds: 3,
76
76
  periodSeconds: 10,
77
77
  });
78
78
  exports.openCodeHealthProbe = openCodeHealthProbe;
@@ -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
- // 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');
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: { ...probe, initialDelaySeconds: 5 },
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: [installer],
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ag-ai",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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",