@intx/inference 0.1.2 → 0.2.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.
Files changed (97) hide show
  1. package/LICENSE +176 -0
  2. package/dist/actions.d.ts +16 -0
  3. package/dist/actions.js +200 -0
  4. package/dist/adapter.d.ts +38 -0
  5. package/dist/adapter.js +31 -0
  6. package/dist/assembly.d.ts +68 -0
  7. package/dist/assembly.js +132 -0
  8. package/dist/audit-collector.d.ts +10 -0
  9. package/dist/audit-collector.js +139 -0
  10. package/dist/auth.d.ts +24 -0
  11. package/{src/auth.ts → dist/auth.js} +13 -19
  12. package/dist/authz-extension.d.ts +32 -0
  13. package/dist/authz-extension.js +100 -0
  14. package/dist/correlation.d.ts +25 -0
  15. package/dist/correlation.js +32 -0
  16. package/dist/default-director.d.ts +111 -0
  17. package/dist/default-director.js +199 -0
  18. package/dist/director.d.ts +6 -0
  19. package/dist/director.js +56 -0
  20. package/dist/errors.d.ts +18 -0
  21. package/dist/errors.js +83 -0
  22. package/dist/gates.d.ts +27 -0
  23. package/dist/gates.js +80 -0
  24. package/dist/harness.d.ts +147 -0
  25. package/dist/harness.js +1319 -0
  26. package/dist/index.d.ts +37 -0
  27. package/dist/index.js +21 -0
  28. package/dist/manifest.d.ts +31 -0
  29. package/dist/manifest.js +44 -0
  30. package/dist/providers/anthropic.d.ts +33 -0
  31. package/dist/providers/anthropic.js +670 -0
  32. package/dist/providers/google-genai-files.d.ts +48 -0
  33. package/dist/providers/google-genai-files.js +205 -0
  34. package/dist/providers/google-genai.d.ts +3 -0
  35. package/dist/providers/google-genai.js +1196 -0
  36. package/dist/providers/index.d.ts +38 -0
  37. package/dist/providers/index.js +56 -0
  38. package/dist/providers/openai.d.ts +3 -0
  39. package/dist/providers/openai.js +609 -0
  40. package/dist/reactor.d.ts +50 -0
  41. package/dist/reactor.js +920 -0
  42. package/dist/retry-policy.d.ts +31 -0
  43. package/{src/retry-policy.ts → dist/retry-policy.js} +41 -53
  44. package/dist/sse.d.ts +1 -0
  45. package/dist/sse.js +63 -0
  46. package/dist/state.d.ts +23 -0
  47. package/dist/state.js +100 -0
  48. package/dist/tool-name.d.ts +6 -0
  49. package/dist/tool-name.js +110 -0
  50. package/dist/transform.d.ts +11 -0
  51. package/dist/transform.js +117 -0
  52. package/dist/transforms/index.d.ts +2 -0
  53. package/dist/transforms/index.js +1 -0
  54. package/dist/transforms/size-cap.d.ts +12 -0
  55. package/dist/transforms/size-cap.js +80 -0
  56. package/dist/turns.d.ts +21 -0
  57. package/dist/turns.js +135 -0
  58. package/package.json +21 -6
  59. package/src/actions.ts +0 -245
  60. package/src/adapter.ts +0 -57
  61. package/src/assembly.test.ts +0 -728
  62. package/src/assembly.ts +0 -250
  63. package/src/audit-collector.test.ts +0 -332
  64. package/src/audit-collector.ts +0 -172
  65. package/src/auth.test.ts +0 -117
  66. package/src/authz-extension.test.ts +0 -269
  67. package/src/authz-extension.ts +0 -145
  68. package/src/correlation.ts +0 -61
  69. package/src/default-director.test.ts +0 -314
  70. package/src/default-director.ts +0 -344
  71. package/src/director.ts +0 -87
  72. package/src/errors.test.ts +0 -133
  73. package/src/errors.ts +0 -115
  74. package/src/gates.ts +0 -128
  75. package/src/harness.test.ts +0 -655
  76. package/src/harness.ts +0 -1571
  77. package/src/index.ts +0 -76
  78. package/src/providers/anthropic.test.ts +0 -771
  79. package/src/providers/anthropic.ts +0 -810
  80. package/src/providers/google-genai-files.ts +0 -289
  81. package/src/providers/google-genai.ts +0 -1518
  82. package/src/providers/openai.ts +0 -719
  83. package/src/providers/registry.ts +0 -33
  84. package/src/reactor.test.ts +0 -3660
  85. package/src/reactor.ts +0 -1058
  86. package/src/scheduler.test.ts +0 -41
  87. package/src/sse.test.ts +0 -133
  88. package/src/sse.ts +0 -76
  89. package/src/state.ts +0 -135
  90. package/src/transform.test.ts +0 -207
  91. package/src/transform.ts +0 -159
  92. package/src/transforms/index.ts +0 -2
  93. package/src/transforms/size-cap.test.ts +0 -172
  94. package/src/transforms/size-cap.ts +0 -110
  95. package/src/turns.ts +0 -54
  96. package/tsconfig.json +0 -4
  97. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Callable shape the helper accepts for `fetch` injection. Mirrors
3
+ * `Dependencies.fetch` on the inference harness, so test doubles
4
+ * stay structurally compatible across both surfaces. Bun's global
5
+ * `typeof fetch` carries non-callable members (e.g. `preconnect`)
6
+ * that test doubles would otherwise have to satisfy gratuitously.
7
+ */
8
+ export type UploadGoogleGenAIFileFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
9
+ export interface UploadGoogleGenAIFileOpts {
10
+ apiKey: string;
11
+ mimeType: string;
12
+ displayName: string;
13
+ bytes: Uint8Array;
14
+ uploadURL?: string;
15
+ fetch?: UploadGoogleGenAIFileFetch;
16
+ signal?: AbortSignal;
17
+ }
18
+ export interface UploadedGoogleGenAIFile {
19
+ fileUri: string;
20
+ mimeType: string;
21
+ sizeBytes?: number;
22
+ name?: string;
23
+ state?: string;
24
+ }
25
+ /**
26
+ * Upload bytes to the Gemini Files API and return the file URI.
27
+ *
28
+ * The Files API is the dereferencable handle path for Gemini media:
29
+ * upload once, reference the returned `fileUri` on subsequent
30
+ * inference requests via a `fileData.fileUri` part (or a
31
+ * `MediaSource` of `kind: "file-reference"` whose `reference` is the
32
+ * URI). The trade-off relative to inline `base64` is bytes-on-the-wire:
33
+ * inline payloads ship with every request, file references ship
34
+ * once and are then quoted.
35
+ *
36
+ * The helper exclusively uses the "raw" upload protocol (one
37
+ * `POST` carries the full bytes). The resumable and multipart
38
+ * protocols are out of scope; large-file workflows that need them
39
+ * should compose against the Gemini SDK directly.
40
+ *
41
+ * @throws an Error when the upload returns non-2xx, when the
42
+ * response is not JSON, or when the response shape does not
43
+ * carry a non-empty `file.uri` + `file.mimeType`. The thrown
44
+ * error's message names the failure mode; HTTP errors include
45
+ * the status code and a snippet of the response body, and
46
+ * schema/JSON failures carry a snippet of the response body too.
47
+ */
48
+ export declare function uploadGoogleGenAIFile(opts: UploadGoogleGenAIFileOpts): Promise<UploadedGoogleGenAIFile>;
@@ -0,0 +1,205 @@
1
+ import { type } from "arktype";
2
+ // Google's Generative Language Files API endpoint for raw single-part
3
+ // uploads. The "raw" upload protocol (declared via
4
+ // `X-Goog-Upload-Protocol: raw`) accepts the bytes as the request
5
+ // body and returns the file resource (uri + metadata) on the
6
+ // response. The resumable and multipart protocols target larger
7
+ // files and a different endpoint vocabulary; the helper here covers
8
+ // only the raw shape because it maps cleanly onto a single fetch.
9
+ const FILES_API_UPLOAD_DEFAULT_URL = "https://generativelanguage.googleapis.com/upload/v1beta/files";
10
+ // Parseable-integer pattern. Accepts an optional minus sign followed
11
+ // by digits and nothing else. Excludes whitespace, trailing
12
+ // non-digits, scientific notation, decimals. `Number.parseInt`
13
+ // alone is permissive on all four ("42abc" parses to 42, " 42"
14
+ // parses to 42); this guard makes the wire schema honest.
15
+ const PARSEABLE_INTEGER = /^-?\d+$/;
16
+ const FilesApiUploadResponse = type({
17
+ file: {
18
+ // `> 0` rejects empty strings -- a "" uri is not dereferenceable
19
+ // and would slip through a `"string"` validator. Same for the
20
+ // mime type.
21
+ uri: "string > 0",
22
+ mimeType: "string > 0",
23
+ // `sizeBytes` lands as a stringified number on the wire. The
24
+ // schema accepts string or number; the parser normalizes both
25
+ // to a runtime integer at extraction time and throws on any
26
+ // value that is not a parseable integer.
27
+ "sizeBytes?": "string | number",
28
+ "name?": "string",
29
+ "state?": "string",
30
+ "source?": "string",
31
+ "createTime?": "string",
32
+ "updateTime?": "string",
33
+ "expirationTime?": "string",
34
+ "sha256Hash?": "string",
35
+ },
36
+ });
37
+ // Header values must be free of control characters (CR/LF are
38
+ // the smuggling-relevant ones, but NUL and other CTL bytes are
39
+ // also illegal per RFC 9110 §5.5 visible-US-ASCII rule). The
40
+ // downstream `fetch` typically rejects CR/LF but not NUL, so
41
+ // catching the broader set here keeps the diagnostic specific
42
+ // to the offending input. This is the boundary that turns
43
+ // caller strings into HTTP request structure (per the style
44
+ // skill's Data Validation rule), so the check belongs here.
45
+ //
46
+ // `apiKey` flows through the same guard because the rule is
47
+ // "validate at the boundary" -- the boundary cannot know an
48
+ // input's provenance, and the caller may not have run their own
49
+ // sanity check.
50
+ // The guard's whole purpose is to reject CR/LF/NUL/other CTL
51
+ // bytes before they reach the downstream fetch, so the regex
52
+ // MUST match those code points.
53
+ // eslint-disable-next-line no-control-regex
54
+ const FORBIDDEN_HEADER_CHARS = /[\x00-\x1f\x7f]/;
55
+ function assertHeaderValueSafe(name, value) {
56
+ if (FORBIDDEN_HEADER_CHARS.test(value)) {
57
+ throw new Error(`google-genai files-API upload: header ${JSON.stringify(name)} ` +
58
+ `contains a control character (CR, LF, NUL, or other CTL byte), ` +
59
+ `which would let the value smuggle additional headers or be ` +
60
+ `rejected downstream with a less specific message.`);
61
+ }
62
+ }
63
+ /**
64
+ * Upload bytes to the Gemini Files API and return the file URI.
65
+ *
66
+ * The Files API is the dereferencable handle path for Gemini media:
67
+ * upload once, reference the returned `fileUri` on subsequent
68
+ * inference requests via a `fileData.fileUri` part (or a
69
+ * `MediaSource` of `kind: "file-reference"` whose `reference` is the
70
+ * URI). The trade-off relative to inline `base64` is bytes-on-the-wire:
71
+ * inline payloads ship with every request, file references ship
72
+ * once and are then quoted.
73
+ *
74
+ * The helper exclusively uses the "raw" upload protocol (one
75
+ * `POST` carries the full bytes). The resumable and multipart
76
+ * protocols are out of scope; large-file workflows that need them
77
+ * should compose against the Gemini SDK directly.
78
+ *
79
+ * @throws an Error when the upload returns non-2xx, when the
80
+ * response is not JSON, or when the response shape does not
81
+ * carry a non-empty `file.uri` + `file.mimeType`. The thrown
82
+ * error's message names the failure mode; HTTP errors include
83
+ * the status code and a snippet of the response body, and
84
+ * schema/JSON failures carry a snippet of the response body too.
85
+ */
86
+ export async function uploadGoogleGenAIFile(opts) {
87
+ const fetchImpl = opts.fetch ?? fetch;
88
+ const url = opts.uploadURL ?? FILES_API_UPLOAD_DEFAULT_URL;
89
+ assertHeaderValueSafe("Content-Type", opts.mimeType);
90
+ assertHeaderValueSafe("X-Goog-Upload-File-Name", opts.displayName);
91
+ assertHeaderValueSafe("x-goog-api-key", opts.apiKey);
92
+ // The `x-goog-api-key` header is intentionally lowercase to
93
+ // match the wire shape Google's Files API responds to (the
94
+ // captured `request-headers.json` records the same casing).
95
+ // HTTP header names are case-insensitive on the wire, so the
96
+ // mixed-case neighbors are functionally identical -- the choice
97
+ // is documentation, not behavior.
98
+ const headers = {
99
+ "Content-Type": opts.mimeType,
100
+ "X-Goog-Upload-Protocol": "raw",
101
+ "X-Goog-Upload-File-Name": opts.displayName,
102
+ "x-goog-api-key": opts.apiKey,
103
+ };
104
+ const init = {
105
+ method: "POST",
106
+ headers,
107
+ body: opts.bytes,
108
+ };
109
+ // `RequestInit.signal` is typed as `AbortSignal | null` under
110
+ // `exactOptionalPropertyTypes`; only attach the property when
111
+ // the caller actually supplied a signal so an absent `signal`
112
+ // does not become `undefined` on the init object (which the
113
+ // overload then rejects).
114
+ if (opts.signal !== undefined) {
115
+ init.signal = opts.signal;
116
+ }
117
+ const response = await fetchImpl(url, init);
118
+ // Read the body as text first, then parse JSON ourselves. This
119
+ // keeps a snippet of the actual server response available for
120
+ // both the JSON-parse and schema-mismatch error paths; reading
121
+ // through `response.json()` would consume the stream before the
122
+ // error site can sample it.
123
+ let body;
124
+ try {
125
+ body = await response.text();
126
+ }
127
+ catch (cause) {
128
+ const message = cause instanceof Error ? cause.message : String(cause);
129
+ throw new Error(`google-genai files-API upload: failed to read response body ` +
130
+ `(status ${String(response.status)} ${response.statusText}): ${message}`, { cause });
131
+ }
132
+ if (!response.ok) {
133
+ throw new Error(`google-genai files-API upload failed: ${String(response.status)} ` +
134
+ `${response.statusText}: ${body.slice(0, 500)}`);
135
+ }
136
+ let parsed;
137
+ try {
138
+ parsed = JSON.parse(body);
139
+ }
140
+ catch (cause) {
141
+ const message = cause instanceof Error ? cause.message : String(cause);
142
+ throw new Error(`google-genai files-API upload: response was not valid JSON ` +
143
+ `(${message}): ${body.slice(0, 500)}`, { cause });
144
+ }
145
+ const validated = FilesApiUploadResponse(parsed);
146
+ if (validated instanceof type.errors) {
147
+ throw new Error(`google-genai files-API upload: response did not match the expected ` +
148
+ `shape (missing file.uri/mimeType or a malformed file resource): ` +
149
+ `${validated.summary}; body: ${body.slice(0, 500)}`);
150
+ }
151
+ const { file } = validated;
152
+ // Normalize `sizeBytes` from `string | number | undefined` to
153
+ // `number | undefined`. The wire ships an integer as a string
154
+ // ("4193"); the helper rejects strings that are not exactly a
155
+ // parseable integer and numbers that are not integers (a size
156
+ // in bytes by definition is not fractional). `Number.parseInt`
157
+ // alone is permissive ("42abc" parses to 42) so the regex guard
158
+ // is what makes the contract honest. The final integer must
159
+ // also be non-negative (bytes count up from zero) and within
160
+ // JavaScript's safe-integer range -- Files API docs call this
161
+ // field int64, and a string like "9007199254740993" silently
162
+ // rounds when coerced to a JS number, so a precision check at
163
+ // the boundary keeps the returned value faithful to the wire.
164
+ function assertSafeNonNegativeInteger(n, raw) {
165
+ if (n < 0) {
166
+ throw new Error(`google-genai files-API upload: file.sizeBytes ` +
167
+ `${JSON.stringify(raw)} is negative; a byte count cannot be ` +
168
+ `less than zero.`);
169
+ }
170
+ if (n > Number.MAX_SAFE_INTEGER) {
171
+ throw new Error(`google-genai files-API upload: file.sizeBytes ` +
172
+ `${JSON.stringify(raw)} exceeds Number.MAX_SAFE_INTEGER and ` +
173
+ `cannot be represented as a JS number without precision loss.`);
174
+ }
175
+ }
176
+ let sizeBytes;
177
+ if (typeof file.sizeBytes === "string") {
178
+ if (!PARSEABLE_INTEGER.test(file.sizeBytes)) {
179
+ throw new Error(`google-genai files-API upload: file.sizeBytes ` +
180
+ `${JSON.stringify(file.sizeBytes)} is not a parseable integer.`);
181
+ }
182
+ const parsedSize = Number.parseInt(file.sizeBytes, 10);
183
+ assertSafeNonNegativeInteger(parsedSize, file.sizeBytes);
184
+ sizeBytes = parsedSize;
185
+ }
186
+ else if (typeof file.sizeBytes === "number") {
187
+ if (!Number.isInteger(file.sizeBytes)) {
188
+ throw new Error(`google-genai files-API upload: file.sizeBytes ` +
189
+ `${JSON.stringify(file.sizeBytes)} is not an integer.`);
190
+ }
191
+ assertSafeNonNegativeInteger(file.sizeBytes, file.sizeBytes);
192
+ sizeBytes = file.sizeBytes;
193
+ }
194
+ const result = {
195
+ fileUri: file.uri,
196
+ mimeType: file.mimeType,
197
+ };
198
+ if (sizeBytes !== undefined)
199
+ result.sizeBytes = sizeBytes;
200
+ if (file.name !== undefined)
201
+ result.name = file.name;
202
+ if (file.state !== undefined)
203
+ result.state = file.state;
204
+ return result;
205
+ }
@@ -0,0 +1,3 @@
1
+ import type { LastCycleSource } from "@intx/types/runtime";
2
+ import type { ProviderAdapter } from "../adapter.js";
3
+ export declare function createGoogleGenAIAdapter(source: LastCycleSource): ProviderAdapter;