@simulatte/doppler 0.1.2 → 0.1.4

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.
@@ -0,0 +1,245 @@
1
+ import { DEFAULT_BATCHING_DEFAULTS, DEFAULT_GENERATION_CONFIG } from './schema/inference-defaults.schema.js';
2
+ import { DEFAULT_KVCACHE_CONFIG } from './schema/kvcache.schema.js';
3
+
4
+ const KV_LAYOUTS = new Set(['contiguous', 'paged', 'tiered', 'bdpa']);
5
+ const PHASES = new Set(['prefill', 'decode', 'both']);
6
+ const COLD_QUANT_MODES = new Set(['none', 'int8', 'int4']);
7
+ const ATTENTION_OPS = new Set(['attention']);
8
+ const EMBED_OPS = new Set(['embed', 'gather']);
9
+ const SAMPLE_OPS = new Set(['sample']);
10
+ const BDPA_MAX_HEAD_DIM = 256;
11
+ const BDPA_MAX_KV_LEN = 2048;
12
+ const TIERED_MAX_QUANT_HEAD_DIM = 256;
13
+
14
+ function isPlainObject(value) {
15
+ return value != null && typeof value === 'object' && !Array.isArray(value);
16
+ }
17
+
18
+ function assertManifestObject(manifest) {
19
+ if (!isPlainObject(manifest)) {
20
+ throw new Error('execution contract: manifest must be an object.');
21
+ }
22
+ return manifest;
23
+ }
24
+
25
+ function assertPositiveInteger(value, label) {
26
+ if (!Number.isInteger(value) || value < 0) {
27
+ throw new Error(`execution contract: ${label} must be a non-negative integer.`);
28
+ }
29
+ return value;
30
+ }
31
+
32
+ function assertPositiveIntegerOrDefault(value, fallback, label) {
33
+ if (value == null) {
34
+ return assertPositiveInteger(fallback, `${label} fallback`);
35
+ }
36
+ return assertPositiveInteger(value, label);
37
+ }
38
+
39
+ function normalizeKVLayout(value) {
40
+ const normalized = String(value ?? DEFAULT_KVCACHE_CONFIG.layout).trim().toLowerCase();
41
+ if (!KV_LAYOUTS.has(normalized)) {
42
+ throw new Error(
43
+ `execution contract: unsupported KV layout "${value}". ` +
44
+ `Expected one of ${[...KV_LAYOUTS].join(', ')}.`
45
+ );
46
+ }
47
+ return normalized;
48
+ }
49
+
50
+ function normalizePhase(value, label) {
51
+ const normalized = String(value ?? '').trim().toLowerCase();
52
+ if (!PHASES.has(normalized)) {
53
+ throw new Error(
54
+ `execution contract: ${label} must be one of ${[...PHASES].join(', ')}.`
55
+ );
56
+ }
57
+ return normalized;
58
+ }
59
+
60
+ function normalizeColdQuantMode(kvcache) {
61
+ const tieringMode = String(
62
+ kvcache?.tiering?.mode
63
+ ?? DEFAULT_KVCACHE_CONFIG.tiering.mode
64
+ ).trim().toLowerCase();
65
+ if (tieringMode === 'off' || tieringMode === 'fp16') {
66
+ return 'none';
67
+ }
68
+ if (!COLD_QUANT_MODES.has(tieringMode)) {
69
+ throw new Error(
70
+ `execution contract: unsupported tiered cold quant mode "${tieringMode}".`
71
+ );
72
+ }
73
+ return tieringMode;
74
+ }
75
+
76
+ function classifyOp(op) {
77
+ const normalized = String(op ?? '').trim().toLowerCase();
78
+ if (!normalized) {
79
+ throw new Error('execution contract: execution step op is required.');
80
+ }
81
+ if (ATTENTION_OPS.has(normalized)) return 'attention';
82
+ if (EMBED_OPS.has(normalized)) return 'embed';
83
+ if (SAMPLE_OPS.has(normalized)) return 'sample';
84
+ if (normalized.includes('norm')) return 'norm';
85
+ if (normalized.includes('residual')) return 'residual';
86
+ if (normalized.endsWith('_proj') || normalized.startsWith('rope_') || normalized === 'activation') {
87
+ return 'projection';
88
+ }
89
+ return 'other';
90
+ }
91
+
92
+ export function sanitizeLeanModuleName(value) {
93
+ const raw = String(value ?? 'GeneratedExecutionContractCheck').trim();
94
+ const alnum = raw.replace(/[^A-Za-z0-9_]/g, '_');
95
+ if (!alnum) {
96
+ return 'GeneratedExecutionContractCheck';
97
+ }
98
+ if (/^[A-Za-z_]/.test(alnum)) {
99
+ return alnum;
100
+ }
101
+ return `Generated_${alnum}`;
102
+ }
103
+
104
+ export function extractExecutionContractFacts(manifest) {
105
+ const normalizedManifest = assertManifestObject(manifest);
106
+ const modelId = String(normalizedManifest.modelId ?? 'model').trim() || 'model';
107
+ const architecture = isPlainObject(normalizedManifest.architecture)
108
+ ? normalizedManifest.architecture
109
+ : {};
110
+ const inference = isPlainObject(normalizedManifest.inference)
111
+ ? normalizedManifest.inference
112
+ : {};
113
+ const sessionDefaults = isPlainObject(inference.sessionDefaults)
114
+ ? inference.sessionDefaults
115
+ : {};
116
+ const execution = isPlainObject(inference.execution)
117
+ ? inference.execution
118
+ : {};
119
+ const kvcache = isPlainObject(sessionDefaults.kvcache)
120
+ ? sessionDefaults.kvcache
121
+ : {};
122
+ const decodeLoop = isPlainObject(sessionDefaults.decodeLoop)
123
+ ? sessionDefaults.decodeLoop
124
+ : {};
125
+
126
+ const steps = Array.isArray(execution.steps)
127
+ ? execution.steps.map((step, index) => {
128
+ if (!isPlainObject(step)) {
129
+ throw new Error(`execution contract: execution.steps[${index}] must be an object.`);
130
+ }
131
+ const id = String(step.id ?? '').trim();
132
+ if (!id) {
133
+ throw new Error(`execution contract: execution.steps[${index}].id is required.`);
134
+ }
135
+ return {
136
+ id,
137
+ phase: normalizePhase(step.phase, `execution.steps[${index}].phase`),
138
+ opClass: classifyOp(step.op),
139
+ };
140
+ })
141
+ : [];
142
+
143
+ return {
144
+ modelId,
145
+ session: {
146
+ layout: normalizeKVLayout(kvcache.layout),
147
+ disableCommandBatching: decodeLoop.disableCommandBatching
148
+ ?? DEFAULT_GENERATION_CONFIG.disableCommandBatching,
149
+ decodeBatchSize: assertPositiveIntegerOrDefault(
150
+ decodeLoop.batchSize,
151
+ DEFAULT_BATCHING_DEFAULTS.batchSize,
152
+ 'sessionDefaults.decodeLoop.batchSize'
153
+ ),
154
+ headDim: assertPositiveInteger(architecture.headDim, 'architecture.headDim'),
155
+ kvLen: assertPositiveIntegerOrDefault(
156
+ architecture.maxSeqLen ?? kvcache.maxSeqLen,
157
+ DEFAULT_KVCACHE_CONFIG.maxSeqLen,
158
+ 'architecture.maxSeqLen'
159
+ ),
160
+ coldQuantMode: normalizeColdQuantMode(kvcache),
161
+ },
162
+ steps,
163
+ };
164
+ }
165
+
166
+ export function validateExecutionContractFacts(facts) {
167
+ const errors = [];
168
+ const checks = [];
169
+ const modelId = String(facts?.modelId ?? 'model');
170
+ const session = facts?.session ?? {};
171
+ const steps = Array.isArray(facts?.steps) ? facts.steps : [];
172
+
173
+ const incompatibleStep = session.layout === 'bdpa'
174
+ ? steps.find((step) => step.opClass === 'attention' && (step.phase === 'prefill' || step.phase === 'both'))
175
+ : null;
176
+ if (incompatibleStep) {
177
+ errors.push(
178
+ `[ExecutionContract] sessionDefaults.kvcache.layout="bdpa" is decode-only, ` +
179
+ `but step "${incompatibleStep.id}" declares ${incompatibleStep.phase} attention.`
180
+ );
181
+ }
182
+ checks.push({
183
+ id: `${modelId}.steps`,
184
+ ok: incompatibleStep == null,
185
+ });
186
+
187
+ const sessionErrorCount = errors.length;
188
+ if (session.layout === 'bdpa') {
189
+ if (session.disableCommandBatching !== true) {
190
+ errors.push(
191
+ '[ExecutionContract] sessionDefaults.kvcache.layout="bdpa" requires ' +
192
+ 'sessionDefaults.decodeLoop.disableCommandBatching=true.'
193
+ );
194
+ }
195
+ if (session.decodeBatchSize > 1) {
196
+ errors.push(
197
+ `[ExecutionContract] sessionDefaults.kvcache.layout="bdpa" requires ` +
198
+ `sessionDefaults.decodeLoop.batchSize <= 1; got ${session.decodeBatchSize}.`
199
+ );
200
+ }
201
+ if (session.headDim > BDPA_MAX_HEAD_DIM) {
202
+ errors.push(
203
+ `[ExecutionContract] sessionDefaults.kvcache.layout="bdpa" requires architecture.headDim <= ${BDPA_MAX_HEAD_DIM}; ` +
204
+ `got ${session.headDim}.`
205
+ );
206
+ }
207
+ if (session.kvLen > BDPA_MAX_KV_LEN) {
208
+ errors.push(
209
+ `[ExecutionContract] sessionDefaults.kvcache.layout="bdpa" requires architecture.maxSeqLen <= ${BDPA_MAX_KV_LEN}; ` +
210
+ `got ${session.kvLen}.`
211
+ );
212
+ }
213
+ }
214
+
215
+ if (
216
+ session.layout === 'tiered'
217
+ && session.coldQuantMode !== 'none'
218
+ && session.headDim > TIERED_MAX_QUANT_HEAD_DIM
219
+ ) {
220
+ errors.push(
221
+ `[ExecutionContract] sessionDefaults.kvcache.layout="tiered" with cold quantization requires ` +
222
+ `architecture.headDim <= ${TIERED_MAX_QUANT_HEAD_DIM}; got ${session.headDim}.`
223
+ );
224
+ }
225
+
226
+ checks.push({
227
+ id: `${modelId}.session`,
228
+ ok: errors.length === sessionErrorCount,
229
+ });
230
+
231
+ return {
232
+ ok: errors.length === 0,
233
+ errors,
234
+ checks,
235
+ };
236
+ }
237
+
238
+ export function validateManifestExecutionContract(manifest) {
239
+ const facts = extractExecutionContractFacts(manifest);
240
+ const evaluation = validateExecutionContractFacts(facts);
241
+ return {
242
+ ...evaluation,
243
+ facts,
244
+ };
245
+ }
@@ -14,6 +14,7 @@ const gemma3Preset = await loadJson('./presets/models/gemma3.json', import.meta.
14
14
  const translateGemmaPreset = await loadJson('./presets/models/translategemma.json', import.meta.url, 'Failed to load preset');
15
15
  const embeddingGemmaPreset = await loadJson('./presets/models/embeddinggemma.json', import.meta.url, 'Failed to load preset');
16
16
  const functiongemmaPreset = await loadJson('./presets/models/functiongemma.json', import.meta.url, 'Failed to load preset');
17
+ const janusTextPreset = await loadJson('./presets/models/janus-text.json', import.meta.url, 'Failed to load preset');
17
18
  const llama3Preset = await loadJson('./presets/models/llama3.json', import.meta.url, 'Failed to load preset');
18
19
  const mixtralPreset = await loadJson('./presets/models/mixtral.json', import.meta.url, 'Failed to load preset');
19
20
  const deepseekPreset = await loadJson('./presets/models/deepseek.json', import.meta.url, 'Failed to load preset');
@@ -36,6 +37,7 @@ export const PRESET_REGISTRY = {
36
37
  translategemma: translateGemmaPreset,
37
38
  embeddinggemma: embeddingGemmaPreset,
38
39
  functiongemma: functiongemmaPreset,
40
+ janus_text: janusTextPreset,
39
41
  llama3: llama3Preset,
40
42
  mixtral: mixtralPreset,
41
43
  deepseek: deepseekPreset,
@@ -85,6 +87,7 @@ export const PRESET_DETECTION_ORDER = [
85
87
  // Most specific first (model variants)
86
88
  'functiongemma',
87
89
  'embeddinggemma',
90
+ 'janus_text',
88
91
  'modernbert',
89
92
  'diffusion',
90
93
  // Model families (check more specific patterns first)
@@ -107,8 +110,9 @@ export function detectPreset(
107
110
  config,
108
111
  architecture
109
112
  ) {
113
+ const nestedTextConfig = getNestedTextConfig(config);
110
114
  const archLower = (architecture || '').toLowerCase();
111
- const modelType = (config.model_type || '').toLowerCase();
115
+ const modelType = String(nestedTextConfig?.model_type ?? config.model_type ?? '').toLowerCase();
112
116
  // Weak hint case: architecture fallback is often model_type copy (e.g. qwen2).
113
117
  const hintsAreWeak = !archLower || !modelType || archLower === modelType;
114
118
 
@@ -317,41 +321,56 @@ function assertArchitecture(manifest, architecture) {
317
321
  // =============================================================================
318
322
 
319
323
  function extractArchitectureFromConfig(config) {
324
+ const nestedTextConfig = getNestedTextConfig(config);
320
325
  return {
321
- numLayers: config.num_hidden_layers ?? config.n_layer ?? config.blockCount,
322
- hiddenSize: config.hidden_size ?? config.n_embd ?? config.embeddingLength,
323
- intermediateSize: config.intermediate_size ?? config.n_inner ?? config.feedForwardLength,
324
- numAttentionHeads: config.num_attention_heads ?? config.n_head ?? config.attentionHeadCount,
325
- numKeyValueHeads: config.num_key_value_heads ?? config.attentionHeadCountKV,
326
- headDim: config.head_dim,
327
- vocabSize: config.vocab_size ?? config.vocabSize,
328
- maxSeqLen: config.max_position_embeddings ?? config.n_positions ?? config.contextLength,
329
- ropeTheta: config.rope_theta ?? config.ropeFreqBase,
330
- rmsNormEps: config.rms_norm_eps ?? config.attentionLayerNormRMSEpsilon,
326
+ numLayers: config.num_hidden_layers ?? nestedTextConfig?.num_hidden_layers ?? config.n_layer ?? config.blockCount,
327
+ hiddenSize: config.hidden_size ?? nestedTextConfig?.hidden_size ?? config.n_embd ?? config.embeddingLength,
328
+ intermediateSize: config.intermediate_size ?? nestedTextConfig?.intermediate_size ?? config.n_inner ?? config.feedForwardLength,
329
+ numAttentionHeads: config.num_attention_heads ?? nestedTextConfig?.num_attention_heads ?? config.n_head ?? config.attentionHeadCount,
330
+ numKeyValueHeads: config.num_key_value_heads ?? nestedTextConfig?.num_key_value_heads ?? config.attentionHeadCountKV,
331
+ headDim: config.head_dim ?? nestedTextConfig?.head_dim,
332
+ vocabSize: config.vocab_size ?? nestedTextConfig?.vocab_size ?? config.vocabSize,
333
+ maxSeqLen: config.max_position_embeddings ?? nestedTextConfig?.max_position_embeddings ?? config.n_positions ?? config.contextLength,
334
+ ropeTheta: config.rope_theta ?? nestedTextConfig?.rope_theta ?? config.ropeFreqBase,
335
+ rmsNormEps: config.rms_norm_eps ?? nestedTextConfig?.rms_norm_eps ?? config.attentionLayerNormRMSEpsilon,
331
336
  };
332
337
  }
333
338
 
334
339
  function extractInferenceFromConfig(config) {
340
+ const nestedTextConfig = getNestedTextConfig(config);
335
341
  return {
336
342
  attention: {
337
- slidingWindow: config.sliding_window,
338
- attnLogitSoftcapping: config.attn_logit_softcapping,
339
- attentionOutputGate: config.attn_output_gate,
343
+ slidingWindow: config.sliding_window ?? nestedTextConfig?.sliding_window,
344
+ attnLogitSoftcapping: config.attn_logit_softcapping ?? nestedTextConfig?.attn_logit_softcapping,
345
+ attentionOutputGate: config.attn_output_gate ?? nestedTextConfig?.attn_output_gate,
340
346
  },
341
347
  output: {
342
- finalLogitSoftcapping: config.final_logit_softcapping,
343
- tieWordEmbeddings: config.tie_word_embeddings,
344
- scaleEmbeddings: config.scale_embeddings,
348
+ finalLogitSoftcapping: config.final_logit_softcapping ?? nestedTextConfig?.final_logit_softcapping,
349
+ tieWordEmbeddings: config.tie_word_embeddings ?? nestedTextConfig?.tie_word_embeddings,
350
+ scaleEmbeddings: config.scale_embeddings ?? nestedTextConfig?.scale_embeddings,
345
351
  },
346
- pipeline: config.pipeline,
352
+ pipeline: config.pipeline ?? nestedTextConfig?.pipeline,
347
353
  rope: {
348
- ropeTheta: config.rope_theta ?? config.ropeFreqBase,
349
- ropeScalingType: config.rope_scaling_type,
350
- ropeScalingFactor: config.rope_scaling_factor,
354
+ ropeTheta: config.rope_theta ?? nestedTextConfig?.rope_theta ?? config.ropeFreqBase,
355
+ ropeScalingType: config.rope_scaling_type ?? nestedTextConfig?.rope_scaling_type,
356
+ ropeScalingFactor: config.rope_scaling_factor ?? nestedTextConfig?.rope_scaling_factor,
351
357
  },
352
358
  };
353
359
  }
354
360
 
361
+ function getNestedTextConfig(config) {
362
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
363
+ return null;
364
+ }
365
+ if (config.text_config && typeof config.text_config === 'object' && !Array.isArray(config.text_config)) {
366
+ return config.text_config;
367
+ }
368
+ if (config.language_config && typeof config.language_config === 'object' && !Array.isArray(config.language_config)) {
369
+ return config.language_config;
370
+ }
371
+ return null;
372
+ }
373
+
355
374
  function extractTokenizerFromManifest(manifest) {
356
375
  if (!manifest.tokenizer) return {};
357
376
 
@@ -0,0 +1,25 @@
1
+ {
2
+ "id": "janus_text",
3
+ "name": "Janus Text Core",
4
+ "extends": "transformer",
5
+ "modelType": "transformer",
6
+
7
+ "inference": {
8
+ "normalization": {
9
+ "rmsNormEps": 1e-6
10
+ },
11
+ "rope": {
12
+ "ropeTheta": 10000
13
+ }
14
+ },
15
+
16
+ "tokenizer": {
17
+ "addBosToken": true,
18
+ "addEosToken": false
19
+ },
20
+
21
+ "detection": {
22
+ "architecturePatterns": ["JanusTextForCausalLM"],
23
+ "modelTypePatterns": ["janus_text"]
24
+ }
25
+ }
@@ -76,7 +76,8 @@ function resolveTokenizerField(tokenizerConfig, ...keys) {
76
76
  }
77
77
 
78
78
  function resolveTokenizerVocabSize(tokenizerConfig, rawConfig, architecture) {
79
- const configVocab = rawConfig?.vocab_size ?? rawConfig?.text_config?.vocab_size;
79
+ const nestedTextConfig = getNestedTextConfig(rawConfig);
80
+ const configVocab = rawConfig?.vocab_size ?? nestedTextConfig?.vocab_size;
80
81
  const tokenizerVocab = tokenizerConfig?.vocab_size ?? tokenizerConfig?.vocabSize;
81
82
  const archVocab = architecture?.vocabSize;
82
83
  return tokenizerVocab ?? configVocab ?? archVocab ?? null;
@@ -223,21 +224,22 @@ function toFloat32ForQ4K(tensorData, sourceDtype, tensorName) {
223
224
 
224
225
  function resolveConfigTokenId(rawConfig, key) {
225
226
  const direct = rawConfig?.[key];
226
- const nested = rawConfig?.text_config?.[key];
227
+ const nested = getNestedTextConfig(rawConfig)?.[key];
227
228
  return resolveTokenizerId(direct ?? nested);
228
229
  }
229
230
 
230
231
  function resolveConfigTokenIds(rawConfig, key) {
231
232
  const direct = rawConfig?.[key];
232
- const nested = rawConfig?.text_config?.[key];
233
+ const nested = getNestedTextConfig(rawConfig)?.[key];
233
234
  return resolveTokenizerIds(direct ?? nested);
234
235
  }
235
236
 
236
237
  function resolveMoEConfigNumber(rawConfig, ...keys) {
238
+ const nestedTextConfig = getNestedTextConfig(rawConfig);
237
239
  for (const key of keys) {
238
240
  const direct = rawConfig?.[key];
239
241
  if (Number.isFinite(direct) && direct > 0) return Number(direct);
240
- const nested = rawConfig?.text_config?.[key];
242
+ const nested = nestedTextConfig?.[key];
241
243
  if (Number.isFinite(nested) && nested > 0) return Number(nested);
242
244
  }
243
245
  return null;
@@ -317,7 +319,7 @@ function resolveIntermediateSizeFromTensors(architecture, model, tensorLocations
317
319
  if (typeof current !== 'number' || !Number.isFinite(current) || current <= 0) {
318
320
  return architecture;
319
321
  }
320
- const modelType = String(rawConfig?.model_type ?? rawConfig?.text_config?.model_type ?? '').toLowerCase();
322
+ const modelType = String(rawConfig?.model_type ?? getNestedTextConfig(rawConfig)?.model_type ?? '').toLowerCase();
321
323
  if (modelType !== 'lfm2') {
322
324
  return architecture;
323
325
  }
@@ -359,7 +361,7 @@ function resolveMoEExpertFormat(rawConfig, resolvedModelType, quantizationInfo,
359
361
  const modelType = String(
360
362
  resolvedModelType ??
361
363
  rawConfig?.model_type ??
362
- rawConfig?.text_config?.model_type ??
364
+ getNestedTextConfig(rawConfig)?.model_type ??
363
365
  ''
364
366
  ).toLowerCase();
365
367
  if (modelType.includes('gpt_oss') || modelType.includes('gpt-oss') || modelType.includes('gptoss')) {
@@ -725,9 +727,7 @@ export function extractArchitecture(config, ggufConfig) {
725
727
 
726
728
  // Try HuggingFace config first
727
729
  if (config && Object.keys(config).length > 0) {
728
- const textConfig = (
729
- config.text_config && typeof config.text_config === 'object' && !Array.isArray(config.text_config)
730
- ) ? config.text_config : null;
730
+ const textConfig = getNestedTextConfig(config);
731
731
  const fromConfig = (...keys) => {
732
732
  const values = [];
733
733
  for (const key of keys) {
@@ -860,6 +860,19 @@ export function extractArchitecture(config, ggufConfig) {
860
860
  throw new Error('Missing model config: cannot extract architecture');
861
861
  }
862
862
 
863
+ function getNestedTextConfig(config) {
864
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
865
+ return null;
866
+ }
867
+ if (config.text_config && typeof config.text_config === 'object' && !Array.isArray(config.text_config)) {
868
+ return config.text_config;
869
+ }
870
+ if (config.language_config && typeof config.language_config === 'object' && !Array.isArray(config.language_config)) {
871
+ return config.language_config;
872
+ }
873
+ return null;
874
+ }
875
+
863
876
 
864
877
  export function buildTensorMap(tensors, shardSize) {
865
878
  if (!shardSize || shardSize <= 0) {
@@ -1,4 +1,5 @@
1
1
  export function resolveEosTokenId({ config, tokenizer, tokenizerJson }) {
2
+ const nestedTextConfig = getNestedTextConfig(config);
2
3
  const candidateSources = [
3
4
  tokenizer?.eosTokenId,
4
5
  tokenizer?.eos_token_id,
@@ -7,9 +8,9 @@ export function resolveEosTokenId({ config, tokenizer, tokenizerJson }) {
7
8
  tokenizerJson?.special_tokens?.eos,
8
9
  tokenizerJson?.special_tokens?.eos_token_id,
9
10
  config?.eos_token_id,
10
- config?.text_config?.eos_token_id,
11
+ nestedTextConfig?.eos_token_id,
11
12
  config?.eos_token_ids,
12
- config?.text_config?.eos_token_ids,
13
+ nestedTextConfig?.eos_token_ids,
13
14
  ];
14
15
 
15
16
  for (const candidate of candidateSources) {
@@ -23,7 +24,7 @@ export function resolveEosTokenId({ config, tokenizer, tokenizerJson }) {
23
24
  tokenizerJson?.specialTokens?.eos_token,
24
25
  tokenizerJson?.special_tokens?.eos_token,
25
26
  config?.eos_token,
26
- config?.text_config?.eos_token,
27
+ nestedTextConfig?.eos_token,
27
28
  ];
28
29
 
29
30
  for (const candidate of eosTokenStringCandidates) {
@@ -48,6 +49,19 @@ export function resolveEosTokenId({ config, tokenizer, tokenizerJson }) {
48
49
  throw new Error('Missing eos_token_id. Provide eos_token_id in config or tokenizer metadata.');
49
50
  }
50
51
 
52
+ function getNestedTextConfig(config) {
53
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
54
+ return null;
55
+ }
56
+ if (config.text_config && typeof config.text_config === 'object' && !Array.isArray(config.text_config)) {
57
+ return config.text_config;
58
+ }
59
+ if (config.language_config && typeof config.language_config === 'object' && !Array.isArray(config.language_config)) {
60
+ return config.language_config;
61
+ }
62
+ return null;
63
+ }
64
+
51
65
  function normalizeEosTokenId(value) {
52
66
  if (Array.isArray(value)) {
53
67
  if (value.length === 0 || value.some((id) => typeof id !== 'number')) {
@@ -1,4 +1,5 @@
1
1
  import { validateTensorConfigConsistency } from './tensor-config-validator.js';
2
+ import { validateManifestExecutionContract } from '../../config/execution-contract-check.js';
2
3
 
3
4
  export function validateManifest(manifest) {
4
5
  const errors = [];
@@ -196,5 +197,17 @@ export function validateManifest(manifest) {
196
197
  }
197
198
  }
198
199
 
200
+ if (!isDiffusion && !isEnergy && errors.length === 0) {
201
+ try {
202
+ const executionContract = validateManifestExecutionContract(manifest);
203
+ for (const error of executionContract.errors) {
204
+ errors.push(error);
205
+ }
206
+ } catch (error) {
207
+ const message = error instanceof Error ? error.message : String(error);
208
+ errors.push(`[ExecutionContract] ${message}`);
209
+ }
210
+ }
211
+
199
212
  return { valid: errors.length === 0, errors, warnings };
200
213
  }
@@ -1,4 +1,5 @@
1
1
  export declare const DOPPLER_VERSION: string;
2
+ export { doppler } from './client/doppler-api.js';
2
3
 
3
4
  export {
4
5
  DopplerLoader,
@@ -1,4 +1,5 @@
1
1
  export const DOPPLER_VERSION = '0.1.0';
2
+ export { doppler } from './client/doppler-api.js';
2
3
 
3
4
  // Core loaders
4
5
  export {
package/src/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export declare const DOPPLER_VERSION: string;
2
+ export { doppler } from './client/doppler-api.js';
2
3
 
3
4
  // Core loaders
4
5
  export {
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export const DOPPLER_VERSION = '0.1.0';
2
+ export { doppler } from './client/doppler-api.js';
2
3
 
3
4
  // Core loaders
4
5
  export {