nexus-ai-pro 2.7.0 → 2.9.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.
Files changed (229) hide show
  1. package/API_STABILITY.md +119 -3
  2. package/CHANGELOG.md +241 -1
  3. package/README.md +21 -3
  4. package/SECURITY.md +41 -1
  5. package/dist/batch/manager.js +16 -33
  6. package/dist/batch/openai-client.d.ts +83 -0
  7. package/dist/batch/openai-client.js +120 -0
  8. package/dist/batch/openai.d.ts +12 -21
  9. package/dist/batch/openai.js +33 -125
  10. package/dist/batch/poll.d.ts +22 -0
  11. package/dist/batch/poll.js +30 -0
  12. package/dist/cli/models.d.ts +36 -0
  13. package/dist/cli/models.js +170 -0
  14. package/dist/cli.js +11 -1
  15. package/dist/deep-agent/helpers.d.ts +4 -0
  16. package/dist/deep-agent/helpers.js +56 -4
  17. package/dist/doctor/index.d.ts +16 -1
  18. package/dist/doctor/index.js +10 -5
  19. package/dist/embeddings/adapters.d.ts +2 -2
  20. package/dist/embeddings/adapters.js +24 -13
  21. package/dist/embeddings/batch-google.d.ts +40 -0
  22. package/dist/embeddings/batch-google.js +128 -0
  23. package/dist/embeddings/batch-mock.d.ts +45 -0
  24. package/dist/embeddings/batch-mock.js +71 -0
  25. package/dist/embeddings/batch-openai.d.ts +23 -0
  26. package/dist/embeddings/batch-openai.js +55 -0
  27. package/dist/embeddings/batch.d.ts +230 -0
  28. package/dist/embeddings/batch.js +282 -0
  29. package/dist/embeddings/cache.d.ts +81 -0
  30. package/dist/embeddings/cache.js +116 -0
  31. package/dist/embeddings/engine.d.ts +7 -0
  32. package/dist/embeddings/engine.js +110 -68
  33. package/dist/embeddings/google-wire.d.ts +32 -0
  34. package/dist/embeddings/google-wire.js +33 -0
  35. package/dist/embeddings/index.d.ts +1 -1
  36. package/dist/embeddings/inputs.d.ts +30 -0
  37. package/dist/embeddings/inputs.js +103 -0
  38. package/dist/embeddings/mock.d.ts +7 -1
  39. package/dist/embeddings/mock.js +8 -2
  40. package/dist/embeddings/models.js +21 -0
  41. package/dist/evaluate/online.d.ts +5 -0
  42. package/dist/evaluate/online.js +74 -5
  43. package/dist/images/asset-stores.d.ts +47 -1
  44. package/dist/images/asset-stores.js +135 -2
  45. package/dist/images/asset-support.d.ts +94 -1
  46. package/dist/images/asset-support.js +159 -3
  47. package/dist/images/assets.d.ts +10 -3
  48. package/dist/images/assets.js +15 -2
  49. package/dist/lifecycle/budget.d.ts +60 -3
  50. package/dist/lifecycle/budget.js +147 -3
  51. package/dist/lifecycle/index.d.ts +2 -2
  52. package/dist/lifecycle/reservations.d.ts +46 -0
  53. package/dist/lifecycle/reservations.js +67 -0
  54. package/dist/media/index.d.ts +244 -0
  55. package/dist/media/index.js +262 -0
  56. package/dist/media/mistral.d.ts +34 -0
  57. package/dist/media/mistral.js +90 -0
  58. package/dist/media/mock.d.ts +36 -0
  59. package/dist/media/mock.js +45 -0
  60. package/dist/media/rag.d.ts +216 -0
  61. package/dist/media/rag.js +340 -0
  62. package/dist/models/data.d.ts +87 -0
  63. package/dist/models/data.js +235 -0
  64. package/dist/models/registry.d.ts +2 -1
  65. package/dist/models/registry.js +3 -2
  66. package/dist/postgres/embedding-cache.d.ts +29 -0
  67. package/dist/postgres/embedding-cache.js +58 -0
  68. package/dist/postgres/tenancy.d.ts +26 -1
  69. package/dist/postgres/tenancy.js +116 -1
  70. package/dist/postgres/traces.d.ts +2 -1
  71. package/dist/postgres/traces.js +13 -1
  72. package/dist/rag/chroma.d.ts +13 -1
  73. package/dist/rag/chroma.js +36 -13
  74. package/dist/rag/pinecone.js +2 -2
  75. package/dist/rag/pipeline.d.ts +17 -2
  76. package/dist/rag/pipeline.js +95 -14
  77. package/dist/rag/qdrant.js +2 -2
  78. package/dist/rag/redis.d.ts +2 -1
  79. package/dist/rag/redis.js +56 -18
  80. package/dist/rag/vector-helpers.d.ts +5 -0
  81. package/dist/rag/vector-helpers.js +7 -0
  82. package/dist/rag/weaviate.d.ts +15 -1
  83. package/dist/rag/weaviate.js +75 -12
  84. package/dist/server/tenancy.d.ts +33 -2
  85. package/dist/server/tenancy.js +162 -0
  86. package/dist/sqlite/embedding-cache.d.ts +34 -0
  87. package/dist/sqlite/embedding-cache.js +61 -0
  88. package/dist/tracing/index.d.ts +2 -2
  89. package/dist/tracing/index.js +1 -1
  90. package/dist/tracing/query.d.ts +5 -1
  91. package/dist/tracing/query.js +14 -1
  92. package/dist/tracing/stores.d.ts +2 -2
  93. package/dist/tracing/stores.js +2 -2
  94. package/dist/types/config.d.ts +5 -0
  95. package/dist/types/embeddings.d.ts +87 -5
  96. package/dist/types/images.d.ts +2 -2
  97. package/dist/types/lifecycle.d.ts +92 -1
  98. package/dist/types/tracing.d.ts +20 -1
  99. package/dist/utils/redis-command.d.ts +15 -0
  100. package/dist/utils/redis-command.js +5 -0
  101. package/dist/utils/sha256.d.ts +9 -0
  102. package/dist/utils/sha256.js +6 -0
  103. package/dist/utils/vector-bytes.d.ts +11 -0
  104. package/dist/utils/vector-bytes.js +18 -0
  105. package/dist/version.d.ts +1 -1
  106. package/dist/version.js +1 -1
  107. package/dist/video/errors.d.ts +37 -0
  108. package/dist/video/errors.js +22 -0
  109. package/dist/video/google.d.ts +52 -0
  110. package/dist/video/google.js +248 -0
  111. package/dist/video/index.d.ts +426 -0
  112. package/dist/video/index.js +666 -0
  113. package/dist/video/mock.d.ts +62 -0
  114. package/dist/video/mock.js +97 -0
  115. package/dist/video/runway.d.ts +47 -0
  116. package/dist/video/runway.js +241 -0
  117. package/dist-cjs/batch/manager.js +16 -33
  118. package/dist-cjs/batch/openai-client.d.ts +83 -0
  119. package/dist-cjs/batch/openai-client.js +125 -0
  120. package/dist-cjs/batch/openai.d.ts +12 -21
  121. package/dist-cjs/batch/openai.js +32 -124
  122. package/dist-cjs/batch/poll.d.ts +22 -0
  123. package/dist-cjs/batch/poll.js +33 -0
  124. package/dist-cjs/cli/models.d.ts +36 -0
  125. package/dist-cjs/cli/models.js +175 -0
  126. package/dist-cjs/deep-agent/helpers.d.ts +4 -0
  127. package/dist-cjs/deep-agent/helpers.js +56 -4
  128. package/dist-cjs/doctor/index.d.ts +16 -1
  129. package/dist-cjs/doctor/index.js +10 -5
  130. package/dist-cjs/embeddings/adapters.d.ts +2 -2
  131. package/dist-cjs/embeddings/adapters.js +23 -12
  132. package/dist-cjs/embeddings/batch-google.d.ts +40 -0
  133. package/dist-cjs/embeddings/batch-google.js +132 -0
  134. package/dist-cjs/embeddings/batch-mock.d.ts +45 -0
  135. package/dist-cjs/embeddings/batch-mock.js +75 -0
  136. package/dist-cjs/embeddings/batch-openai.d.ts +23 -0
  137. package/dist-cjs/embeddings/batch-openai.js +59 -0
  138. package/dist-cjs/embeddings/batch.d.ts +230 -0
  139. package/dist-cjs/embeddings/batch.js +320 -0
  140. package/dist-cjs/embeddings/cache.d.ts +81 -0
  141. package/dist-cjs/embeddings/cache.js +125 -0
  142. package/dist-cjs/embeddings/engine.d.ts +7 -0
  143. package/dist-cjs/embeddings/engine.js +143 -68
  144. package/dist-cjs/embeddings/google-wire.d.ts +32 -0
  145. package/dist-cjs/embeddings/google-wire.js +38 -0
  146. package/dist-cjs/embeddings/index.d.ts +1 -1
  147. package/dist-cjs/embeddings/inputs.d.ts +30 -0
  148. package/dist-cjs/embeddings/inputs.js +141 -0
  149. package/dist-cjs/embeddings/mock.d.ts +7 -1
  150. package/dist-cjs/embeddings/mock.js +8 -2
  151. package/dist-cjs/embeddings/models.js +21 -0
  152. package/dist-cjs/evaluate/online.d.ts +5 -0
  153. package/dist-cjs/evaluate/online.js +74 -5
  154. package/dist-cjs/images/asset-stores.d.ts +47 -1
  155. package/dist-cjs/images/asset-stores.js +133 -0
  156. package/dist-cjs/images/asset-support.d.ts +94 -1
  157. package/dist-cjs/images/asset-support.js +169 -3
  158. package/dist-cjs/images/assets.d.ts +10 -3
  159. package/dist-cjs/images/assets.js +17 -1
  160. package/dist-cjs/lifecycle/budget.d.ts +60 -3
  161. package/dist-cjs/lifecycle/budget.js +147 -3
  162. package/dist-cjs/lifecycle/index.d.ts +2 -2
  163. package/dist-cjs/lifecycle/reservations.d.ts +46 -0
  164. package/dist-cjs/lifecycle/reservations.js +71 -0
  165. package/dist-cjs/media/index.d.ts +244 -0
  166. package/dist-cjs/media/index.js +269 -0
  167. package/dist-cjs/media/mistral.d.ts +34 -0
  168. package/dist-cjs/media/mistral.js +94 -0
  169. package/dist-cjs/media/mock.d.ts +36 -0
  170. package/dist-cjs/media/mock.js +49 -0
  171. package/dist-cjs/media/rag.d.ts +216 -0
  172. package/dist-cjs/media/rag.js +344 -0
  173. package/dist-cjs/models/data.d.ts +87 -0
  174. package/dist-cjs/models/data.js +245 -0
  175. package/dist-cjs/models/registry.d.ts +2 -1
  176. package/dist-cjs/models/registry.js +3 -2
  177. package/dist-cjs/postgres/embedding-cache.d.ts +29 -0
  178. package/dist-cjs/postgres/embedding-cache.js +96 -0
  179. package/dist-cjs/postgres/tenancy.d.ts +26 -1
  180. package/dist-cjs/postgres/tenancy.js +115 -0
  181. package/dist-cjs/postgres/traces.d.ts +2 -1
  182. package/dist-cjs/postgres/traces.js +13 -1
  183. package/dist-cjs/rag/chroma.d.ts +13 -1
  184. package/dist-cjs/rag/chroma.js +35 -12
  185. package/dist-cjs/rag/pinecone.js +1 -1
  186. package/dist-cjs/rag/pipeline.d.ts +17 -2
  187. package/dist-cjs/rag/pipeline.js +95 -14
  188. package/dist-cjs/rag/qdrant.js +1 -1
  189. package/dist-cjs/rag/redis.d.ts +2 -1
  190. package/dist-cjs/rag/redis.js +55 -17
  191. package/dist-cjs/rag/vector-helpers.d.ts +5 -0
  192. package/dist-cjs/rag/vector-helpers.js +8 -0
  193. package/dist-cjs/rag/weaviate.d.ts +15 -1
  194. package/dist-cjs/rag/weaviate.js +74 -11
  195. package/dist-cjs/server/tenancy.d.ts +33 -2
  196. package/dist-cjs/server/tenancy.js +162 -0
  197. package/dist-cjs/sqlite/embedding-cache.d.ts +34 -0
  198. package/dist-cjs/sqlite/embedding-cache.js +99 -0
  199. package/dist-cjs/tracing/index.d.ts +2 -2
  200. package/dist-cjs/tracing/index.js +3 -1
  201. package/dist-cjs/tracing/query.d.ts +5 -1
  202. package/dist-cjs/tracing/query.js +16 -1
  203. package/dist-cjs/tracing/stores.d.ts +2 -2
  204. package/dist-cjs/tracing/stores.js +3 -1
  205. package/dist-cjs/types/config.d.ts +5 -0
  206. package/dist-cjs/types/embeddings.d.ts +87 -5
  207. package/dist-cjs/types/images.d.ts +2 -2
  208. package/dist-cjs/types/lifecycle.d.ts +92 -1
  209. package/dist-cjs/types/tracing.d.ts +20 -1
  210. package/dist-cjs/utils/redis-command.d.ts +15 -0
  211. package/dist-cjs/utils/redis-command.js +8 -0
  212. package/dist-cjs/utils/sha256.d.ts +9 -0
  213. package/dist-cjs/utils/sha256.js +7 -0
  214. package/dist-cjs/utils/vector-bytes.d.ts +11 -0
  215. package/dist-cjs/utils/vector-bytes.js +22 -0
  216. package/dist-cjs/version.d.ts +1 -1
  217. package/dist-cjs/version.js +1 -1
  218. package/dist-cjs/video/errors.d.ts +37 -0
  219. package/dist-cjs/video/errors.js +27 -0
  220. package/dist-cjs/video/google.d.ts +52 -0
  221. package/dist-cjs/video/google.js +252 -0
  222. package/dist-cjs/video/index.d.ts +426 -0
  223. package/dist-cjs/video/index.js +708 -0
  224. package/dist-cjs/video/mock.d.ts +62 -0
  225. package/dist-cjs/video/mock.js +101 -0
  226. package/dist-cjs/video/runway.d.ts +47 -0
  227. package/dist-cjs/video/runway.js +245 -0
  228. package/llms.txt +3 -1
  229. package/package.json +215 -4
@@ -0,0 +1,170 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { bundledRegistryData, checkRegistryData, diffRegistryData, } from '../models/data.js';
3
+ import { CliUsageError, flagBool, listFlag, printTable, stringFlag, writeJson } from './args.js';
4
+ export async function runModelsCommand(subcommand, { positionals, flags }, io = { fetch: globalThis.fetch, env: process.env }) {
5
+ if (subcommand === 'export') {
6
+ const text = `${JSON.stringify(bundledRegistryData(), null, 2)}\n`;
7
+ const out = stringFlag(flags, 'out');
8
+ if (out) {
9
+ await writeFile(out, text);
10
+ console.log(`Wrote the bundled registry data to ${out}.`);
11
+ }
12
+ else
13
+ process.stdout.write(text);
14
+ return;
15
+ }
16
+ if (subcommand === 'check') {
17
+ const file = positionals[0];
18
+ if (!file)
19
+ throw new CliUsageError('Usage: nexus models check <registry.json> [--json]');
20
+ const text = await readFile(file, 'utf8');
21
+ const problems = checkRegistryData(text);
22
+ const diff = problems.length
23
+ ? undefined
24
+ : diffRegistryData(bundledRegistryData(), JSON.parse(text));
25
+ if (flagBool(flags, 'json'))
26
+ writeJson({ valid: problems.length === 0, problems, diff });
27
+ else if (problems.length) {
28
+ console.error(`${file} is not valid registry data:`);
29
+ for (const problem of problems)
30
+ console.error(` - ${problem}`);
31
+ }
32
+ else
33
+ printDiff(file, diff);
34
+ if (problems.length)
35
+ process.exitCode = 1;
36
+ return;
37
+ }
38
+ if (subcommand === 'refresh') {
39
+ const file = stringFlag(flags, 'file');
40
+ const data = file ? JSON.parse(await readFile(file, 'utf8')) : bundledRegistryData();
41
+ const wanted = listFlag(flags, 'providers') ?? Object.keys(PROVIDER_LISTS);
42
+ const unknown = wanted.filter((provider) => !(provider in PROVIDER_LISTS));
43
+ if (unknown.length) {
44
+ throw new CliUsageError(`Unknown provider ${unknown.join(', ')}. Choose from ${Object.keys(PROVIDER_LISTS).join(', ')}.`);
45
+ }
46
+ const listings = {};
47
+ const skipped = [];
48
+ for (const provider of wanted) {
49
+ const source = PROVIDER_LISTS[provider];
50
+ const key = io.env[source.env];
51
+ if (!key) {
52
+ skipped.push(`${provider} (no ${source.env})`);
53
+ continue;
54
+ }
55
+ listings[provider] = await source.list(key, io.fetch);
56
+ }
57
+ const report = refreshReport(data, listings);
58
+ if (flagBool(flags, 'json'))
59
+ writeJson({ report, skipped });
60
+ else
61
+ printRefresh(report, skipped);
62
+ if (flagBool(flags, 'strict') && report.some((row) => row.missing.length || row.unlisted.length))
63
+ process.exitCode = 1;
64
+ return;
65
+ }
66
+ throw new CliUsageError(`Unknown models command "${subcommand}". Use export, check, or refresh, or no command to list.`);
67
+ }
68
+ export function refreshReport(data, listings) {
69
+ return Object.entries(listings).map(([provider, listed]) => {
70
+ const known = new Set([
71
+ ...Object.entries(data.models)
72
+ .filter(([, entry]) => entry.provider === provider)
73
+ .map(([name]) => name),
74
+ ...Object.entries(data.embeddingModels ?? {})
75
+ .filter(([, entry]) => entry.provider === provider)
76
+ .map(([name]) => name),
77
+ ]);
78
+ const offered = new Set(listed);
79
+ return {
80
+ provider,
81
+ missing: [...offered].filter((name) => !known.has(name)).sort(),
82
+ unlisted: [...known].filter((name) => !offered.has(name)).sort(),
83
+ };
84
+ });
85
+ }
86
+ export const PROVIDER_LISTS = {
87
+ openai: { env: 'OPENAI_API_KEY', list: openAiStyle('https://api.openai.com/v1/models') },
88
+ anthropic: {
89
+ env: 'ANTHROPIC_API_KEY',
90
+ async list(key, fetch) {
91
+ const ids = [];
92
+ let after;
93
+ for (;;) {
94
+ const page = (await getJson(fetch, `https://api.anthropic.com/v1/models?limit=1000${after ? `&after_id=${encodeURIComponent(after)}` : ''}`, { 'x-api-key': key, 'anthropic-version': '2023-06-01' }));
95
+ ids.push(...(page.data ?? []).map((model) => model.id));
96
+ if (!page.has_more || !page.last_id)
97
+ return ids;
98
+ after = page.last_id;
99
+ }
100
+ },
101
+ },
102
+ google: {
103
+ env: 'GOOGLE_API_KEY',
104
+ async list(key, fetch) {
105
+ const ids = [];
106
+ let token;
107
+ for (;;) {
108
+ const page = (await getJson(fetch, `https://generativelanguage.googleapis.com/v1beta/models?pageSize=1000${token ? `&pageToken=${encodeURIComponent(token)}` : ''}`, { 'x-goog-api-key': key }));
109
+ ids.push(...(page.models ?? []).map((model) => model.name.replace(/^models\//, '')));
110
+ if (!page.nextPageToken)
111
+ return ids;
112
+ token = page.nextPageToken;
113
+ }
114
+ },
115
+ },
116
+ mistral: { env: 'MISTRAL_API_KEY', list: openAiStyle('https://api.mistral.ai/v1/models') },
117
+ cohere: {
118
+ env: 'COHERE_API_KEY',
119
+ async list(key, fetch) {
120
+ const ids = [];
121
+ let token;
122
+ for (;;) {
123
+ const page = (await getJson(fetch, `https://api.cohere.com/v1/models?page_size=1000${token ? `&page_token=${encodeURIComponent(token)}` : ''}`, { authorization: `Bearer ${key}` }));
124
+ ids.push(...(page.models ?? []).map((model) => model.name));
125
+ if (!page.next_page_token)
126
+ return ids;
127
+ token = page.next_page_token;
128
+ }
129
+ },
130
+ },
131
+ groq: { env: 'GROQ_API_KEY', list: openAiStyle('https://api.groq.com/openai/v1/models') },
132
+ deepseek: { env: 'DEEPSEEK_API_KEY', list: openAiStyle('https://api.deepseek.com/models') },
133
+ };
134
+ function openAiStyle(url) {
135
+ return async (key, fetch) => {
136
+ const page = (await getJson(fetch, url, { authorization: `Bearer ${key}` }));
137
+ return (page.data ?? []).map((model) => model.id);
138
+ };
139
+ }
140
+ async function getJson(fetch, url, headers) {
141
+ const response = await fetch(url, { headers });
142
+ if (!response.ok)
143
+ throw new Error(`${new URL(url).host} answered ${response.status} listing its models`);
144
+ return response.json();
145
+ }
146
+ function printDiff(file, diff) {
147
+ console.log(`${file} is valid registry data. Against the bundled data:`);
148
+ console.log(` ${diff.added.length} added, ${diff.removed.length} removed, ${diff.changed.length} changed, ${diff.aliases.length} aliases moved`);
149
+ for (const name of diff.added)
150
+ console.log(` + ${name}`);
151
+ for (const name of diff.removed)
152
+ console.log(` - ${name}`);
153
+ for (const { model, changes } of diff.changed) {
154
+ console.log(` ~ ${model}: ${changes.map(({ field, from, to }) => `${field} ${JSON.stringify(from)} -> ${JSON.stringify(to)}`).join(', ')}`);
155
+ }
156
+ for (const { alias, from, to } of diff.aliases)
157
+ console.log(` @ ${alias}: ${from ?? '(none)'} -> ${to ?? '(none)'}`);
158
+ }
159
+ function printRefresh(report, skipped) {
160
+ if (report.length) {
161
+ printTable(report.map((row) => ({
162
+ provider: row.provider,
163
+ 'listed, not in the data': row.missing.join(', ') || '-',
164
+ 'in the data, not listed': row.unlisted.join(', ') || '-',
165
+ })), ['provider', 'listed, not in the data', 'in the data, not listed']);
166
+ }
167
+ if (skipped.length)
168
+ console.log(`Skipped: ${skipped.join(', ')}.`);
169
+ console.log('Prices are not in any provider list: check new and changed entries against each pricing page.');
170
+ }
package/dist/cli.js CHANGED
@@ -24,9 +24,16 @@ async function main() {
24
24
  case 'scan':
25
25
  await runScan(parsed);
26
26
  return;
27
- case 'models':
27
+ case 'models': {
28
+ const [subcommand, ...args] = parsed.positionals;
29
+ if (subcommand) {
30
+ const { runModelsCommand } = await import('./cli/models.js');
31
+ await runModelsCommand(subcommand, { ...parsed, positionals: args });
32
+ return;
33
+ }
28
34
  runModels(parsed);
29
35
  return;
36
+ }
30
37
  case 'eval': {
31
38
  const [subcommand, ...args] = parsed.positionals;
32
39
  if (subcommand === 'run' || subcommand === 'compare' || subcommand === 'gate') {
@@ -401,6 +408,9 @@ function printHelp() {
401
408
  Usage:
402
409
  nexus scan [paths...] [--json] [--max-bytes 1000000] [--no-fail] [--reveal-values]
403
410
  nexus models [--provider openai] [--json]
411
+ nexus models export [--out registry.json]
412
+ nexus models check <registry.json> [--json]
413
+ nexus models refresh [--file registry.json] [--providers openai,anthropic] [--strict] [--json]
404
414
  nexus eval <eval.json|eval.mjs> [--json]
405
415
  nexus eval run <eval.mjs> [--out experiment.json] [--experiments dir] [--baseline baseline.json] [--fail-on-regression]
406
416
  [--name n] [--concurrency 4] [--repetitions 1] [--timeout-ms n] [--json]
@@ -6,6 +6,10 @@
6
6
  * its checkpoints to the deep agent's checkpointer. A crash leaves the helper's finished steps behind
7
7
  * it: collecting the task in a new process carries the helper on from its last checkpoint, and a call
8
8
  * the agent repeats after the crash finds the same thread instead of starting the helper again.
9
+ *
10
+ * The helpers an agent creates are recorded on a thread of their own beside its own, before the call
11
+ * that creates one returns, so an agent resumed in another process counts them against its `max` and
12
+ * delegates to them again by name.
9
13
  */
10
14
  import { type CreateAgentOptions } from '../agent/create-agent.js';
11
15
  import type { PermissionPolicyLike } from '../agent/permissions.js';
@@ -1,11 +1,55 @@
1
1
  import { agentInput, createAgent, } from '../agent/create-agent.js';
2
2
  const MARK = 'deepAgentHelper';
3
+ const CREATED = 'deepAgentCreatedHelpers';
3
4
  export function helperTools(setup) {
4
5
  const declared = new Map(setup.declared.map((helper) => [helper.name, helper]));
5
6
  const created = new Map();
6
7
  const graphs = new Map();
7
8
  const live = new Map();
8
- const spec = (name, instructions, thread) => {
9
+ const recorded = new Map();
10
+ const createdThread = (thread) => `${thread || 'thread'}/helpers-created`;
11
+ const loadCreated = (thread) => {
12
+ const checkpointer = setup.checkpointer;
13
+ if (!checkpointer || !setup.create)
14
+ return Promise.resolve();
15
+ let loading = recorded.get(thread);
16
+ if (!loading) {
17
+ loading = (async () => {
18
+ const stored = await checkpointer.get(createdThread(thread));
19
+ const records = (stored?.metadata?.[CREATED] ?? []);
20
+ const mine = created.get(thread) ?? new Map();
21
+ for (const record of records) {
22
+ if (!mine.has(record.name))
23
+ mine.set(record.name, { ...record, created: true });
24
+ }
25
+ created.set(thread, mine);
26
+ })();
27
+ loading.catch(() => recorded.delete(thread));
28
+ recorded.set(thread, loading);
29
+ }
30
+ return loading;
31
+ };
32
+ const recordCreated = async (thread, mine) => {
33
+ const checkpointer = setup.checkpointer;
34
+ if (!checkpointer)
35
+ return;
36
+ const threadId = createdThread(thread);
37
+ const records = [...mine.values()].map(({ created: _created, ...record }) => record);
38
+ await checkpointer.put({
39
+ version: 2,
40
+ id: `${threadId}:${records.length}`,
41
+ threadId,
42
+ step: records.length,
43
+ state: {},
44
+ next: [],
45
+ tasks: [],
46
+ status: 'completed',
47
+ interrupts: [],
48
+ createdAt: new Date().toISOString(),
49
+ metadata: { [CREATED]: records },
50
+ });
51
+ };
52
+ const spec = async (name, instructions, thread) => {
9
53
  const helper = declared.get(name);
10
54
  if (helper) {
11
55
  return {
@@ -17,6 +61,7 @@ export function helperTools(setup) {
17
61
  created: false,
18
62
  };
19
63
  }
64
+ await loadCreated(thread);
20
65
  const mine = created.get(thread) ?? new Map();
21
66
  const existing = mine.get(name);
22
67
  if (existing)
@@ -42,6 +87,13 @@ export function helperTools(setup) {
42
87
  };
43
88
  mine.set(name, made);
44
89
  created.set(thread, mine);
90
+ try {
91
+ await recordCreated(thread, mine);
92
+ }
93
+ catch (error) {
94
+ mine.delete(name);
95
+ throw error;
96
+ }
45
97
  return made;
46
98
  };
47
99
  const graph = (helper, durable) => {
@@ -165,7 +217,7 @@ export function helperTools(setup) {
165
217
  },
166
218
  capabilities: [],
167
219
  execute: async (args, context = {}) => {
168
- const helper = spec(String(args.agent ?? ''), typeof args.instructions === 'string' ? args.instructions : undefined, context.threadId ?? '');
220
+ const helper = await spec(String(args.agent ?? ''), typeof args.instructions === 'string' ? args.instructions : undefined, context.threadId ?? '');
169
221
  const task = String(args.task ?? '');
170
222
  const call = context.toolCallId ?? Math.random().toString(36).slice(2, 10);
171
223
  if (args.background !== true) {
@@ -211,8 +263,8 @@ export function helperTools(setup) {
211
263
  const mark = stored.metadata?.[MARK];
212
264
  const name = mark?.name ?? task.slice(0, task.lastIndexOf('@'));
213
265
  const helper = mark?.created
214
- ? spec(name, mark.instructions, context.threadId ?? '')
215
- : spec(name, undefined, context.threadId ?? '');
266
+ ? await spec(name, mark.instructions, context.threadId ?? '')
267
+ : await spec(name, undefined, context.threadId ?? '');
216
268
  await start(helper, undefined, threadId, context);
217
269
  }
218
270
  const entry = live.get(threadId);
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Checks a deployment before it serves traffic: the runtime, credentials, the database and its
3
+ * migrations, Redis, the operation queue, the model registry, provider health, and settings that
4
+ * only work in one process. `nexus doctor` runs the same checks from a shell.
5
+ *
6
+ * Every check is cheap and read-only, and anything heavy — the model registry, the migration lists —
7
+ * is loaded only when its check runs, so an application can call `diagnose()` at boot or from a
8
+ * health endpoint.
9
+ */
10
+ import type { NexusAIConfig } from '../types/config.js';
1
11
  import type { SchemaMigration } from '../utils/schema-migrations.js';
2
12
  /** How one check came out: `fail` breaks the deployment, `warn` needs a look, `skip` had nothing to check. */
3
13
  export type DoctorStatus = 'ok' | 'warn' | 'fail' | 'skip';
@@ -78,8 +88,13 @@ export interface DoctorOptions {
78
88
  stale?: boolean;
79
89
  }>;
80
90
  };
81
- /** How old the bundled model registry may be, in days. Defaults to the registry's own window. */
91
+ /** How old the model registry may be, in days. Defaults to the registry's own window. */
82
92
  registryMaxAgeDays?: number;
93
+ /**
94
+ * The application's model registry configuration, such as `withRegistryData()` produced, so the
95
+ * registry's age is measured from the data it supplies rather than the bundled data.
96
+ */
97
+ models?: NexusAIConfig['models'];
83
98
  /** How the application is deployed. */
84
99
  deployment?: DoctorDeployment;
85
100
  /** Replaces the clock, for tests. */
@@ -47,7 +47,7 @@ export async function diagnose(options = {}) {
47
47
  await run('node', 'Node.js version', () => nodeCheck(options.nodeVersion ?? process.versions.node));
48
48
  await run('peers', 'Optional packages', () => peersCheck(options.resolvePackage ?? defaultResolver()));
49
49
  await run('credentials', 'Provider credentials', () => credentialsCheck(options.env ?? process.env));
50
- await run('registry', 'Model registry age', () => registryCheck(now(), options.registryMaxAgeDays));
50
+ await run('registry', 'Model registry age', () => registryCheck(now(), options.registryMaxAgeDays, options.models));
51
51
  await run('database', 'Database connectivity', () => databaseCheck(options.database));
52
52
  await run('migrations', 'Schema migrations', () => migrationsCheck(options.database));
53
53
  await run('redis', 'Redis', () => redisCheck(options.redis));
@@ -97,14 +97,19 @@ function credentialsCheck(env) {
97
97
  hint: 'Set the API key of each hosted provider you use, such as OPENAI_API_KEY. Local providers need none.',
98
98
  };
99
99
  }
100
- async function registryCheck(now, maxAgeDays) {
100
+ async function registryCheck(now, maxAgeDays, models) {
101
101
  const { checkRegistryFreshness } = await import('../models/registry.js');
102
- const freshness = checkRegistryFreshness({ now, ...(maxAgeDays === undefined ? {} : { maxAgeDays }) });
102
+ const freshness = checkRegistryFreshness({
103
+ now,
104
+ ...(maxAgeDays === undefined ? {} : { maxAgeDays }),
105
+ ...(models ? { config: { models } } : {}),
106
+ });
107
+ const which = models?.verifiedAt ? 'supplied' : 'bundled';
103
108
  return freshness.stale
104
109
  ? {
105
110
  status: 'warn',
106
- detail: `The bundled registry was verified ${freshness.ageDays} days ago (${freshness.verifiedAt}), past its ${freshness.maxAgeDays}-day window.`,
107
- hint: 'Upgrade nexus-ai-pro, or override stale entries with models.registry.',
111
+ detail: `The ${which} registry was verified ${freshness.ageDays} days ago (${freshness.verifiedAt}), past its ${freshness.maxAgeDays}-day window.`,
112
+ hint: 'Supply newer registry data with withRegistryData(), checked by nexus models check, or upgrade nexus-ai-pro.',
108
113
  }
109
114
  : { status: 'ok', detail: `Verified ${freshness.ageDays} days ago (${freshness.verifiedAt}).` };
110
115
  }
@@ -52,7 +52,7 @@ export declare class GoogleEmbeddingProvider extends HttpEmbeddingProvider {
52
52
  readonly info: EmbeddingProviderInfo;
53
53
  constructor(options?: EmbeddingAdapterOptions);
54
54
  protected authHeaders(): Record<string, string>;
55
- /** Embeds a batch of texts. */
55
+ /** Embeds a batch of inputs: texts, or parts with images, audio, and documents. */
56
56
  embed(request: EmbeddingProviderRequest, context: EmbeddingProviderCallContext): Promise<EmbeddingProviderResult>;
57
57
  }
58
58
  /** Cohere v2 `/embed`. */
@@ -60,7 +60,7 @@ export declare class CohereEmbeddingProvider extends HttpEmbeddingProvider {
60
60
  /** Provider name, capabilities, and models. */
61
61
  readonly info: EmbeddingProviderInfo;
62
62
  constructor(options?: EmbeddingAdapterOptions);
63
- /** Embeds a batch of texts. */
63
+ /** Embeds a batch of inputs: texts, or parts with images. */
64
64
  embed(request: EmbeddingProviderRequest, context: EmbeddingProviderCallContext): Promise<EmbeddingProviderResult>;
65
65
  }
66
66
  /** Mistral `/v1/embeddings`, which follows the OpenAI request and response shape. */
@@ -1,6 +1,7 @@
1
1
  import { decodeBase64 } from '../utils/base64.js';
2
2
  import { createProviderHttpError, toNexusProviderError } from '../providers/errors.js';
3
- import { EmbeddingProviderResponseError } from './errors.js';
3
+ import { EmbeddingProviderResponseError, EmbeddingValidationError } from './errors.js';
4
+ import { GOOGLE_TASK_TYPES, googleParts, usesTaskPrefix } from './google-wire.js';
4
5
  class HttpEmbeddingProvider {
5
6
  options;
6
7
  constructor(options = {}) {
@@ -82,12 +83,6 @@ export class OpenAIEmbeddingProvider extends HttpEmbeddingProvider {
82
83
  };
83
84
  }
84
85
  }
85
- const GOOGLE_TASK_TYPES = {
86
- document: 'RETRIEVAL_DOCUMENT',
87
- query: 'RETRIEVAL_QUERY',
88
- classification: 'CLASSIFICATION',
89
- clustering: 'CLUSTERING',
90
- };
91
86
  export class GoogleEmbeddingProvider extends HttpEmbeddingProvider {
92
87
  info;
93
88
  constructor(options = {}) {
@@ -96,9 +91,10 @@ export class GoogleEmbeddingProvider extends HttpEmbeddingProvider {
96
91
  name: 'google',
97
92
  defaultModel: options.model || 'gemini-embedding-001',
98
93
  capabilities: {
99
- models: ['gemini-embedding-001', 'text-embedding-004'],
94
+ models: ['gemini-embedding-001', 'gemini-embedding-2', 'text-embedding-004'],
100
95
  maxBatchSize: 100,
101
- maxInputTokens: 2048,
96
+ maxInputTokens: 8192,
97
+ inputModalities: ['text', 'image', 'audio', 'document'],
102
98
  dimensions: true,
103
99
  inputTypes: ['document', 'query', 'classification', 'clustering'],
104
100
  encodingFormats: ['float'],
@@ -112,11 +108,13 @@ export class GoogleEmbeddingProvider extends HttpEmbeddingProvider {
112
108
  async embed(request, context) {
113
109
  const model = request.model;
114
110
  const qualified = model.startsWith('models/') ? model : `models/${model}`;
111
+ const prefixed = usesTaskPrefix(model);
112
+ const contents = request.contents ?? request.input.map((text) => [{ type: 'text', text }]);
115
113
  const payload = (await this.post(`${this.baseUrl('https://generativelanguage.googleapis.com/v1beta')}/${qualified}:batchEmbedContents`, {
116
- requests: request.input.map((text) => ({
114
+ requests: contents.map((parts) => ({
117
115
  model: qualified,
118
- content: { parts: [{ text }] },
119
- taskType: request.inputType ? GOOGLE_TASK_TYPES[request.inputType] : undefined,
116
+ content: { parts: googleParts(parts, prefixed ? request.inputType : undefined) },
117
+ taskType: request.inputType && !prefixed ? GOOGLE_TASK_TYPES[request.inputType] : undefined,
120
118
  outputDimensionality: request.dimensions,
121
119
  ...request.providerOptions,
122
120
  })),
@@ -147,6 +145,7 @@ export class CohereEmbeddingProvider extends HttpEmbeddingProvider {
147
145
  capabilities: {
148
146
  models: ['embed-v4.0', 'embed-english-v3.0', 'embed-multilingual-v3.0'],
149
147
  maxBatchSize: 96,
148
+ inputModalities: ['text', 'image'],
150
149
  dimensions: [256, 512, 1024, 1536],
151
150
  inputTypes: ['document', 'query', 'classification', 'clustering'],
152
151
  encodingFormats: ['float'],
@@ -158,7 +157,9 @@ export class CohereEmbeddingProvider extends HttpEmbeddingProvider {
158
157
  async embed(request, context) {
159
158
  const payload = (await this.post(`${this.baseUrl('https://api.cohere.com/v2')}/embed`, {
160
159
  model: request.model,
161
- texts: request.input,
160
+ ...(request.contents
161
+ ? { inputs: request.contents.map((parts) => ({ content: parts.map(cohereContent) })) }
162
+ : { texts: request.input }),
162
163
  input_type: COHERE_INPUT_TYPES[request.inputType ?? 'document'],
163
164
  embedding_types: ['float'],
164
165
  output_dimension: request.dimensions,
@@ -177,6 +178,16 @@ export class CohereEmbeddingProvider extends HttpEmbeddingProvider {
177
178
  };
178
179
  }
179
180
  }
181
+ function cohereContent(part) {
182
+ if (part.type === 'text')
183
+ return { type: 'text', text: part.text };
184
+ if (part.type === 'image' && part.data !== undefined) {
185
+ return { type: 'image_url', image_url: { url: `data:${part.mimeType};base64,${part.data}` } };
186
+ }
187
+ throw new EmbeddingValidationError(part.type === 'image'
188
+ ? 'Cohere embeds an image from its bytes, sent as a data URI, not from a URL; pass the image as bytes'
189
+ : `Cohere embeds text and images, not ${part.type}`);
190
+ }
180
191
  export class MistralEmbeddingProvider extends OpenAIEmbeddingProvider {
181
192
  constructor(options = {}) {
182
193
  super({
@@ -0,0 +1,40 @@
1
+ import type { BatchJobRef, BatchJobState, BatchProviderCallContext } from '../types/batch.js';
2
+ import type { EmbeddingBatchOutputItem, EmbeddingBatchProvider, EmbeddingBatchProviderInfo, EmbeddingBatchProviderRequest } from './batch.js';
3
+ /** Options for Gemini's embedding batch tier. */
4
+ export interface GoogleEmbeddingBatchProviderOptions {
5
+ /** Gemini API key, sent as `x-goog-api-key`. */
6
+ apiKey: string;
7
+ /** API base URL. Defaults to `https://generativelanguage.googleapis.com/v1beta`. */
8
+ baseUrl?: string;
9
+ /** Headers added to every request. */
10
+ headers?: Record<string, string>;
11
+ /** Replaces the global `fetch`. */
12
+ fetch?: typeof fetch;
13
+ }
14
+ /**
15
+ * Gemini's Batch API for embeddings (`asyncBatchEmbedContent`): half price, inline requests under
16
+ * 20 MB, and results inline, in the order submitted. It takes text, images, audio, and PDFs on a model
17
+ * that does, such as gemini-embedding-2. The wire shape follows Google's API reference and its own
18
+ * clients; it graduates with a recorded live run.
19
+ */
20
+ export declare class GoogleEmbeddingBatchProvider implements EmbeddingBatchProvider {
21
+ private readonly options;
22
+ /** Always `google`, with its batch limits. */
23
+ readonly info: EmbeddingBatchProviderInfo;
24
+ constructor(options: GoogleEmbeddingBatchProviderOptions);
25
+ /**
26
+ * Sends every input inline as one batch. Results come back in the same order, so the ref keeps the
27
+ * ids in that order.
28
+ */
29
+ submit(request: EmbeddingBatchProviderRequest, context: BatchProviderCallContext): Promise<BatchJobRef>;
30
+ /** Reads a batch's state and counts. */
31
+ poll(ref: BatchJobRef, context: BatchProviderCallContext): Promise<BatchJobState>;
32
+ /** Every input's vector or error, matched to its id by position. */
33
+ results(ref: BatchJobRef, context: BatchProviderCallContext): Promise<EmbeddingBatchOutputItem[]>;
34
+ /** Cancels a batch. */
35
+ cancel(ref: BatchJobRef, context: BatchProviderCallContext): Promise<BatchJobState>;
36
+ /** The batch resource, whether Google answers with it or with an operation that holds it. */
37
+ private read;
38
+ private state;
39
+ private request;
40
+ }
@@ -0,0 +1,128 @@
1
+ import { BatchCapabilityError, BatchProviderResponseError } from '../batch/errors.js';
2
+ import { GOOGLE_TASK_TYPES, googleParts, usesTaskPrefix } from './google-wire.js';
3
+ const INLINE_BYTES = 20 * 1024 * 1024;
4
+ const STATES = {
5
+ PENDING: 'validating',
6
+ RUNNING: 'in_progress',
7
+ SUCCEEDED: 'completed',
8
+ FAILED: 'failed',
9
+ CANCELLED: 'cancelled',
10
+ EXPIRED: 'expired',
11
+ };
12
+ export class GoogleEmbeddingBatchProvider {
13
+ options;
14
+ info = {
15
+ name: 'google',
16
+ capabilities: {
17
+ maxBytes: INLINE_BYTES,
18
+ supportsCancel: true,
19
+ discount: 0.5,
20
+ inputModalities: ['text', 'image', 'audio', 'document'],
21
+ },
22
+ };
23
+ constructor(options) {
24
+ this.options = options;
25
+ }
26
+ async submit(request, context) {
27
+ const qualified = request.model.startsWith('models/') ? request.model : `models/${request.model}`;
28
+ const prefixed = usesTaskPrefix(request.model);
29
+ const body = JSON.stringify({
30
+ batch: {
31
+ displayName: request.metadata?.key ?? 'nexus-embeddings',
32
+ inputConfig: {
33
+ requests: {
34
+ requests: request.items.map((item) => ({
35
+ request: {
36
+ model: qualified,
37
+ content: {
38
+ parts: googleParts(item.contents ?? [{ type: 'text', text: item.input }], prefixed ? request.inputType : undefined),
39
+ },
40
+ ...(request.inputType && !prefixed ? { taskType: GOOGLE_TASK_TYPES[request.inputType] } : {}),
41
+ ...(request.dimensions !== undefined ? { outputDimensionality: request.dimensions } : {}),
42
+ },
43
+ })),
44
+ },
45
+ },
46
+ },
47
+ });
48
+ if (body.length > INLINE_BYTES) {
49
+ throw new BatchCapabilityError('google', 'maxBytes', `An inline Gemini batch holds at most 20 MB; this one is ${Math.ceil(body.length / 1024 / 1024)} MB, so split it`);
50
+ }
51
+ const created = (await this.request(`/${qualified}:asyncBatchEmbedContent`, { method: 'POST', body }, context));
52
+ const name = created.name ?? created.metadata?.name;
53
+ if (!name)
54
+ throw new BatchProviderResponseError('google', 'the batch response returned no name');
55
+ return { id: name, provider: 'google', metadata: { customIds: request.items.map((item) => item.customId) } };
56
+ }
57
+ async poll(ref, context) {
58
+ return this.state(ref, await this.read(ref, context));
59
+ }
60
+ async results(ref, context) {
61
+ const batch = await this.read(ref, context);
62
+ const output = batch.output;
63
+ if (output?.responsesFile) {
64
+ throw new BatchProviderResponseError('google', 'the batch wrote its results to a file; this adapter reads inline results');
65
+ }
66
+ const ids = (ref.metadata?.customIds ?? []);
67
+ const responses = output?.inlinedEmbedContentResponses?.inlinedResponses ?? [];
68
+ return responses.map((entry, index) => {
69
+ const customId = ids[index] ?? String(index);
70
+ const values = entry.response?.embedding?.values;
71
+ if (Array.isArray(values))
72
+ return { customId, vector: values };
73
+ return {
74
+ customId,
75
+ error: {
76
+ message: entry.error?.message ?? 'the response carried no embedding',
77
+ ...(entry.error?.code !== undefined ? { code: String(entry.error.code) } : {}),
78
+ },
79
+ };
80
+ });
81
+ }
82
+ async cancel(ref, context) {
83
+ await this.request(`/${ref.id}:cancel`, { method: 'POST' }, context);
84
+ return this.poll(ref, context);
85
+ }
86
+ async read(ref, context) {
87
+ const answer = (await this.request(`/${ref.id}`, {}, context));
88
+ return { ...answer, ...answer.metadata };
89
+ }
90
+ state(ref, batch) {
91
+ const status = STATES[(batch.state ?? '').replace(/^(BATCH|JOB)_STATE_/, '')] ?? 'in_progress';
92
+ const stats = batch.batchStats;
93
+ const count = (key) => Number(stats?.[key] ?? 0);
94
+ return {
95
+ ref,
96
+ status,
97
+ ...(stats
98
+ ? {
99
+ counts: {
100
+ total: count('requestCount'),
101
+ completed: count('successfulRequestCount'),
102
+ failed: count('failedRequestCount'),
103
+ },
104
+ }
105
+ : {}),
106
+ ...(batch.createTime ? { createdAt: batch.createTime } : {}),
107
+ ...(batch.endTime ? { completedAt: batch.endTime } : {}),
108
+ ...(batch.error
109
+ ? { error: { message: batch.error.message ?? 'batch failed', code: String(batch.error.code ?? '') } }
110
+ : {}),
111
+ raw: batch,
112
+ };
113
+ }
114
+ async request(path, init, context) {
115
+ const fetchImpl = this.options.fetch ?? fetch;
116
+ const base = (this.options.baseUrl ?? 'https://generativelanguage.googleapis.com/v1beta').replace(/\/+$/, '');
117
+ const response = await fetchImpl(`${base}${path}`, {
118
+ ...init,
119
+ headers: { 'content-type': 'application/json', 'x-goog-api-key': this.options.apiKey, ...this.options.headers },
120
+ signal: context.signal,
121
+ });
122
+ const text = await response.text();
123
+ if (!response.ok) {
124
+ throw new BatchProviderResponseError('google', `${path} failed: ${response.status} ${text.slice(0, 500)}`);
125
+ }
126
+ return text ? JSON.parse(text) : {};
127
+ }
128
+ }