nexus-ai-pro 2.20.2 → 2.22.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 (233) hide show
  1. package/API_STABILITY.md +93 -0
  2. package/CHANGELOG.md +208 -1
  3. package/README.md +11 -6
  4. package/SECURITY.md +16 -2
  5. package/dist/adapter-kit/gather.d.ts +76 -0
  6. package/dist/adapter-kit/gather.js +147 -0
  7. package/dist/adapter-kit/index.d.ts +1 -0
  8. package/dist/adapter-kit/index.js +1 -0
  9. package/dist/adapter-kit/record.d.ts +3 -1
  10. package/dist/adapter-kit/record.js +20 -5
  11. package/dist/agent/openapi.d.ts +94 -8
  12. package/dist/agent/openapi.js +528 -32
  13. package/dist/cli/adapters.d.ts +4 -3
  14. package/dist/cli/adapters.js +71 -2
  15. package/dist/cli/doctor.js +3 -0
  16. package/dist/cli.js +3 -1
  17. package/dist/core/config-builder.d.ts +1 -0
  18. package/dist/core/config-builder.js +1 -0
  19. package/dist/core/defaults.d.ts +34 -0
  20. package/dist/core/defaults.js +39 -0
  21. package/dist/core/secure-stream.js +4 -1
  22. package/dist/decisions/completion.d.ts +10 -7
  23. package/dist/decisions/completion.js +82 -8
  24. package/dist/decisions/core.d.ts +88 -43
  25. package/dist/decisions/core.js +115 -28
  26. package/dist/decisions/errors.d.ts +25 -0
  27. package/dist/decisions/errors.js +12 -0
  28. package/dist/decisions/index.d.ts +6 -5
  29. package/dist/decisions/index.js +2 -1
  30. package/dist/decisions/integrations.d.ts +14 -5
  31. package/dist/decisions/integrations.js +16 -1
  32. package/dist/decisions/mock.d.ts +5 -3
  33. package/dist/decisions/mock.js +4 -1
  34. package/dist/decisions/pictures.d.ts +43 -0
  35. package/dist/decisions/pictures.js +103 -0
  36. package/dist/decisions/typesafe.d.ts +33 -5
  37. package/dist/decisions/typesafe.js +121 -8
  38. package/dist/doctor/index.d.ts +6 -9
  39. package/dist/doctor/index.js +41 -0
  40. package/dist/embeddings/adapters.d.ts +1 -1
  41. package/dist/embeddings/adapters.js +3 -3
  42. package/dist/embeddings/batch-google.d.ts +27 -8
  43. package/dist/embeddings/batch-google.js +112 -36
  44. package/dist/embeddings/batch-mock.js +1 -1
  45. package/dist/embeddings/batch.js +10 -4
  46. package/dist/embeddings/engine.js +14 -13
  47. package/dist/embeddings/index.d.ts +1 -1
  48. package/dist/embeddings/inputs.d.ts +26 -3
  49. package/dist/embeddings/inputs.js +84 -5
  50. package/dist/embeddings/mock.js +1 -1
  51. package/dist/embeddings/models.js +39 -4
  52. package/dist/loaders/csv.d.ts +23 -1
  53. package/dist/loaders/csv.js +125 -72
  54. package/dist/loaders/index.d.ts +7 -2
  55. package/dist/loaders/input.d.ts +6 -0
  56. package/dist/loaders/input.js +42 -1
  57. package/dist/loaders/json.d.ts +14 -3
  58. package/dist/loaders/json.js +171 -20
  59. package/dist/loaders/web.d.ts +55 -0
  60. package/dist/loaders/web.js +20 -1
  61. package/dist/mcp/client.d.ts +54 -5
  62. package/dist/mcp/client.js +171 -20
  63. package/dist/mcp/http.d.ts +45 -0
  64. package/dist/mcp/http.js +233 -0
  65. package/dist/mcp/index.d.ts +4 -2
  66. package/dist/mcp/index.js +3 -1
  67. package/dist/mcp/protocol.d.ts +107 -0
  68. package/dist/mcp/sampling.d.ts +41 -0
  69. package/dist/mcp/sampling.js +52 -0
  70. package/dist/mcp/server.d.ts +96 -14
  71. package/dist/mcp/server.js +274 -59
  72. package/dist/models/data.js +20 -1
  73. package/dist/operations/adapters.d.ts +1 -0
  74. package/dist/operations/adapters.js +3 -1
  75. package/dist/protocols/a2a-grpc.js +10 -3
  76. package/dist/protocols/a2a-signing.d.ts +65 -0
  77. package/dist/protocols/a2a-signing.js +133 -0
  78. package/dist/protocols/a2a.d.ts +14 -0
  79. package/dist/protocols/a2a.js +60 -5
  80. package/dist/providers/cohere.js +16 -2
  81. package/dist/providers/google.js +7 -1
  82. package/dist/providers/ollama.d.ts +1 -1
  83. package/dist/providers/ollama.js +35 -27
  84. package/dist/providers/openai.js +19 -2
  85. package/dist/rag/pinecone.d.ts +63 -2
  86. package/dist/rag/pinecone.js +80 -0
  87. package/dist/realtime/conversation.js +18 -0
  88. package/dist/realtime/gemini-live.transport.d.ts +28 -3
  89. package/dist/realtime/gemini-live.transport.js +170 -32
  90. package/dist/realtime/session.d.ts +2 -0
  91. package/dist/realtime/session.js +19 -4
  92. package/dist/realtime/types.d.ts +35 -3
  93. package/dist/security/output-guard.js +4 -1
  94. package/dist/server/auth.d.ts +263 -2
  95. package/dist/server/auth.js +432 -5
  96. package/dist/server/deployments.d.ts +3 -2
  97. package/dist/server/deployments.js +6 -1
  98. package/dist/telephony/index.d.ts +1 -1
  99. package/dist/telephony/manager.d.ts +8 -1
  100. package/dist/telephony/manager.js +13 -0
  101. package/dist/telephony/providers/telnyx.d.ts +18 -5
  102. package/dist/telephony/providers/telnyx.js +44 -7
  103. package/dist/telephony/providers/twilio.d.ts +3 -1
  104. package/dist/telephony/providers/twilio.js +16 -0
  105. package/dist/tracing/stores.d.ts +2 -0
  106. package/dist/tracing/stores.js +1 -0
  107. package/dist/tracing/tracer.d.ts +5 -2
  108. package/dist/tracing/tracer.js +6 -3
  109. package/dist/types/embeddings.d.ts +47 -5
  110. package/dist/types/messages.d.ts +7 -0
  111. package/dist/types/principal.d.ts +1 -1
  112. package/dist/types/response.d.ts +5 -0
  113. package/dist/types/telephony.d.ts +38 -0
  114. package/dist/types/tracing.d.ts +6 -0
  115. package/dist/version.d.ts +1 -1
  116. package/dist/version.js +1 -1
  117. package/dist/voice/providers/google.d.ts +44 -0
  118. package/dist/voice/providers/google.js +215 -0
  119. package/dist-cjs/adapter-kit/gather.d.ts +76 -0
  120. package/dist-cjs/adapter-kit/gather.js +152 -0
  121. package/dist-cjs/adapter-kit/index.d.ts +1 -0
  122. package/dist-cjs/adapter-kit/index.js +4 -1
  123. package/dist-cjs/adapter-kit/record.d.ts +3 -1
  124. package/dist-cjs/adapter-kit/record.js +20 -5
  125. package/dist-cjs/agent/openapi.d.ts +94 -8
  126. package/dist-cjs/agent/openapi.js +530 -32
  127. package/dist-cjs/cli/adapters.d.ts +4 -3
  128. package/dist-cjs/cli/adapters.js +70 -1
  129. package/dist-cjs/cli/doctor.js +3 -0
  130. package/dist-cjs/core/config-builder.d.ts +1 -0
  131. package/dist-cjs/core/config-builder.js +5 -1
  132. package/dist-cjs/core/defaults.d.ts +34 -0
  133. package/dist-cjs/core/defaults.js +44 -0
  134. package/dist-cjs/core/secure-stream.js +4 -1
  135. package/dist-cjs/decisions/completion.d.ts +10 -7
  136. package/dist-cjs/decisions/completion.js +81 -7
  137. package/dist-cjs/decisions/core.d.ts +88 -43
  138. package/dist-cjs/decisions/core.js +134 -46
  139. package/dist-cjs/decisions/errors.d.ts +25 -0
  140. package/dist-cjs/decisions/errors.js +16 -0
  141. package/dist-cjs/decisions/index.d.ts +6 -5
  142. package/dist-cjs/decisions/index.js +5 -1
  143. package/dist-cjs/decisions/integrations.d.ts +14 -5
  144. package/dist-cjs/decisions/integrations.js +16 -1
  145. package/dist-cjs/decisions/mock.d.ts +5 -3
  146. package/dist-cjs/decisions/mock.js +4 -1
  147. package/dist-cjs/decisions/pictures.d.ts +43 -0
  148. package/dist-cjs/decisions/pictures.js +109 -0
  149. package/dist-cjs/decisions/typesafe.d.ts +33 -5
  150. package/dist-cjs/decisions/typesafe.js +126 -13
  151. package/dist-cjs/doctor/index.d.ts +6 -9
  152. package/dist-cjs/doctor/index.js +41 -0
  153. package/dist-cjs/embeddings/adapters.d.ts +1 -1
  154. package/dist-cjs/embeddings/adapters.js +3 -3
  155. package/dist-cjs/embeddings/batch-google.d.ts +27 -8
  156. package/dist-cjs/embeddings/batch-google.js +112 -36
  157. package/dist-cjs/embeddings/batch-mock.js +1 -1
  158. package/dist-cjs/embeddings/batch.js +9 -3
  159. package/dist-cjs/embeddings/engine.js +13 -12
  160. package/dist-cjs/embeddings/index.d.ts +1 -1
  161. package/dist-cjs/embeddings/inputs.d.ts +26 -3
  162. package/dist-cjs/embeddings/inputs.js +89 -5
  163. package/dist-cjs/embeddings/mock.js +1 -1
  164. package/dist-cjs/embeddings/models.js +39 -4
  165. package/dist-cjs/loaders/csv.d.ts +23 -1
  166. package/dist-cjs/loaders/csv.js +126 -71
  167. package/dist-cjs/loaders/index.d.ts +7 -2
  168. package/dist-cjs/loaders/input.d.ts +6 -0
  169. package/dist-cjs/loaders/input.js +43 -1
  170. package/dist-cjs/loaders/json.d.ts +14 -3
  171. package/dist-cjs/loaders/json.js +170 -19
  172. package/dist-cjs/loaders/web.d.ts +55 -0
  173. package/dist-cjs/loaders/web.js +21 -1
  174. package/dist-cjs/mcp/client.d.ts +54 -5
  175. package/dist-cjs/mcp/client.js +171 -20
  176. package/dist-cjs/mcp/http.d.ts +45 -0
  177. package/dist-cjs/mcp/http.js +236 -0
  178. package/dist-cjs/mcp/index.d.ts +4 -2
  179. package/dist-cjs/mcp/index.js +5 -1
  180. package/dist-cjs/mcp/protocol.d.ts +107 -0
  181. package/dist-cjs/mcp/sampling.d.ts +41 -0
  182. package/dist-cjs/mcp/sampling.js +55 -0
  183. package/dist-cjs/mcp/server.d.ts +96 -14
  184. package/dist-cjs/mcp/server.js +273 -58
  185. package/dist-cjs/models/data.js +20 -1
  186. package/dist-cjs/operations/adapters.d.ts +1 -0
  187. package/dist-cjs/operations/adapters.js +3 -1
  188. package/dist-cjs/protocols/a2a-grpc.js +10 -3
  189. package/dist-cjs/protocols/a2a-signing.d.ts +65 -0
  190. package/dist-cjs/protocols/a2a-signing.js +140 -0
  191. package/dist-cjs/protocols/a2a.d.ts +14 -0
  192. package/dist-cjs/protocols/a2a.js +65 -6
  193. package/dist-cjs/providers/cohere.js +16 -2
  194. package/dist-cjs/providers/google.js +7 -1
  195. package/dist-cjs/providers/ollama.d.ts +1 -1
  196. package/dist-cjs/providers/ollama.js +35 -27
  197. package/dist-cjs/providers/openai.js +19 -2
  198. package/dist-cjs/rag/pinecone.d.ts +63 -2
  199. package/dist-cjs/rag/pinecone.js +81 -0
  200. package/dist-cjs/realtime/conversation.js +18 -0
  201. package/dist-cjs/realtime/gemini-live.transport.d.ts +28 -3
  202. package/dist-cjs/realtime/gemini-live.transport.js +170 -32
  203. package/dist-cjs/realtime/session.d.ts +2 -0
  204. package/dist-cjs/realtime/session.js +19 -4
  205. package/dist-cjs/realtime/types.d.ts +35 -3
  206. package/dist-cjs/security/output-guard.js +4 -1
  207. package/dist-cjs/server/auth.d.ts +263 -2
  208. package/dist-cjs/server/auth.js +437 -5
  209. package/dist-cjs/server/deployments.d.ts +3 -2
  210. package/dist-cjs/server/deployments.js +6 -1
  211. package/dist-cjs/telephony/index.d.ts +1 -1
  212. package/dist-cjs/telephony/manager.d.ts +8 -1
  213. package/dist-cjs/telephony/manager.js +13 -0
  214. package/dist-cjs/telephony/providers/telnyx.d.ts +18 -5
  215. package/dist-cjs/telephony/providers/telnyx.js +44 -7
  216. package/dist-cjs/telephony/providers/twilio.d.ts +3 -1
  217. package/dist-cjs/telephony/providers/twilio.js +16 -0
  218. package/dist-cjs/tracing/stores.d.ts +2 -0
  219. package/dist-cjs/tracing/stores.js +1 -0
  220. package/dist-cjs/tracing/tracer.d.ts +5 -2
  221. package/dist-cjs/tracing/tracer.js +6 -3
  222. package/dist-cjs/types/embeddings.d.ts +47 -5
  223. package/dist-cjs/types/messages.d.ts +7 -0
  224. package/dist-cjs/types/principal.d.ts +1 -1
  225. package/dist-cjs/types/response.d.ts +5 -0
  226. package/dist-cjs/types/telephony.d.ts +38 -0
  227. package/dist-cjs/types/tracing.d.ts +6 -0
  228. package/dist-cjs/version.d.ts +1 -1
  229. package/dist-cjs/version.js +1 -1
  230. package/dist-cjs/voice/providers/google.d.ts +44 -0
  231. package/dist-cjs/voice/providers/google.js +252 -0
  232. package/llms.txt +1 -1
  233. package/package.json +17 -1
@@ -1,3 +1,4 @@
1
+ import { boxOverlap, } from './core.js';
1
2
  export function decisionRoute(decisions, options) {
2
3
  return async (graph) => {
3
4
  const { answers } = await decisions.decide({
@@ -128,9 +129,23 @@ export function decisionEvaluator(decisions, options) {
128
129
  const answer = answers.judged;
129
130
  const metadata = {
130
131
  confidence: answer.confidence,
131
- probabilities: answer.type === 'yes-no' ? { yes: answer.probability } : answer.probabilities,
132
+ ...(answer.type === 'box'
133
+ ? { box: answer.box }
134
+ : { probabilities: answer.type === 'yes-no' ? { yes: answer.probability } : answer.probabilities }),
132
135
  model,
133
136
  };
137
+ if (answer.type === 'box') {
138
+ const where = options.where?.(context);
139
+ if (!where)
140
+ return { key: options.key, score: answer.confidence, metadata };
141
+ const overlap = boxOverlap(answer.box, where);
142
+ return {
143
+ key: options.key,
144
+ score: overlap,
145
+ passed: overlap >= (options.overlap ?? 0.5),
146
+ metadata,
147
+ };
148
+ }
134
149
  if (answer.type === 'yes-no') {
135
150
  const passWhen = options.passing !== false;
136
151
  return {
@@ -7,8 +7,9 @@ export interface MockDecisionProviderOptions {
7
7
  /** The model it reports. Defaults to `mock-decision`. */
8
8
  model?: string;
9
9
  /**
10
- * Answers each question with its probabilities, from the question's name, the question, and the
11
- * state. Returning `undefined`, or leaving it out, answers with an even spread: confidence 0.
10
+ * Answers each question with its probabilities, or a box, from the question's name, the question,
11
+ * and the state, pictures and all. Returning `undefined`, or leaving it out, answers with an even
12
+ * spread, and a box with the whole picture: confidence 0.
12
13
  */
13
14
  answer?: (name: string, question: DecisionProviderQuestion, state: DecisionState) => DecisionDistribution | undefined;
14
15
  /** Fails every call with this error, or the error the function returns. */
@@ -18,7 +19,8 @@ export interface MockDecisionProviderOptions {
18
19
  }
19
20
  /**
20
21
  * Answers decisions as `answer` says, without a model, and keeps every request it was asked in
21
- * `requests`. It takes every question type, 255 options, and 10 levels, as TypeSafe AI's Jev does.
22
+ * `requests`. It takes every question type, pictures, 255 options, and 10 levels, as TypeSafe AI's Jev
23
+ * does.
22
24
  */
23
25
  export declare class MockDecisionProvider implements DecisionProvider {
24
26
  private readonly options;
@@ -7,9 +7,10 @@ export class MockDecisionProvider {
7
7
  this.info = {
8
8
  name: options.name ?? 'mock',
9
9
  capabilities: {
10
- types: ['yes-no', 'choice', 'score'],
10
+ types: ['yes-no', 'choice', 'score', 'box'],
11
11
  maxOptions: 255,
12
12
  maxLevels: 10,
13
+ images: true,
13
14
  defaultModel: options.model ?? 'mock-decision',
14
15
  },
15
16
  };
@@ -37,6 +38,8 @@ export class MockDecisionProvider {
37
38
  function evenly(question) {
38
39
  if (question.type === 'yes-no')
39
40
  return { type: 'yes-no', yes: 0.5 };
41
+ if (question.type === 'box')
42
+ return { type: 'box', box: { top: 0, bottom: 1, left: 0, right: 1 }, confidence: 0 };
40
43
  if (question.type === 'choice') {
41
44
  return {
42
45
  type: 'choice',
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The pictures a decision's state holds: image parts, or image assets, anywhere in an object or a
3
+ * list, each named by its path, which a question writes in backticks, such as `photo`, `faces.happy`,
4
+ * or `frames[2].frame`.
5
+ */
6
+ import { type MediaResolveOptions } from '../evaluate/media.js';
7
+ import type { MediaPart } from '../types/evaluate.js';
8
+ /** A picture in a decision's state, and where it is. */
9
+ export interface DecisionPicture {
10
+ /** Its path in the state, as a question names it in backticks: `photo`, `faces.happy`, or `frames[2].frame`. */
11
+ path: string;
12
+ /** The picture: an image part, `{ type: 'image', source }`, or an image asset. */
13
+ part: MediaPart;
14
+ }
15
+ /** A picture's bytes, read for a provider that sends them. */
16
+ export interface ReadDecisionPicture extends DecisionPicture {
17
+ /** Its bytes. */
18
+ bytes: Uint8Array;
19
+ /** Its MIME type, as its first bytes say, whatever it was declared as. */
20
+ mimeType: string;
21
+ /** Its width in pixels, from its header. */
22
+ width?: number;
23
+ /** Its height in pixels. */
24
+ height?: number;
25
+ }
26
+ /** Every picture in a state, in the order they appear, each with its path. A picture used twice is listed at both. */
27
+ export declare function statePictures(state: unknown): DecisionPicture[];
28
+ /**
29
+ * The state with each picture replaced by what `replace` returns for it, copying only the objects and
30
+ * lists on the way to one, so a state without pictures comes back as it was given.
31
+ */
32
+ export declare function replacePictures(state: unknown, replace: (picture: DecisionPicture) => unknown): unknown;
33
+ /**
34
+ * Identifies a picture without its bytes, for a cache key or a trace: a digest of what it holds, or
35
+ * where it is when it is a file or at a URL, so a picture changed at the same path is keyed the same.
36
+ */
37
+ export declare function pictureDigest(part: MediaPart): string;
38
+ /**
39
+ * Reads a picture's bytes, with the media family's readers: bytes and base64 as they are, a file with
40
+ * `readFile`, a URL with `fetch`, and a stored asset from `assets`. Throws `DecisionError`
41
+ * `DECISION_INVALID`, naming the picture's path, for one that cannot be read or is not a picture.
42
+ */
43
+ export declare function readPicture(picture: DecisionPicture, options?: MediaResolveOptions): Promise<ReadDecisionPicture>;
@@ -0,0 +1,103 @@
1
+ import { resolveMedia } from '../evaluate/media.js';
2
+ import { asMediaPart } from '../evaluate/trace.js';
3
+ import { sha256Hex } from '../utils/sha256.js';
4
+ import { DecisionError } from './errors.js';
5
+ const MAX_DEPTH = 256;
6
+ export function statePictures(state) {
7
+ const found = [];
8
+ replacePictures(state, (picture) => {
9
+ found.push(picture);
10
+ return picture.part;
11
+ });
12
+ return found;
13
+ }
14
+ export function replacePictures(state, replace) {
15
+ const above = new Set();
16
+ const visit = (item, path, depth) => {
17
+ if (item === null || typeof item !== 'object' || depth > MAX_DEPTH || above.has(item))
18
+ return item;
19
+ if (ArrayBuffer.isView(item) || item instanceof ArrayBuffer)
20
+ return item;
21
+ const part = pictureOf(item);
22
+ if (part)
23
+ return replace({ path, part });
24
+ above.add(item);
25
+ try {
26
+ if (Array.isArray(item)) {
27
+ let changed = false;
28
+ const copy = item.map((entry, index) => {
29
+ const next = visit(entry, `${path}[${index}]`, depth + 1);
30
+ if (next !== entry)
31
+ changed = true;
32
+ return next;
33
+ });
34
+ return changed ? copy : item;
35
+ }
36
+ const prototype = Object.getPrototypeOf(item);
37
+ if (prototype !== Object.prototype && prototype !== null)
38
+ return item;
39
+ let changed = false;
40
+ const copy = {};
41
+ for (const [key, entry] of Object.entries(item)) {
42
+ const next = visit(entry, path ? `${path}.${key}` : key, depth + 1);
43
+ if (next !== entry)
44
+ changed = true;
45
+ copy[key] = next;
46
+ }
47
+ return changed ? copy : item;
48
+ }
49
+ finally {
50
+ above.delete(item);
51
+ }
52
+ };
53
+ return visit(state, '', 0);
54
+ }
55
+ export function pictureDigest(part) {
56
+ const source = (part.type === 'asset' ? part.asset.location : part.source);
57
+ if (typeof source.base64 === 'string')
58
+ return `sha256:${sha256Hex(source.base64)}`;
59
+ const bytes = source.buffer ?? source.data;
60
+ if (bytes instanceof Uint8Array)
61
+ return `sha256:${sha256Hex(bytes)}`;
62
+ if (typeof source.path === 'string')
63
+ return `path:${source.path}`;
64
+ if (typeof source.url === 'string')
65
+ return `url:${source.url}`;
66
+ if (typeof source.assetId === 'string')
67
+ return `asset:${source.assetId}`;
68
+ if (source.asset && typeof source.asset === 'object')
69
+ return pictureDigest({ type: 'asset', asset: source.asset });
70
+ return `sha256:${sha256Hex(JSON.stringify(source) ?? '')}`;
71
+ }
72
+ export async function readPicture(picture, options = {}) {
73
+ const media = await resolveMedia(picture.part, options);
74
+ if (!media.bytes) {
75
+ const why = media.unreadable ??
76
+ (media.path !== undefined
77
+ ? `it is the file ${media.path}, and no readFile was given to read it`
78
+ : media.url !== undefined
79
+ ? `it is at ${media.url}, and no fetch was given to fetch it`
80
+ : 'it holds no bytes');
81
+ throw new DecisionError(`The picture at \`${picture.path}\` could not be read: ${why}`, 'DECISION_INVALID');
82
+ }
83
+ if (media.kind !== 'image' || !media.mimeType?.startsWith('image/')) {
84
+ throw new DecisionError(`The picture at \`${picture.path}\` is not an image its bytes name${media.mimeType ? `, but ${media.mimeType}` : ''}`, 'DECISION_INVALID');
85
+ }
86
+ return {
87
+ ...picture,
88
+ bytes: media.bytes,
89
+ mimeType: media.mimeType,
90
+ ...(media.width !== undefined ? { width: media.width } : {}),
91
+ ...(media.height !== undefined ? { height: media.height } : {}),
92
+ };
93
+ }
94
+ function pictureOf(item) {
95
+ const part = asMediaPart(item);
96
+ if (!part)
97
+ return undefined;
98
+ if (part.type === 'image')
99
+ return part;
100
+ if (part.type === 'asset' && part.asset.mimeType.startsWith('image/'))
101
+ return part;
102
+ return undefined;
103
+ }
@@ -1,4 +1,10 @@
1
- import { type DecisionCallContext, type DecisionProvider, type DecisionProviderInfo, type DecisionProviderRequest, type DecisionProviderResult } from './core.js';
1
+ /**
2
+ * TypeSafe AI's decision models, Jev first, over its System One API: every question of a decision in
3
+ * one call, each answered with its probabilities, or with a box around something in a picture, and no
4
+ * text to parse.
5
+ */
6
+ import type { MediaResolveOptions } from '../evaluate/media.js';
7
+ import type { DecisionCallContext, DecisionProvider, DecisionProviderInfo, DecisionProviderRequest, DecisionProviderResult } from './core.js';
2
8
  /** Options for `TypeSafeDecisionProvider`. */
3
9
  export interface TypeSafeDecisionProviderOptions {
4
10
  /** A TypeSafe AI API key, from its console. A tenant's resolved credential replaces it per call. */
@@ -15,6 +21,18 @@ export interface TypeSafeDecisionProviderOptions {
15
21
  * than its usual context, and marks as experimental.
16
22
  */
17
23
  longContext?: boolean;
24
+ /**
25
+ * Reads the pictures a state holds by reference, with the media family's readers: `readFile` for a
26
+ * file, `fetch` for a URL, and `assets` for a stored asset. A picture held as bytes or base64 needs
27
+ * none of them. Without them, a picture by reference fails the decision, naming it.
28
+ */
29
+ media?: MediaResolveOptions;
30
+ /**
31
+ * How a decision's pictures travel: `multipart`, the default, each as a part of its own beside the
32
+ * request, as bytes; or `base64`, within the JSON request, a third larger. Either way a request
33
+ * holds at most 32 MiB, so multipart carries more.
34
+ */
35
+ pictures?: 'multipart' | 'base64';
18
36
  /** How long a call may take before it fails, in milliseconds. Defaults to 30 seconds. */
19
37
  timeoutMs?: number;
20
38
  /** Headers added to every request. */
@@ -24,9 +42,12 @@ export interface TypeSafeDecisionProviderOptions {
24
42
  }
25
43
  /**
26
44
  * TypeSafe AI's System One API (`POST /v1/systemone`): Jev, a model that answers only typed
27
- * questions, a yes or no ("noul"), a choice among up to 255 options, or a score on up to ten levels,
28
- * each with its probabilities, and bills for input tokens alone. Every question of a decision goes in
29
- * one call and is answered in isolation, against the same state.
45
+ * questions, a yes or no ("noul"), a choice among up to 255 options, a score on up to ten levels, or a
46
+ * box around something in a picture, and bills for input tokens alone. Every question of a decision
47
+ * goes in one call and is answered in isolation, against the same state. The pictures a state holds
48
+ * are sent as the API's assets, each as a part of a multipart request unless `pictures` says
49
+ * `base64`, with the `TypeSafe-Danger-Zone-Images` header it requires for them and for a box, both of
50
+ * which it marks as experimental; a box needs Jev 1.14 or later.
30
51
  *
31
52
  * ```ts
32
53
  * const decisions = new DecisionManager({ providers: { jev: new TypeSafeDecisionProvider({ apiKey: process.env.TYPESAFE_API_KEY! }) } });
@@ -34,12 +55,19 @@ export interface TypeSafeDecisionProviderOptions {
34
55
  */
35
56
  export declare class TypeSafeDecisionProvider implements DecisionProvider {
36
57
  private readonly options;
37
- /** Its name and what it takes: every question type, 255 options, and 10 levels. */
58
+ /** Its name and what it takes: every question type, pictures, 255 options, and 10 levels. */
38
59
  readonly info: DecisionProviderInfo;
39
60
  constructor(options: TypeSafeDecisionProviderOptions);
40
61
  /** Answers every question of a decision in one call. */
41
62
  decide(request: DecisionProviderRequest, context: DecisionCallContext): Promise<DecisionProviderResult>;
42
63
  /** The models the key may use, from `GET /v1/models`. */
43
64
  listModels(context?: Partial<DecisionCallContext>): Promise<string[]>;
65
+ /**
66
+ * The state with each picture replaced by a reference of its own, and the pictures as assets by those
67
+ * references; a state without pictures as it was. A new reference per picture per request, as the
68
+ * API advises, since it matches a reference wherever its text appears. Sent multipart, each picture's
69
+ * part is named by its reference too.
70
+ */
71
+ private withAssets;
44
72
  private call;
45
73
  }
@@ -1,5 +1,11 @@
1
+ import { encodeBase64 } from '../utils/base64.js';
1
2
  import { linkSignals } from '../utils/signals.js';
2
- import { DecisionError, } from './core.js';
3
+ import { DecisionError } from './errors.js';
4
+ import { readPicture, replacePictures, statePictures } from './pictures.js';
5
+ const PICTURE_BYTES = 16 * 1024 * 1024;
6
+ const PICTURE_SIDE = { min: 280, max: 1568 };
7
+ const REQUEST_BYTES = 32 * 1024 * 1024;
8
+ const PART_OVERHEAD = 256;
3
9
  export class TypeSafeDecisionProvider {
4
10
  options;
5
11
  info;
@@ -8,16 +14,40 @@ export class TypeSafeDecisionProvider {
8
14
  this.info = {
9
15
  name: 'typesafe',
10
16
  capabilities: {
11
- types: ['yes-no', 'choice', 'score'],
17
+ types: ['yes-no', 'choice', 'score', 'box'],
12
18
  maxOptions: 255,
13
19
  maxLevels: 10,
20
+ images: true,
14
21
  defaultModel: options.model ?? 'jev-latest',
15
22
  },
16
23
  };
17
24
  }
18
25
  async decide(request, context) {
26
+ context.signal.throwIfAborted();
27
+ const boxes = Object.keys(request.questions).filter((name) => request.questions[name]?.type === 'box');
28
+ const before = boxes.length ? beforeBoxes(request.model) : undefined;
29
+ if (before) {
30
+ throw new DecisionError(`TypeSafe AI answers a box, such as "${boxes[0]}", on Jev 1.14 or later, not ${before}`, 'DECISION_UNSUPPORTED');
31
+ }
32
+ const multipart = (this.options.pictures ?? 'multipart') === 'multipart';
33
+ const { state, assets, parts } = await this.withAssets(request.state, context, multipart);
19
34
  const questions = Object.fromEntries(Object.entries(request.questions).map(([name, question]) => [name, wireQuestion(question)]));
20
- const payload = await this.call('/v1/systemone', { method: 'POST', body: JSON.stringify({ model: request.model, state: request.state, questions }) }, context);
35
+ const json = JSON.stringify({ model: request.model, state, questions, ...(assets ? { assets } : {}) });
36
+ const size = new TextEncoder().encode(json).byteLength +
37
+ (parts.length ? parts.reduce((total, part) => total + part.bytes.byteLength + PART_OVERHEAD, PART_OVERHEAD) : 0);
38
+ if (assets && size > REQUEST_BYTES) {
39
+ throw new DecisionError(`The decision is ${size} bytes with its pictures, and TypeSafe AI takes at most ${REQUEST_BYTES}`, 'DECISION_INVALID');
40
+ }
41
+ let body = json;
42
+ if (parts.length) {
43
+ const form = new FormData();
44
+ form.append('request', json);
45
+ for (const part of parts) {
46
+ form.append(part.name, new Blob([part.bytes], { type: `image/${part.format}` }), `${part.name}.${part.format === 'png' ? 'png' : 'jpg'}`);
47
+ }
48
+ body = form;
49
+ }
50
+ const payload = await this.call('/v1/systemone', { method: 'POST', body }, context, assets || boxes.length ? { 'TypeSafe-Danger-Zone-Images': 'true' } : {});
21
51
  const distributions = {};
22
52
  for (const [name, question] of Object.entries(request.questions)) {
23
53
  const answer = payload.answers?.[name];
@@ -51,7 +81,29 @@ export class TypeSafeDecisionProvider {
51
81
  : String(item?.id ?? item?.name ?? ''))
52
82
  .filter(Boolean);
53
83
  }
54
- async call(path, init, context) {
84
+ async withAssets(state, context, multipart) {
85
+ const pictures = typeof state === 'string' ? [] : statePictures(state);
86
+ if (pictures.length === 0)
87
+ return { state, parts: [] };
88
+ const read = await Promise.all(pictures.map((picture) => readPicture(picture, this.options.media)));
89
+ context.signal.throwIfAborted();
90
+ const references = new Map();
91
+ const assets = {};
92
+ const parts = [];
93
+ for (const picture of read) {
94
+ const format = fitting(picture);
95
+ const reference = globalThis.crypto.randomUUID();
96
+ references.set(picture.path, reference);
97
+ if (multipart) {
98
+ assets[reference] = { type: 'image', format, part: reference };
99
+ parts.push({ name: reference, format, bytes: picture.bytes });
100
+ }
101
+ else
102
+ assets[reference] = { type: 'image', format, data_base64: encodeBase64(picture.bytes) };
103
+ }
104
+ return { state: replacePictures(state, (picture) => references.get(picture.path)), assets, parts };
105
+ }
106
+ async call(path, init, context, extra = {}) {
55
107
  const credential = await context.credential?.(this.info.name);
56
108
  const linked = linkSignals([context.signal, AbortSignal.timeout(this.options.timeoutMs ?? 30_000)]);
57
109
  const signal = linked.signal;
@@ -61,9 +113,10 @@ export class TypeSafeDecisionProvider {
61
113
  response = await (this.options.fetch ?? fetch)(`${(this.options.baseUrl ?? 'https://api.typesafe.ai').replace(/\/+$/, '')}${path}`, {
62
114
  ...init,
63
115
  headers: {
64
- ...(init.body ? { 'content-type': 'application/json' } : {}),
116
+ ...(typeof init.body === 'string' ? { 'content-type': 'application/json' } : {}),
65
117
  authorization: `Bearer ${credential?.apiKey ?? this.options.apiKey}`,
66
118
  ...(this.options.longContext ? { 'TypeSafe-Danger-Zone-Long-Context': 'true' } : {}),
119
+ ...extra,
67
120
  ...this.options.headers,
68
121
  ...credential?.headers,
69
122
  },
@@ -89,7 +142,51 @@ export class TypeSafeDecisionProvider {
89
142
  }
90
143
  }
91
144
  }
145
+ function beforeBoxes(model) {
146
+ const version = /^jev-(\d+)\.(\d+)/.exec(model);
147
+ if (!version)
148
+ return undefined;
149
+ const [major, minor] = [Number(version[1]), Number(version[2])];
150
+ return major < 1 || (major === 1 && minor < 14) ? model : undefined;
151
+ }
152
+ function fitting(picture) {
153
+ const at = `The picture at \`${picture.path}\``;
154
+ const refuse = (why) => new DecisionError(`${at} ${why}; TypeSafe AI refuses it rather than convert or resize it`, 'DECISION_INVALID');
155
+ if (picture.mimeType !== 'image/png' && picture.mimeType !== 'image/jpeg') {
156
+ throw refuse(`is ${picture.mimeType}, and TypeSafe AI takes PNG and JPEG`);
157
+ }
158
+ if (picture.mimeType === 'image/png' && animated(picture.bytes))
159
+ throw refuse('is an animated PNG');
160
+ if (picture.bytes.length > PICTURE_BYTES) {
161
+ throw refuse(`is ${picture.bytes.length} bytes, over TypeSafe AI's ${PICTURE_BYTES}`);
162
+ }
163
+ const { width, height } = picture;
164
+ if (width === undefined || height === undefined)
165
+ throw refuse('has a header its size cannot be read from');
166
+ if ([width, height].some((side) => side < PICTURE_SIDE.min || side > PICTURE_SIDE.max)) {
167
+ throw refuse(`is ${width} by ${height} pixels, and TypeSafe AI takes ${PICTURE_SIDE.min} to ${PICTURE_SIDE.max} a side`);
168
+ }
169
+ return picture.mimeType === 'image/png' ? 'png' : 'jpeg';
170
+ }
171
+ function animated(bytes) {
172
+ let offset = 8;
173
+ while (offset + 8 <= bytes.length) {
174
+ const length = ((bytes[offset] ?? 0) << 24) |
175
+ ((bytes[offset + 1] ?? 0) << 16) |
176
+ ((bytes[offset + 2] ?? 0) << 8) |
177
+ (bytes[offset + 3] ?? 0);
178
+ const type = String.fromCharCode(...bytes.subarray(offset + 4, offset + 8));
179
+ if (type === 'acTL')
180
+ return true;
181
+ if (type === 'IDAT' || type === 'IEND' || length < 0)
182
+ return false;
183
+ offset += 12 + length;
184
+ }
185
+ return false;
186
+ }
92
187
  function wireQuestion(question) {
188
+ if (question.type === 'box')
189
+ return { type: 'box', instructions: question.question };
93
190
  const instructions = question.question ? { instructions: question.question } : {};
94
191
  if (question.type === 'yes-no') {
95
192
  const criteria = {
@@ -118,21 +215,37 @@ function distributionOf(name, question, answer) {
118
215
  throw invalidAnswer(name, 'choice', answer);
119
216
  return { type: 'choice', probabilities: answer.probabilities };
120
217
  }
218
+ if (question.type === 'box') {
219
+ const box = answer.box;
220
+ if (!box || typeof answer.confidence !== 'number')
221
+ throw invalidAnswer(name, 'box', answer);
222
+ const edges = box;
223
+ return {
224
+ type: 'box',
225
+ box: { top: edges.top, bottom: edges.bottom, left: edges.left, right: edges.right },
226
+ confidence: answer.confidence,
227
+ };
228
+ }
121
229
  if (!answer.probabilities)
122
230
  throw invalidAnswer(name, 'score', answer);
123
231
  const probabilities = answer.probabilities;
124
232
  return { type: 'score', probabilities: question.levels.map((_, level) => probabilities[String(level)] ?? 0) };
125
233
  }
126
234
  function invalidAnswer(name, type, answer) {
127
- return new DecisionError(`TypeSafe AI answered "${name}" without the ${type} probabilities it asks for: ${JSON.stringify(answer).slice(0, 200)}`, 'DECISION_FAILED');
235
+ return new DecisionError(`TypeSafe AI answered "${name}" without the ${type} it asks for: ${JSON.stringify(answer).slice(0, 200)}`, 'DECISION_FAILED');
128
236
  }
129
237
  function failure(status, text) {
130
- if (status === 422) {
238
+ if (status === 422 || status === 400 || status === 413) {
131
239
  let detail = text.slice(0, 300);
132
240
  try {
133
241
  const parsed = JSON.parse(text);
134
242
  if (Array.isArray(parsed.detail)) {
135
- detail = parsed.detail.map((item) => `${(item.loc ?? []).join('.')}: ${item.msg ?? 'invalid'}`).join('; ');
243
+ detail = parsed.detail
244
+ .map((item) => `${(item.loc ?? []).join('.')}: ${item.msg ?? 'invalid'}`)
245
+ .join('; ');
246
+ }
247
+ else if (typeof parsed.detail === 'string') {
248
+ detail = parsed.detail;
136
249
  }
137
250
  }
138
251
  catch {
@@ -1,12 +1,3 @@
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
1
  import type { NexusAIConfig } from '../types/config.js';
11
2
  import type { SchemaMigration } from '../utils/schema-migrations.js';
12
3
  /** How one check came out: `fail` breaks the deployment, `warn` needs a look, `skip` had nothing to check. */
@@ -97,6 +88,12 @@ export interface DoctorOptions {
97
88
  models?: NexusAIConfig['models'];
98
89
  /** How the application is deployed. */
99
90
  deployment?: DoctorDeployment;
91
+ /**
92
+ * A release to check readiness for: `3.0` lists, for the stores `deployment` describes, what 3.0's
93
+ * defaults would change and what to do first, or confirms the preview of them is on. Without it, the
94
+ * check is skipped.
95
+ */
96
+ upgrade?: '3.0';
100
97
  /** Replaces the clock, for tests. */
101
98
  now?: () => Date;
102
99
  }
@@ -1,3 +1,4 @@
1
+ import { nexusDefaults } from '../core/defaults.js';
1
2
  import { createRequire } from 'node:module';
2
3
  import path from 'node:path';
3
4
  const PEERS = {
@@ -55,6 +56,7 @@ export async function diagnose(options = {}) {
55
56
  await run('health', 'Provider health', () => healthCheck(options.ai));
56
57
  await run('topology', 'Settings that only work in one process', () => topologyCheck(options.deployment));
57
58
  await run('settings', 'Opt-in settings a deployment needs', () => settingsCheck(options.deployment));
59
+ await run('upgrade', 'What 3.0 changes', () => upgradeCheck(options.upgrade, options.deployment));
58
60
  const failures = checks.filter((check) => check.status === 'fail').length;
59
61
  const warnings = checks.filter((check) => check.status === 'warn').length;
60
62
  return { checks, ok: failures === 0, failures, warnings };
@@ -266,6 +268,45 @@ function settingsCheck(deployment) {
266
268
  return { status: 'ok', detail: 'No store lacks a setting a deployment needs.' };
267
269
  return { status: 'warn', detail: `${findings.join('; ')}.`, hint: [...hints].join(' ') };
268
270
  }
271
+ async function upgradeCheck(upgrade, deployment) {
272
+ if (upgrade === undefined)
273
+ return { status: 'skip', detail: 'No release asked about: pass upgrade 3.0.' };
274
+ if (upgrade !== '3.0')
275
+ throw new Error(`Readiness is checked for 3.0, not ${JSON.stringify(upgrade)}`);
276
+ if (nexusDefaults() === '3.0') {
277
+ return { status: 'ok', detail: "3.0's defaults are on already: what runs here is what 3.0 runs." };
278
+ }
279
+ const stores = Object.entries(deployment?.stores ?? {});
280
+ if (stores.length === 0)
281
+ return { status: 'skip', detail: 'No stores described.' };
282
+ const findings = [];
283
+ for (const [name, value] of stores) {
284
+ const kind = kindOf(value);
285
+ if (kind === 'Tracer') {
286
+ const tracer = value;
287
+ if (tracer.incremental === false && tracer.store && tracer.store.durable !== false) {
288
+ findings.push(`${name} writes each trace when its root finishes; 3.0 writes each run as it starts and ends, unless it is created with incremental: false`);
289
+ }
290
+ }
291
+ if (kind === 'RedisOperationStore' && value.indexed === false) {
292
+ findings.push(`${name} reads every record to find queued work; 3.0 keeps an index, unless it is created with index: false: first run 2.2 or later on every worker sharing its prefix, and call reindex() once`);
293
+ }
294
+ if (kind === 'Deployments' && typeof value.list === 'function') {
295
+ const records = await value.list();
296
+ const unrecorded = records.filter((record) => record.version === 0 && record.bucketStrategy === undefined);
297
+ if (unrecorded.length > 0) {
298
+ findings.push(`${name} has not yet recorded ${unrecorded.map((record) => record.assistant).join(', ')}; 3.0 would first record ${unrecorded.length === 1 ? 'it' : 'them'} with hash-v2, unless registered with bucketStrategy: 'fnv1a-v1'`);
299
+ }
300
+ }
301
+ }
302
+ if (findings.length === 0)
303
+ return { status: 'ok', detail: "Nothing described here changes under 3.0's defaults." };
304
+ return {
305
+ status: 'warn',
306
+ detail: `${findings.join('; ')}.`,
307
+ hint: "Run with 3.0's defaults now: call useDefaults('3.0') before creating anything, or set NEXUS_DEFAULTS=3.0.",
308
+ };
309
+ }
269
310
  function kindOf(value) {
270
311
  return value?.constructor?.name;
271
312
  }
@@ -52,7 +52,7 @@ export declare class GoogleEmbeddingProvider extends HttpEmbeddingProvider {
52
52
  readonly info: EmbeddingProviderInfo;
53
53
  constructor(options?: EmbeddingAdapterOptions);
54
54
  protected authHeaders(key: string | undefined): Record<string, string>;
55
- /** Embeds a batch of inputs: texts, or parts with images, audio, and documents. */
55
+ /** Embeds a batch of inputs: texts, or parts with images, audio, video, and documents. */
56
56
  embed(request: EmbeddingProviderRequest, context: EmbeddingProviderCallContext): Promise<EmbeddingProviderResult>;
57
57
  }
58
58
  /** Cohere v2 `/embed`. */
@@ -100,7 +100,7 @@ export class GoogleEmbeddingProvider extends HttpEmbeddingProvider {
100
100
  models: ['gemini-embedding-001', 'gemini-embedding-2', 'text-embedding-004'],
101
101
  maxBatchSize: 100,
102
102
  maxInputTokens: 8192,
103
- inputModalities: ['text', 'image', 'audio', 'document'],
103
+ inputModalities: ['text', 'image', 'audio', 'video', 'document'],
104
104
  dimensions: true,
105
105
  inputTypes: ['document', 'query', 'classification', 'clustering'],
106
106
  encodingFormats: ['float'],
@@ -149,10 +149,10 @@ export class CohereEmbeddingProvider extends HttpEmbeddingProvider {
149
149
  name: 'cohere',
150
150
  defaultModel: options.model || 'embed-v4.0',
151
151
  capabilities: {
152
- models: ['embed-v4.0', 'embed-english-v3.0', 'embed-multilingual-v3.0'],
152
+ models: ['embed-v5.0-pro', 'embed-v5.0-fast', 'embed-v4.0', 'embed-english-v3.0', 'embed-multilingual-v3.0'],
153
153
  maxBatchSize: 96,
154
154
  inputModalities: ['text', 'image'],
155
- dimensions: [256, 512, 1024, 1536],
155
+ dimensions: [256, 512, 768, 1024, 1536, 2048],
156
156
  inputTypes: ['document', 'query', 'classification', 'clustering'],
157
157
  encodingFormats: ['float'],
158
158
  truncate: true,