zorya-classifier 0.0.0-stage → 0.1.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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +11 -0
  3. package/README.md +127 -2
  4. package/dist/app/cli-route.d.ts +3 -0
  5. package/dist/app/cli-route.js +40 -0
  6. package/dist/app/create-classifier.d.ts +14 -0
  7. package/dist/app/create-classifier.js +100 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +33 -0
  10. package/dist/entities/contracts.d.ts +395 -0
  11. package/dist/entities/contracts.js +90 -0
  12. package/dist/entities/errors.d.ts +7 -0
  13. package/dist/entities/errors.js +16 -0
  14. package/dist/features/choice-beam-layout.d.ts +18 -0
  15. package/dist/features/choice-beam-layout.js +136 -0
  16. package/dist/features/choice.d.ts +4 -0
  17. package/dist/features/choice.js +83 -0
  18. package/dist/features/prompts.d.ts +8 -0
  19. package/dist/features/prompts.js +56 -0
  20. package/dist/features/uniform-grid.d.ts +3 -0
  21. package/dist/features/uniform-grid.js +35 -0
  22. package/dist/index.d.ts +5 -0
  23. package/dist/index.js +2 -0
  24. package/dist/infrastructure/openrouter-common.d.ts +130 -0
  25. package/dist/infrastructure/openrouter-common.js +317 -0
  26. package/dist/infrastructure/openrouter-vision.d.ts +8 -0
  27. package/dist/infrastructure/openrouter-vision.js +12 -0
  28. package/dist/infrastructure/openrouter.d.ts +19 -0
  29. package/dist/infrastructure/openrouter.js +207 -0
  30. package/dist/infrastructure/raster.d.ts +2 -0
  31. package/dist/infrastructure/raster.js +121 -0
  32. package/dist/infrastructure/vision-chat.d.ts +64 -0
  33. package/dist/infrastructure/vision-chat.js +309 -0
  34. package/dist/openai-compatible.d.ts +5 -0
  35. package/dist/openai-compatible.js +7 -0
  36. package/dist/openrouter.d.ts +4 -0
  37. package/dist/openrouter.js +2 -0
  38. package/dist/processes/classify.d.ts +2 -0
  39. package/dist/processes/classify.js +21 -0
  40. package/dist/processes/locate.d.ts +2 -0
  41. package/dist/processes/locate.js +201 -0
  42. package/dist/shared/choice-mass.d.ts +2 -0
  43. package/dist/shared/choice-mass.js +19 -0
  44. package/dist/shared/gate.d.ts +11 -0
  45. package/dist/shared/gate.js +52 -0
  46. package/dist/shared/scope.d.ts +12 -0
  47. package/dist/shared/scope.js +29 -0
  48. package/docs/README.ru.md +245 -0
  49. package/docs/acceptance.md +31 -0
  50. package/docs/api.md +45 -0
  51. package/docs/architecture.md +32 -0
  52. package/docs/benchmarks.md +53 -0
  53. package/docs/evidence/current-linux-check.json +15 -0
  54. package/docs/evidence/decisions-completion-v1-manifest.json +96 -0
  55. package/docs/evidence/decisions-completion-v1-report.json +1205 -0
  56. package/docs/evidence/decisions-selection-v1-manifest.json +96 -0
  57. package/docs/evidence/decisions-selection-v1-report.json +1457 -0
  58. package/docs/evidence/jev-pricing-2026-10-18.json +69 -0
  59. package/docs/evidence/linux-check.json +20 -0
  60. package/docs/evidence/linux-check.txt +205 -0
  61. package/docs/evidence/live-release-smoke-manifest.json +82 -0
  62. package/docs/evidence/live-release-smoke-provenance.json +9 -0
  63. package/docs/evidence/live-release-smoke-report.json +2136 -0
  64. package/docs/evidence/live-v1-manifest.json +84 -0
  65. package/docs/evidence/live-v1-provenance.json +9 -0
  66. package/docs/evidence/live-v1-report.json +15555 -0
  67. package/docs/evidence/luna-decisions-fine-probe.json +19 -0
  68. package/docs/evidence/luna-decisions-locator-smoke-manifest.json +39 -0
  69. package/docs/evidence/luna-decisions-locator-smoke-report.json +149 -0
  70. package/docs/evidence/pack-check.json +26 -0
  71. package/docs/evidence/protocol-probes.json +61 -0
  72. package/docs/evidence/rejected-vision-selection-v1-qwen-photo-dev-01.json +42 -0
  73. package/docs/evidence/rejected-vision-selection-v1-qwen-photo-dev-17.json +42 -0
  74. package/docs/evidence/rejected-vision-selection-v1-qwen-photo-heldout-09.json +42 -0
  75. package/docs/evidence/rejected-vision-selection-v2-haiku-photo-dev-16.json +42 -0
  76. package/docs/evidence/rejected-vision-selection-v2-haiku-support-heldout-016.json +42 -0
  77. package/docs/evidence/vision-cost-reconciliation.json +37 -0
  78. package/docs/evidence/vision-model-summary.json +562 -0
  79. package/docs/evidence/vision-selection-v1-manifest.json +95 -0
  80. package/docs/evidence/vision-selection-v1-report.json +4598 -0
  81. package/docs/evidence/vision-selection-v2-manifest.json +95 -0
  82. package/docs/evidence/vision-selection-v2-report.json +1139 -0
  83. package/docs/evidence/windows-native.json +73 -0
  84. package/docs/evidence/windows-secrets.json +46 -0
  85. package/docs/operations.md +15 -0
  86. package/docs/plan.md +25 -0
  87. package/docs/provenance.json +32 -0
  88. package/docs/publishing.md +21 -0
  89. package/docs/results.md +69 -0
  90. package/docs/vision-results.md +51 -0
  91. package/package.json +89 -4
@@ -0,0 +1,19 @@
1
+ import type { DecisionPort } from '../entities/contracts.js';
2
+ export interface OpenRouterJevOptions {
3
+ apiKey: string;
4
+ fetch?: typeof globalThis.fetch;
5
+ maxRequestBytes?: number;
6
+ maxResponseBytes?: number;
7
+ }
8
+ export interface OpenRouterDecisionsOptions extends OpenRouterJevOptions {
9
+ model: string;
10
+ provider: string;
11
+ providerName?: string;
12
+ responseModelIds?: string[];
13
+ /** Only enable after confirming this wire quantization for the selected model. */
14
+ probabilityRoundingStep?: 0.01;
15
+ }
16
+ /** One pinned provider call; the caller owns the absolute deadline through its signal. */
17
+ export declare function createOpenRouterDecisions(input: OpenRouterDecisionsOptions): DecisionPort;
18
+ /** Optional historical integration; never an automatic fallback. */
19
+ export declare function createOpenRouterJev(input: OpenRouterJevOptions): DecisionPort;
@@ -0,0 +1,207 @@
1
+ import { z } from 'zod';
2
+ import { ClassifierError } from '../entities/errors.js';
3
+ import { acceptsChoiceMass } from '../shared/choice-mass.js';
4
+ import { active, validated, ownedError, invalid, preflightJson, requestSchema, validateImage, failure, readResponse, requestLimit, responseLimit, probability, key, } from './openrouter-common.js';
5
+ const endpoint = 'https://openrouter.ai/api/alpha/decisions';
6
+ const responseSchema = z
7
+ .object({
8
+ id: z.string().min(1).max(512).nullable().optional(),
9
+ model: z.string().min(1).max(128).nullable().optional(),
10
+ provider: z.string().min(1).max(128).nullable().optional(),
11
+ usage: z
12
+ .object({
13
+ input_tokens: z.number().int().nonnegative().optional(),
14
+ output_tokens: z.number().int().nonnegative().optional(),
15
+ prompt_tokens: z.number().int().nonnegative().optional(),
16
+ completion_tokens: z.number().int().nonnegative().optional(),
17
+ total_tokens: z.number().int().nonnegative().optional(),
18
+ cost: z.number().finite().nonnegative().nullable().optional(),
19
+ })
20
+ .strict()
21
+ .nullable()
22
+ .optional(),
23
+ answers: z.record(key, z.discriminatedUnion('type', [
24
+ z
25
+ .object({
26
+ type: z.literal('choice'),
27
+ choice: key,
28
+ probabilities: z.record(key, probability),
29
+ confidence: probability.optional(),
30
+ })
31
+ .strict(),
32
+ z.object({ type: z.literal('noul'), noul: probability }).strict(),
33
+ ])),
34
+ })
35
+ .strict();
36
+ const legacyOptionsSchema = z
37
+ .object({
38
+ apiKey: z
39
+ .string()
40
+ .min(1)
41
+ .max(4096)
42
+ .refine((value) => !/\s/u.test(value)),
43
+ fetch: z.custom((value) => typeof value === 'function').optional(),
44
+ maxRequestBytes: z.number().int().min(1).max(requestLimit).default(requestLimit),
45
+ maxResponseBytes: z.number().int().min(1).max(responseLimit).default(responseLimit),
46
+ })
47
+ .strict();
48
+ const identifier = z
49
+ .string()
50
+ .min(1)
51
+ .max(256)
52
+ .regex(/^[a-zA-Z0-9][a-zA-Z0-9._:/-]*$/u);
53
+ const optionsSchema = legacyOptionsSchema
54
+ .extend({
55
+ model: identifier,
56
+ provider: identifier,
57
+ providerName: z.string().min(1).max(256).optional(),
58
+ responseModelIds: z.array(identifier).max(8).optional(),
59
+ probabilityRoundingStep: z.literal(0.01).optional(),
60
+ })
61
+ .strict();
62
+ function routeName(value) {
63
+ return value.toLowerCase().replace(/[^a-z0-9]/gu, '');
64
+ }
65
+ function serialize(input, maxBytes, options) {
66
+ const envelope = {
67
+ model: options.model,
68
+ provider: { only: [options.provider], allow_fallbacks: false },
69
+ };
70
+ const snapshot = preflightJson(input, maxBytes - Buffer.byteLength(JSON.stringify(envelope)) + 1);
71
+ const request = validated(requestSchema, snapshot, 'INVALID_INPUT');
72
+ const questions = Object.fromEntries(Object.entries(request.questions).map(([id, question]) => [
73
+ id,
74
+ question.type === 'choice'
75
+ ? { type: question.type, instructions: question.instructions, criteria: question.criteria }
76
+ : question,
77
+ ]));
78
+ const body = JSON.stringify({ ...request, questions, ...envelope });
79
+ if (Buffer.byteLength(body) > maxBytes)
80
+ throw ownedError('RESOURCE_LIMIT', 'Decision request exceeds byte limit');
81
+ for (const part of request.state)
82
+ if (typeof part !== 'string')
83
+ validateImage(part.image_url.url);
84
+ return { request, body };
85
+ }
86
+ function acceptedModel(value, options) {
87
+ if (value === options.model || options.responseModelIds?.includes(value))
88
+ return true;
89
+ if (!value.startsWith(options.model + '-'))
90
+ return false;
91
+ const suffix = value.slice(options.model.length + 1);
92
+ if (!/^\d{4}-?\d{2}-?\d{2}$/u.test(suffix))
93
+ return false;
94
+ const digits = suffix.replaceAll('-', ''), date = `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6, 8)}`;
95
+ const parsed = new Date(date + 'T00:00:00.000Z');
96
+ return Number.isFinite(parsed.valueOf()) && parsed.toISOString().slice(0, 10) === date;
97
+ }
98
+ function result(raw, request, response, durationMs, options) {
99
+ let json;
100
+ try {
101
+ json = JSON.parse(raw);
102
+ }
103
+ catch {
104
+ throw invalid('INVALID_RESPONSE', 'Provider returned invalid JSON');
105
+ }
106
+ const value = validated(responseSchema, json, 'INVALID_RESPONSE');
107
+ if (value.model && !acceptedModel(value.model, options))
108
+ throw invalid('INVALID_RESPONSE', 'Provider returned an unexpected model');
109
+ if (value.provider &&
110
+ routeName(value.provider) !== routeName(options.providerName ?? options.provider))
111
+ throw invalid('INVALID_RESPONSE', 'Provider returned an unexpected route');
112
+ const questionIds = Object.keys(request.questions);
113
+ if (Object.keys(value.answers).length !== questionIds.length)
114
+ throw invalid('INVALID_RESPONSE', 'Provider returned mismatched questions');
115
+ const answers = { ...value.answers };
116
+ for (const id of questionIds) {
117
+ const question = request.questions[id], answer = value.answers[id];
118
+ if (!question || !answer || question.type !== answer.type)
119
+ throw invalid('INVALID_RESPONSE', 'Provider returned mismatched answer types');
120
+ if (question.type === 'choice' && answer.type === 'choice') {
121
+ const keys = Object.keys(question.criteria);
122
+ const scores = answer.probabilities;
123
+ if (Object.keys(scores).length !== keys.length ||
124
+ keys.some((option) => !Object.hasOwn(scores, option)) ||
125
+ !Object.hasOwn(question.criteria, answer.choice) ||
126
+ scores[answer.choice] !== Math.max(...Object.values(scores)) ||
127
+ !acceptsChoiceMass(Object.values(scores), options.probabilityRoundingStep))
128
+ throw invalid('INVALID_RESPONSE', 'Provider returned an invalid choice distribution');
129
+ // Explicitly configured wire quantization; retain raw scores and native ties.
130
+ if (!acceptsChoiceMass(Object.values(scores)))
131
+ answers[id] = { ...answer, probabilityRoundingStep: 0.01 };
132
+ }
133
+ }
134
+ const requestId = value.id ?? response.headers.get('x-generation-id');
135
+ if (requestId !== null && requestId.length > 512)
136
+ throw invalid('INVALID_RESPONSE', 'Provider returned an invalid request identity');
137
+ return {
138
+ answers,
139
+ metadata: {
140
+ model: value.model ?? null,
141
+ provider: value.provider ?? null,
142
+ requestId,
143
+ costUsd: value.usage?.cost ?? null,
144
+ durationMs,
145
+ attempts: 1,
146
+ },
147
+ };
148
+ }
149
+ /** One pinned provider call; the caller owns the absolute deadline through its signal. */
150
+ export function createOpenRouterDecisions(input) {
151
+ const options = validated(optionsSchema, input, 'INVALID_INPUT');
152
+ const fetcher = options.fetch ?? globalThis.fetch;
153
+ return {
154
+ async decide(input, signal) {
155
+ active(signal);
156
+ let prepared;
157
+ try {
158
+ prepared = serialize(input, options.maxRequestBytes, options);
159
+ }
160
+ catch (error) {
161
+ active(signal);
162
+ if (error instanceof ClassifierError)
163
+ throw error;
164
+ throw invalid('INVALID_INPUT', 'Decision input could not be serialized');
165
+ }
166
+ active(signal);
167
+ const started = performance.now();
168
+ let response;
169
+ try {
170
+ response = await fetcher(endpoint, {
171
+ method: 'POST',
172
+ headers: {
173
+ Authorization: `Bearer ${options.apiKey}`,
174
+ 'Content-Type': 'application/json',
175
+ },
176
+ body: prepared.body,
177
+ signal,
178
+ redirect: 'error',
179
+ });
180
+ }
181
+ catch {
182
+ active(signal);
183
+ throw ownedError('PROVIDER', 'OpenRouter transport failed');
184
+ }
185
+ try {
186
+ const raw = await readResponse(response, signal, options.maxResponseBytes);
187
+ const value = result(raw, prepared.request, response, Math.max(0, performance.now() - started), options);
188
+ active(signal);
189
+ return value;
190
+ }
191
+ catch (error) {
192
+ throw failure(error, signal);
193
+ }
194
+ },
195
+ };
196
+ }
197
+ /** Optional historical integration; never an automatic fallback. */
198
+ export function createOpenRouterJev(input) {
199
+ const { fetch: fetcher, ...options } = validated(legacyOptionsSchema, input, 'INVALID_INPUT');
200
+ return createOpenRouterDecisions({
201
+ ...options,
202
+ ...(fetcher ? { fetch: fetcher } : {}),
203
+ model: 'typesafe/jev-1.14',
204
+ provider: 'typesafe',
205
+ probabilityRoundingStep: 0.01,
206
+ });
207
+ }
@@ -0,0 +1,2 @@
1
+ import type { RasterPort } from '../entities/contracts.js';
2
+ export declare function createSharpRaster(): RasterPort;
@@ -0,0 +1,121 @@
1
+ import sharp from 'sharp';
2
+ import { limits } from '../entities/contracts.js';
3
+ import { ClassifierError, assertActive } from '../entities/errors.js';
4
+ function contains(outer, inner) {
5
+ return (inner.left >= outer.left &&
6
+ inner.top >= outer.top &&
7
+ inner.left + inner.width <= outer.left + outer.width &&
8
+ inner.top + inner.height <= outer.top + outer.height);
9
+ }
10
+ async function normalizeImage(bytes, signal) {
11
+ assertActive(signal);
12
+ const pipeline = sharp(bytes, { limitInputPixels: limits.imagePixels, failOn: 'error' });
13
+ try {
14
+ const metadata = await pipeline.metadata();
15
+ assertActive(signal);
16
+ if (!['png', 'jpeg', 'webp'].includes(metadata.format ?? '') || (metadata.pages ?? 1) !== 1)
17
+ throw new ClassifierError('INVALID_INPUT', 'Use a single PNG, JPEG or WebP image');
18
+ const normalized = await pipeline.rotate().png().toBuffer({ resolveWithObject: true });
19
+ assertActive(signal);
20
+ if (normalized.data.byteLength > limits.imageBytes)
21
+ throw new ClassifierError('RESOURCE_LIMIT', 'Decoded presentation exceeds image byte limit');
22
+ return { bytes: normalized.data, width: normalized.info.width, height: normalized.info.height };
23
+ }
24
+ catch (error) {
25
+ assertActive(signal);
26
+ if (error instanceof ClassifierError)
27
+ throw error;
28
+ throw new ClassifierError('INVALID_INPUT', 'Image cannot be decoded within pixel limits');
29
+ }
30
+ finally {
31
+ pipeline.destroy();
32
+ }
33
+ }
34
+ async function cropImage(image, region, signal, scale = 1) {
35
+ assertActive(signal);
36
+ if (!contains({ left: 0, top: 0, width: image.width, height: image.height }, region))
37
+ throw new ClassifierError('INVALID_INPUT', 'ROI lies outside oriented source image');
38
+ const pipeline = sharp(image.bytes, { limitInputPixels: limits.imagePixels })
39
+ .extract(region)
40
+ .resize(region.width * scale, region.height * scale, { kernel: 'nearest' });
41
+ try {
42
+ const data = await pipeline.png().toBuffer();
43
+ assertActive(signal);
44
+ return { bytes: data, width: region.width * scale, height: region.height * scale };
45
+ }
46
+ finally {
47
+ pipeline.destroy();
48
+ }
49
+ }
50
+ async function imageState(image, signal) {
51
+ assertActive(signal);
52
+ const scale = Math.min(1568 / Math.max(image.width, image.height), Math.max(1, 280 / Math.min(image.width, image.height)));
53
+ const width = Math.max(1, Math.round(image.width * scale)), height = Math.max(1, Math.round(image.height * scale));
54
+ const outWidth = Math.max(280, width), outHeight = Math.max(280, height);
55
+ const left = Math.floor((outWidth - width) / 2), top = Math.floor((outHeight - height) / 2);
56
+ const pipeline = sharp(image.bytes, { limitInputPixels: limits.imagePixels })
57
+ .resize(width, height, { kernel: 'nearest' })
58
+ .extend({
59
+ left,
60
+ top,
61
+ right: outWidth - width - left,
62
+ bottom: outHeight - height - top,
63
+ background: '#000000',
64
+ });
65
+ try {
66
+ const bytes = await pipeline.png().toBuffer();
67
+ assertActive(signal);
68
+ if (bytes.length > limits.imageBytes)
69
+ throw new ClassifierError('RESOURCE_LIMIT', 'Prepared image is too large');
70
+ const part = {
71
+ type: 'image_url',
72
+ image_url: { url: `data:image/png;base64,${bytes.toString('base64')}`, detail: 'high' },
73
+ };
74
+ return [
75
+ part,
76
+ JSON.stringify({
77
+ sourceImageTransform: {
78
+ source: { width: image.width, height: image.height },
79
+ output: { width: outWidth, height: outHeight },
80
+ content: { left, top, width, height },
81
+ scaleX: width / image.width,
82
+ scaleY: height / image.height,
83
+ },
84
+ }),
85
+ ];
86
+ }
87
+ finally {
88
+ pipeline.destroy();
89
+ }
90
+ }
91
+ async function renderGrid(image, region, cells, labels, signal) {
92
+ const crop = await cropImage(image, region, signal);
93
+ const scale = Math.min(1512 / Math.max(crop.width, crop.height), Math.max(1, 900 / Math.min(crop.width, crop.height)));
94
+ const width = Math.max(1, Math.round(crop.width * scale)), height = Math.max(1, Math.round(crop.height * scale));
95
+ const rectangles = cells
96
+ .map((cell) => {
97
+ const label = labels.get(cell.id);
98
+ if (!label || !/^[PC][0-9]{2,3}$/.test(label))
99
+ throw new ClassifierError('INVALID_INPUT', 'Invalid grid label');
100
+ const x = ((cell.region.left - region.left) * width) / region.width, y = ((cell.region.top - region.top) * height) / region.height;
101
+ const w = (cell.region.width * width) / region.width, h = (cell.region.height * height) / region.height;
102
+ const font = Math.max(3, Math.min(18, w / (label.length * 0.7), h / 2));
103
+ return `<rect x="${x}" y="${y}" width="${w}" height="${h}" fill="none" stroke="white" stroke-width="1"/><rect x="${x + 1}" y="${y + 1}" width="${Math.min(w - 1, font * label.length * 0.7)}" height="${font + 2}" fill="#111827" fill-opacity="0.78"/><text x="${x + 2}" y="${y + font}" font-family="DejaVu Sans,sans-serif" font-size="${font}" fill="white">${label}</text>`;
104
+ })
105
+ .join('');
106
+ const svg = Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">${rectangles}</svg>`);
107
+ const pipeline = sharp(crop.bytes, { limitInputPixels: limits.imagePixels })
108
+ .resize(width, height, { kernel: 'nearest' })
109
+ .composite([{ input: svg }]);
110
+ try {
111
+ const bytes = await pipeline.png().toBuffer();
112
+ assertActive(signal);
113
+ return { bytes, width, height };
114
+ }
115
+ finally {
116
+ pipeline.destroy();
117
+ }
118
+ }
119
+ export function createSharpRaster() {
120
+ return { normalize: normalizeImage, crop: cropImage, imageState, grid: renderGrid };
121
+ }
@@ -0,0 +1,64 @@
1
+ import { z } from 'zod';
2
+ import type { DecisionPort } from '../entities/contracts.js';
3
+ export declare const visionOptionsSchema: z.ZodObject<{
4
+ endpoint: z.ZodEffects<z.ZodString, string, string>;
5
+ headers: z.ZodOptional<z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodEffects<z.ZodString, string, string>>, Record<string, string>, Record<string, string>>>;
6
+ apiKey: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
7
+ model: z.ZodString;
8
+ provider: z.ZodOptional<z.ZodString>;
9
+ providerName: z.ZodOptional<z.ZodString>;
10
+ responseModelIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
11
+ responseFormat: z.ZodDefault<z.ZodEnum<["prompt", "json-object", "json-schema"]>>;
12
+ maxOutputTokens: z.ZodDefault<z.ZodNumber>;
13
+ reasoningEffort: z.ZodOptional<z.ZodEnum<["none", "minimal", "low", "medium", "high"]>>;
14
+ fetch: z.ZodOptional<z.ZodType<typeof fetch, z.ZodTypeDef, typeof fetch>>;
15
+ maxRequestBytes: z.ZodDefault<z.ZodNumber>;
16
+ maxResponseBytes: z.ZodDefault<z.ZodNumber>;
17
+ }, "strict", z.ZodTypeAny, {
18
+ model: string;
19
+ endpoint: string;
20
+ responseFormat: "prompt" | "json-object" | "json-schema";
21
+ maxOutputTokens: number;
22
+ maxRequestBytes: number;
23
+ maxResponseBytes: number;
24
+ provider?: string | undefined;
25
+ headers?: Record<string, string> | undefined;
26
+ apiKey?: string | undefined;
27
+ providerName?: string | undefined;
28
+ responseModelIds?: string[] | undefined;
29
+ reasoningEffort?: "high" | "none" | "minimal" | "low" | "medium" | undefined;
30
+ fetch?: typeof fetch | undefined;
31
+ }, {
32
+ model: string;
33
+ endpoint: string;
34
+ provider?: string | undefined;
35
+ headers?: Record<string, string> | undefined;
36
+ apiKey?: string | undefined;
37
+ providerName?: string | undefined;
38
+ responseModelIds?: string[] | undefined;
39
+ responseFormat?: "prompt" | "json-object" | "json-schema" | undefined;
40
+ maxOutputTokens?: number | undefined;
41
+ reasoningEffort?: "high" | "none" | "minimal" | "low" | "medium" | undefined;
42
+ fetch?: typeof fetch | undefined;
43
+ maxRequestBytes?: number | undefined;
44
+ maxResponseBytes?: number | undefined;
45
+ }>;
46
+ export interface VisionChatOptions {
47
+ endpoint: string;
48
+ headers?: Record<string, string>;
49
+ apiKey?: string;
50
+ model: string;
51
+ provider?: string;
52
+ /** Expected display name when it differs from the pinned provider slug. */
53
+ providerName?: string;
54
+ /** Explicit additional response snapshot identities; never a routing fallback. */
55
+ responseModelIds?: string[];
56
+ responseFormat?: 'prompt' | 'json-object' | 'json-schema';
57
+ maxOutputTokens?: number;
58
+ reasoningEffort?: 'none' | 'minimal' | 'low' | 'medium' | 'high';
59
+ fetch?: typeof globalThis.fetch;
60
+ maxRequestBytes?: number;
61
+ maxResponseBytes?: number;
62
+ }
63
+ /** Ordinary text/image chat completion, with no native classifier/Decisions requirement. */
64
+ export declare function createVisionChat(input: unknown): DecisionPort;