tinker-agent 1.11.0 → 2.0.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ All notable user-facing changes to Tinker are documented here. The project follo
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [2.0.0] - 2026-08-20
9
+
10
+ ### Changed
11
+
12
+ - Estimate image input locally with deterministic size buckets instead of a
13
+ provider-side token-estimation request. Images are normalized for orientation
14
+ and provider limits before estimation and upload, keeping preflight accounting
15
+ aligned with the payload sent to the model.
16
+
17
+ ### Removed
18
+
19
+ - Remove the `tokenEstimator` model-profile setting. Existing image profiles must
20
+ delete that field; sessions created under the previous image policy remain
21
+ inspectable but cannot be resumed for execution.
22
+
8
23
  ## [1.11.0] - 2026-08-15
9
24
 
10
25
  ### Added
@@ -208,7 +223,8 @@ All notable user-facing changes to Tinker are documented here. The project follo
208
223
  - First formal npm release under the `tinker-agent` package name with the `tinker`
209
224
  executable.
210
225
 
211
- [Unreleased]: https://github.com/ishowshao/tinker/compare/v1.11.0...HEAD
226
+ [Unreleased]: https://github.com/ishowshao/tinker/compare/v2.0.0...HEAD
227
+ [2.0.0]: https://github.com/ishowshao/tinker/releases/tag/v2.0.0
212
228
  [1.11.0]: https://github.com/ishowshao/tinker/releases/tag/v1.11.0
213
229
  [1.10.1]: https://github.com/ishowshao/tinker/releases/tag/v1.10.1
214
230
  [1.9.0]: https://github.com/ishowshao/tinker/releases/tag/v1.9.0
package/README.md CHANGED
@@ -194,7 +194,6 @@ Profile fields:
194
194
  | `includeReasoningContent` | No | JSON boolean | `false` | No | Replay provider reasoning_content in Chat Completions history; ignored by Responses. |
195
195
  | `stream` | No | JSON boolean | `true` | No | Use streaming transport for the selected model API. |
196
196
  | `inputModalities` | No | Normalized modality array | `["text"]` | No | Accepted model input modalities; normalizes to ["text"] or ["text", "image"]. |
197
- | `tokenEstimator` | With image | Object | — | Yes | Independent token estimator required for image profiles. |
198
197
 
199
198
  `reasoning` fields:
200
199
 
@@ -205,17 +204,6 @@ Profile fields:
205
204
 
206
205
  The optional `reasoning` object declares provider-specific effort values. Efforts must be unique non-whitespace strings, `reset` is reserved by the TUI command, and `defaultEffort` must appear in `supportedEfforts`. Omitting `reasoning` sends no effort parameter and disables `/reasoning` for that profile.
207
206
 
208
- `tokenEstimator` fields:
209
-
210
- | Field | Type / constraint | Secret | Description |
211
- | --- | --- | --- | --- |
212
- | `kind` | Literal `"moonshot-estimate-token-count-v1"` | No | Estimator protocol discriminator. |
213
- | `model` | Non-empty string | No | Estimator model name. |
214
- | `apiBase` | Non-empty string | No | Estimator API base URL. |
215
- | `apiKey` | Non-empty string | Yes | Estimator API credential. |
216
- | `timeoutMs` | Integer 1000–60000 | No | Estimator request timeout in milliseconds. |
217
- | `maxRetries` | Literal `0` | No | Estimator retry count; retries are disabled. |
218
-
219
207
  Text-only profile example:
220
208
 
221
209
  ```json
@@ -271,15 +259,7 @@ Image-capable profile example:
271
259
  "inputModalities": [
272
260
  "text",
273
261
  "image"
274
- ],
275
- "tokenEstimator": {
276
- "kind": "moonshot-estimate-token-count-v1",
277
- "model": "example-token-estimator",
278
- "apiBase": "https://estimator.example.com/v1",
279
- "apiKey": "your-estimator-api-key",
280
- "timeoutMs": 30000,
281
- "maxRetries": 0
282
- }
262
+ ]
283
263
  }
284
264
  }
285
265
  }
@@ -370,7 +350,7 @@ configured profile.
370
350
  ### Image Input
371
351
 
372
352
  Image attachment is enabled only for a profile whose `inputModalities` explicitly
373
- includes `image` and which supplies a valid `tokenEstimator`. In the interactive
353
+ includes `image`. In the interactive
374
354
  TUI, type `@` and select a file that is inside the workspace and visible to the
375
355
  workspace search rules. One-shot commands, clipboard image bytes, remote URLs, and
376
356
  files outside or ignored by the workspace search are not supported.
@@ -378,7 +358,11 @@ files outside or ignored by the workspace search are not supported.
378
358
  Tinker accepts PNG (not APNG), JPEG, and static WebP. It rejects GIF, animated
379
359
  WebP, and other formats. A message and provider request may contain at most eight
380
360
  images; each image may be at most 20 MiB, 4096 pixels on either edge, and 8,847,360
381
- pixels in total. See the
361
+ pixels in total. Provider requests preserve smaller images and proportionally
362
+ downscale larger images to a maximum 2048-pixel long edge. Context planning uses
363
+ fixed local token buckets derived from the materialized dimensions and performs no
364
+ independent token-estimator request. See the
365
+ [`image token bucket design`](docs/image-token-bucket-estimation-design.md) and
382
366
  [`multimodal image input design`](docs/multimodal-image-input-design.md) for the
383
367
  complete fixed policy and persistence contract.
384
368
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tinker-agent",
3
- "version": "1.11.0",
3
+ "version": "2.0.0",
4
4
  "description": "A personal coding agent with an interactive TUI and one-shot CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -2,13 +2,8 @@ import type { ModelContextBudget } from "../model/model-context-profile";
2
2
  import type {
3
3
  ModelRequestOutput,
4
4
  ModelUsage,
5
- MaterializedModelRequest,
6
5
  PreparedModelRequest,
7
6
  } from "../model/model-client";
8
- import type {
9
- InputTokenEstimate,
10
- InputTokenEstimator,
11
- } from "../model/input-token-estimator";
12
7
  import {
13
8
  assertContextBudget,
14
9
  contextPressure,
@@ -68,7 +63,6 @@ export class ContextMeter {
68
63
  private anchor?: MeasuredContextAnchor;
69
64
  private lastProviderUsage?: ModelUsage;
70
65
  private calibrationIdentity?: string;
71
- private readonly providerEstimateCache = new Map<string, InputTokenEstimate>();
72
66
 
73
67
  constructor(
74
68
  private readonly budget: ModelContextBudget,
@@ -129,13 +123,14 @@ export class ContextMeter {
129
123
  let guardedDeltaTokens: number | undefined;
130
124
  if (anchor === undefined) {
131
125
  source = "estimated_full";
132
- usedInputTokens = Math.ceil(rawFullEstimate.totalTokens * correctionFactor);
126
+ usedInputTokens = guardedEstimate(rawFullEstimate, correctionFactor);
133
127
  } else {
134
128
  source = "measured_plus_estimated_delta";
135
- rawDeltaTokens = estimatePromptSegments(
129
+ const rawDelta = estimatePromptSegments(
136
130
  prepared.promptSegments.slice(anchor.segmentCount),
137
- ).totalTokens;
138
- guardedDeltaTokens = Math.ceil(rawDeltaTokens * correctionFactor);
131
+ );
132
+ rawDeltaTokens = rawDelta.totalTokens;
133
+ guardedDeltaTokens = guardedEstimate(rawDelta, correctionFactor);
139
134
  usedInputTokens = anchor.totalTokens + guardedDeltaTokens;
140
135
  }
141
136
 
@@ -217,62 +212,6 @@ export class ContextMeter {
217
212
  };
218
213
  }
219
214
 
220
- applyProviderEstimate(
221
- prepared: PreparedModelRequest,
222
- estimate: {
223
- inputTokens: number;
224
- coverage: "messages" | "full_request";
225
- },
226
- ): ContextUsageSnapshot {
227
- if (!Number.isSafeInteger(estimate.inputTokens) || estimate.inputTokens < 0) {
228
- throw new Error("Provider input estimate must be a non-negative safe integer.");
229
- }
230
- const local = this.measure(prepared);
231
- const measurement = this.measurements.get(prepared)!;
232
- const guardedTools =
233
- estimate.coverage === "messages"
234
- ? Math.ceil(
235
- measurement.rawFullEstimate.toolSchemaTokens * local.correctionFactor,
236
- )
237
- : 0;
238
- const providerGuarded = estimate.inputTokens + guardedTools;
239
- if (providerGuarded <= local.usedInputTokens) {
240
- return local;
241
- }
242
- const snapshot: ContextUsageSnapshot = {
243
- ...local,
244
- usedInputTokens: providerGuarded,
245
- source: "provider_estimated",
246
- pressure: contextPressure(providerGuarded, this.budget),
247
- };
248
- this.measurements.set(prepared, {
249
- rawFullEstimate: measurement.rawFullEstimate,
250
- snapshot,
251
- });
252
- return snapshot;
253
- }
254
-
255
- async estimateProviderInput(
256
- prepared: MaterializedModelRequest,
257
- estimator: InputTokenEstimator,
258
- options: { signal: AbortSignal },
259
- ): Promise<InputTokenEstimate> {
260
- options.signal.throwIfAborted();
261
- const key = providerEstimateCacheKey(
262
- prepared,
263
- estimator.compatibility.coverageVersion,
264
- );
265
- const cached = this.providerEstimateCache.get(key);
266
- if (cached !== undefined) {
267
- return cached;
268
- }
269
- const estimate = await estimator.estimate(prepared, options);
270
- options.signal.throwIfAborted();
271
- const frozen = Object.freeze({ ...estimate });
272
- this.providerEstimateCache.set(key, frozen);
273
- return frozen;
274
- }
275
-
276
215
  assertWithinBudget(snapshot: ContextUsageSnapshot): void {
277
216
  assertContextBudget({
278
217
  usedInputTokens: snapshot.usedInputTokens,
@@ -295,7 +234,6 @@ export class ContextMeter {
295
234
  this.lastProviderUsage = undefined;
296
235
  this.measurements = new WeakMap();
297
236
  this.calibration.clear();
298
- this.providerEstimateCache.clear();
299
237
  this.calibrationIdentity = nextIdentity;
300
238
  throw new Error(
301
239
  "Context revision changed the request configuration or tool schema.",
@@ -304,7 +242,6 @@ export class ContextMeter {
304
242
  this.anchor = undefined;
305
243
  this.lastProviderUsage = undefined;
306
244
  this.measurements = new WeakMap();
307
- this.providerEstimateCache.clear();
308
245
  this.calibrationIdentity = nextIdentity;
309
246
  }
310
247
 
@@ -315,7 +252,6 @@ export class ContextMeter {
315
252
  this.measurements = new WeakMap();
316
253
  this.calibration.clear();
317
254
  this.calibrationIdentity = undefined;
318
- this.providerEstimateCache.clear();
319
255
  }
320
256
 
321
257
  private usableAnchor(
@@ -330,10 +266,7 @@ export class ContextMeter {
330
266
  anchor.requestConfigHash !== prepared.requestConfigHash ||
331
267
  anchor.toolSchemaHash !== prepared.toolSchemaHash ||
332
268
  anchor.segmentCount > prepared.promptSegments.length ||
333
- prefixHashes[anchor.segmentCount] !== anchor.prefixHash ||
334
- prepared.promptSegments
335
- .slice(anchor.segmentCount)
336
- .some((segment) => (segment.media?.length ?? 0) > 0)
269
+ prefixHashes[anchor.segmentCount] !== anchor.prefixHash
337
270
  ) {
338
271
  this.anchor = undefined;
339
272
  return undefined;
@@ -359,18 +292,14 @@ export class ContextMeter {
359
292
  }
360
293
  }
361
294
 
362
- function providerEstimateCacheKey(
363
- prepared: PreparedModelRequest,
364
- coverageVersion: string,
365
- ): string {
366
- return [
367
- prepared.requestConfigHash,
368
- prepared.toolSchemaHash,
369
- lastPromptPrefixHash(
370
- promptPrefixHashes(prepared.requestConfigHash, prepared.promptSegments),
371
- ),
372
- coverageVersion,
373
- ].join(":");
295
+ function guardedEstimate(
296
+ breakdown: RawContextBreakdown,
297
+ correctionFactor: number,
298
+ ): number {
299
+ return (
300
+ Math.ceil(breakdown.textAndProtocolTokens * correctionFactor) +
301
+ breakdown.imageTokens
302
+ );
374
303
  }
375
304
 
376
305
  function assertMeasuredContextAnchor(anchor: MeasuredContextAnchor): void {
package/src/agent/loop.ts CHANGED
@@ -175,20 +175,7 @@ export async function runAgent(input: RunAgentInput): Promise<RunAgentResult> {
175
175
  assetStore: input.assetStore,
176
176
  signal: input.signal,
177
177
  });
178
- if (preflight.source === "measured_plus_estimated_delta") {
179
- preflight = input.contextMeter.measure(request);
180
- } else {
181
- const estimator = input.model.inputTokenEstimator;
182
- if (estimator === undefined) {
183
- throw new Error("Image model request has no input token estimator.");
184
- }
185
- const estimate = await input.contextMeter.estimateProviderInput(
186
- request as MaterializedModelRequest,
187
- estimator,
188
- { signal: input.signal },
189
- );
190
- preflight = input.contextMeter.applyProviderEstimate(request, estimate);
191
- }
178
+ preflight = input.contextMeter.measure(request);
192
179
  input.contextMeter.assertWithinBudget(preflight);
193
180
  await input.runtimeSession.append({
194
181
  type: "context.usage.updated",
@@ -581,11 +581,6 @@ class DefaultRuntimeSession implements RuntimeSession {
581
581
  contextProfile: input.contextProfile,
582
582
  messageProtocol: input.modelClient.messageProtocol,
583
583
  inputModalities: input.modelClient.inputModalities ?? ["text"],
584
- ...(input.modelClient.inputTokenEstimator === undefined
585
- ? {}
586
- : {
587
- tokenEstimator: input.modelClient.inputTokenEstimator.compatibility,
588
- }),
589
584
  });
590
585
  if (input.selection.mode === "resume") {
591
586
  store.assertSessionCompatibility(compatibility);
@@ -1641,24 +1636,6 @@ class DefaultRuntimeSession implements RuntimeSession {
1641
1636
  { assetStore: this.assetStore, signal: controller.signal },
1642
1637
  );
1643
1638
  admissionSnapshot = this.contextMeter.measure(admissionPrepared);
1644
- if (
1645
- prepared.mediaOccurrenceCount > 0 &&
1646
- admissionSnapshot.source !== "measured_plus_estimated_delta"
1647
- ) {
1648
- const estimator = this.input.modelClient.inputTokenEstimator;
1649
- if (estimator === undefined) {
1650
- throw new Error("Image model request has no input token estimator.");
1651
- }
1652
- const estimate = await this.contextMeter.estimateProviderInput(
1653
- admissionPrepared,
1654
- estimator,
1655
- { signal: controller.signal },
1656
- );
1657
- admissionSnapshot = this.contextMeter.applyProviderEstimate(
1658
- admissionPrepared,
1659
- estimate,
1660
- );
1661
- }
1662
1639
  this.contextMeter.assertWithinBudget(admissionSnapshot);
1663
1640
  controller.signal.throwIfAborted();
1664
1641
  const turn = this.stageTurn(input.userMessage);
package/src/cli/config.ts CHANGED
@@ -14,7 +14,6 @@ import {
14
14
  type ModelInputModality,
15
15
  type ModelProfile,
16
16
  type ModelProfiles,
17
- type ModelTokenEstimatorProfile,
18
17
  unknownProfileError,
19
18
  } from "./model-profiles";
20
19
  import type { MemoryEmbeddingConfig } from "../memory/contracts";
@@ -40,7 +39,6 @@ export type RunnerConfig = {
40
39
  readonly contextBudget: ModelContextBudget;
41
40
  readonly profileName?: string;
42
41
  readonly inputModalities: readonly ModelInputModality[];
43
- readonly tokenEstimator?: ModelTokenEstimatorProfile;
44
42
  readonly bashGuardMode: "guard" | "yolo";
45
43
  readonly bashGuardSource: "default" | "environment" | "cli";
46
44
  };
@@ -184,9 +182,6 @@ function runnerConfigTemplateFromProfile(
184
182
  inputModalities: profile.inputModalities,
185
183
  bashGuardMode: environment.bashGuardMode,
186
184
  bashGuardSource: environment.bashGuardSource,
187
- ...(profile.tokenEstimator === undefined
188
- ? {}
189
- : { tokenEstimator: profile.tokenEstimator }),
190
185
  });
191
186
  }
192
187
 
@@ -10,9 +10,6 @@ import {
10
10
  MODEL_PROFILE_FIELDS,
11
11
  MODEL_PROFILES_DOCUMENT_FIELDS,
12
12
  MODEL_REASONING_FIELDS,
13
- MODEL_TOKEN_ESTIMATOR_FIELDS,
14
- type ModelTokenEstimatorKind,
15
- type ModelTokenEstimatorMaxRetries,
16
13
  } from "./public-config-contract";
17
14
  import type { MemoryEmbeddingConfig } from "../memory/contracts";
18
15
  import type { ReasoningEffortConfig } from "../model/reasoning-effort";
@@ -29,20 +26,10 @@ export type ModelProfile = {
29
26
  readonly includeReasoningContent: boolean;
30
27
  readonly stream: boolean;
31
28
  readonly inputModalities: readonly ModelInputModality[];
32
- readonly tokenEstimator?: ModelTokenEstimatorProfile;
33
29
  };
34
30
 
35
31
  export type ModelInputModality = "text" | "image";
36
32
 
37
- export type ModelTokenEstimatorProfile = {
38
- readonly kind: ModelTokenEstimatorKind;
39
- readonly model: string;
40
- readonly apiBase: string;
41
- readonly apiKey: string;
42
- readonly timeoutMs: number;
43
- readonly maxRetries: ModelTokenEstimatorMaxRetries;
44
- };
45
-
46
33
  export type ModelProfiles = {
47
34
  readonly defaultProfile: string;
48
35
  readonly profiles: ReadonlyMap<string, ModelProfile>;
@@ -285,16 +272,6 @@ function parseProfile(
285
272
  value.inputModalities,
286
273
  `${where}: "inputModalities"`,
287
274
  );
288
- const tokenEstimator =
289
- value.tokenEstimator === undefined
290
- ? undefined
291
- : parseTokenEstimator(value.tokenEstimator, `${where}: "tokenEstimator"`);
292
- if (inputModalities.includes("image") && tokenEstimator === undefined) {
293
- throw new Error(
294
- `${where}: image input requires a complete "tokenEstimator" configuration.`,
295
- );
296
- }
297
-
298
275
  createModelContextProfile({
299
276
  contextWindowTokens,
300
277
  maxSupportedOutputTokens,
@@ -312,7 +289,6 @@ function parseProfile(
312
289
  includeReasoningContent,
313
290
  stream,
314
291
  inputModalities,
315
- ...(tokenEstimator === undefined ? {} : { tokenEstimator }),
316
292
  });
317
293
  }
318
294
 
@@ -434,54 +410,7 @@ function parseInputModalities(
434
410
  );
435
411
  }
436
412
 
437
- function parseTokenEstimator(value: unknown, name: string): ModelTokenEstimatorProfile {
438
- if (!isRecord(value)) {
439
- throw new Error(`${name} must be an object.`);
440
- }
441
- assertKnownKeys(
442
- value,
443
- MODEL_TOKEN_ESTIMATOR_FIELDS.map((field) => field.name),
444
- name,
445
- );
446
- const kindField = tokenEstimatorField("kind");
447
- if (value.kind !== kindField.literalValue) {
448
- throw new Error(`${name}.kind must be ${JSON.stringify(kindField.literalValue)}.`);
449
- }
450
- const model = parseTokenEstimatorString(value, "model", name);
451
- const apiBase = parseTokenEstimatorString(value, "apiBase", name);
452
- const apiKey = parseTokenEstimatorString(value, "apiKey", name);
453
- const timeoutField = tokenEstimatorField("timeoutMs");
454
- if (timeoutField.valueKind !== "positive-integer") {
455
- throw new Error("Token estimator timeoutMs contract kind is invalid.");
456
- }
457
- const timeoutMs = requirePositiveInteger(value.timeoutMs, `${name}.timeoutMs`);
458
- if (
459
- timeoutField.minimum === undefined ||
460
- timeoutField.maximum === undefined ||
461
- timeoutMs < timeoutField.minimum ||
462
- timeoutMs > timeoutField.maximum
463
- ) {
464
- throw new Error(
465
- `${name}.timeoutMs must be between ${timeoutField.minimum} and ${timeoutField.maximum}.`,
466
- );
467
- }
468
- const maxRetriesField = tokenEstimatorField("maxRetries");
469
- if (value.maxRetries !== maxRetriesField.literalValue) {
470
- throw new Error(`${name}.maxRetries must be ${maxRetriesField.literalValue}.`);
471
- }
472
- return Object.freeze({
473
- kind: kindField.literalValue,
474
- model,
475
- apiBase,
476
- apiKey,
477
- timeoutMs,
478
- maxRetries: maxRetriesField.literalValue,
479
- });
480
- }
481
-
482
413
  type ModelProfileFieldName = (typeof MODEL_PROFILE_FIELDS)[number]["name"];
483
- type ModelTokenEstimatorFieldName =
484
- (typeof MODEL_TOKEN_ESTIMATOR_FIELDS)[number]["name"];
485
414
 
486
415
  function modelProfileField<Name extends ModelProfileFieldName>(
487
416
  name: Name,
@@ -496,21 +425,6 @@ function modelProfileField<Name extends ModelProfileFieldName>(
496
425
  >;
497
426
  }
498
427
 
499
- function tokenEstimatorField<Name extends ModelTokenEstimatorFieldName>(
500
- name: Name,
501
- ): Extract<(typeof MODEL_TOKEN_ESTIMATOR_FIELDS)[number], { readonly name: Name }> {
502
- const field = MODEL_TOKEN_ESTIMATOR_FIELDS.find(
503
- (candidate) => candidate.name === name,
504
- );
505
- if (field === undefined) {
506
- throw new Error(`Missing token estimator field contract for ${name}.`);
507
- }
508
- return field as Extract<
509
- (typeof MODEL_TOKEN_ESTIMATOR_FIELDS)[number],
510
- { readonly name: Name }
511
- >;
512
- }
513
-
514
428
  function parseProfileString(
515
429
  value: Record<string, unknown>,
516
430
  name: "model" | "apiBase" | "apiKey",
@@ -555,18 +469,6 @@ function parseProfileBoolean(
555
469
  : parseBoolean(value[name], `${where}: ${JSON.stringify(name)}`);
556
470
  }
557
471
 
558
- function parseTokenEstimatorString(
559
- value: Record<string, unknown>,
560
- name: "model" | "apiBase" | "apiKey",
561
- where: string,
562
- ): string {
563
- const field = tokenEstimatorField(name);
564
- if (field.valueKind !== "non-empty-string") {
565
- throw new Error(`Token estimator field ${name} has an invalid contract kind.`);
566
- }
567
- return requireString(value[name], `${where}.${name}`);
568
- }
569
-
570
472
  function assertKnownKeys(
571
473
  value: Record<string, unknown>,
572
474
  allowed: readonly string[],
@@ -242,11 +242,7 @@ export const PUBLIC_CONFIG_FIELDS = Object.freeze([
242
242
 
243
243
  export type ModelProfileField = {
244
244
  readonly name: string;
245
- readonly valueKind:
246
- | PublicConfigValueKind
247
- | "input-modalities"
248
- | "reasoning"
249
- | "token-estimator";
245
+ readonly valueKind: PublicConfigValueKind | "input-modalities" | "reasoning";
250
246
  readonly required: boolean;
251
247
  readonly defaultValue?: string | number | boolean | readonly string[];
252
248
  readonly secret: boolean;
@@ -337,13 +333,6 @@ export const MODEL_PROFILE_FIELDS = Object.freeze([
337
333
  description:
338
334
  'Accepted model input modalities; normalizes to ["text"] or ["text", "image"].',
339
335
  }),
340
- profileField({
341
- name: "tokenEstimator",
342
- valueKind: "token-estimator",
343
- required: false,
344
- secret: true,
345
- description: "Independent token estimator required for image profiles.",
346
- }),
347
336
  ]);
348
337
 
349
338
  export type ModelReasoningField = {
@@ -375,82 +364,6 @@ export const MODEL_REASONING_FIELDS = Object.freeze([
375
364
  }),
376
365
  ]);
377
366
 
378
- export type ModelTokenEstimatorField = {
379
- readonly name: string;
380
- readonly valueKind: PublicConfigValueKind | "literal-string" | "literal-number";
381
- readonly required: true;
382
- readonly secret: boolean;
383
- readonly literalValue?: string | number;
384
- readonly minimum?: number;
385
- readonly maximum?: number;
386
- readonly description: string;
387
- };
388
-
389
- function estimatorField<const T extends ModelTokenEstimatorField>(
390
- field: T,
391
- ): Readonly<T> {
392
- return Object.freeze(field);
393
- }
394
-
395
- export const MODEL_TOKEN_ESTIMATOR_FIELDS = Object.freeze([
396
- estimatorField({
397
- name: "kind",
398
- valueKind: "literal-string",
399
- required: true,
400
- secret: false,
401
- literalValue: "moonshot-estimate-token-count-v1",
402
- description: "Estimator protocol discriminator.",
403
- }),
404
- estimatorField({
405
- name: "model",
406
- valueKind: "non-empty-string",
407
- required: true,
408
- secret: false,
409
- description: "Estimator model name.",
410
- }),
411
- estimatorField({
412
- name: "apiBase",
413
- valueKind: "non-empty-string",
414
- required: true,
415
- secret: false,
416
- description: "Estimator API base URL.",
417
- }),
418
- estimatorField({
419
- name: "apiKey",
420
- valueKind: "non-empty-string",
421
- required: true,
422
- secret: true,
423
- description: "Estimator API credential.",
424
- }),
425
- estimatorField({
426
- name: "timeoutMs",
427
- valueKind: "positive-integer",
428
- required: true,
429
- secret: false,
430
- minimum: 1_000,
431
- maximum: 60_000,
432
- description: "Estimator request timeout in milliseconds.",
433
- }),
434
- estimatorField({
435
- name: "maxRetries",
436
- valueKind: "literal-number",
437
- required: true,
438
- secret: false,
439
- literalValue: 0,
440
- description: "Estimator retry count; retries are disabled.",
441
- }),
442
- ]);
443
-
444
- export type ModelTokenEstimatorKind = Extract<
445
- (typeof MODEL_TOKEN_ESTIMATOR_FIELDS)[number],
446
- { readonly name: "kind" }
447
- >["literalValue"];
448
-
449
- export type ModelTokenEstimatorMaxRetries = Extract<
450
- (typeof MODEL_TOKEN_ESTIMATOR_FIELDS)[number],
451
- { readonly name: "maxRetries" }
452
- >["literalValue"];
453
-
454
367
  export type MemoryConfigField = {
455
368
  readonly name: "profile" | "embedding";
456
369
  readonly valueKind: "non-empty-string" | "embedding-profile";
@@ -66,7 +66,6 @@ export function createModelClient(
66
66
  | "apiKey"
67
67
  | "apiBase"
68
68
  | "inputModalities"
69
- | "tokenEstimator"
70
69
  >,
71
70
  env: NodeJS.ProcessEnv = process.env,
72
71
  reasoningEffort?: ReasoningEffortController,
@@ -82,9 +81,6 @@ export function createModelClient(
82
81
  ...(activeReasoningEffort === undefined
83
82
  ? {}
84
83
  : { reasoningEffort: activeReasoningEffort }),
85
- ...(config.tokenEstimator === undefined
86
- ? {}
87
- : { tokenEstimator: config.tokenEstimator }),
88
84
  ...(env.TINKER_TEST_FAKE_MODEL_REQUEST_LOG === undefined ||
89
85
  env.TINKER_TEST_FAKE_MODEL_REQUEST_LOG === ""
90
86
  ? {}
@@ -102,9 +98,6 @@ export function createModelClient(
102
98
  ...(activeReasoningEffort === undefined
103
99
  ? {}
104
100
  : { reasoningEffort: activeReasoningEffort }),
105
- ...(config.tokenEstimator === undefined
106
- ? {}
107
- : { tokenEstimator: config.tokenEstimator }),
108
101
  };
109
102
  if (config.api === "responses") {
110
103
  return new OpenAIResponsesModelClient(common);
@@ -47,9 +47,6 @@ export async function initializeTuiMemory(input: {
47
47
  stream: profile.stream,
48
48
  contextBudget: memoryConfig.contextBudget,
49
49
  inputModalities: profile.inputModalities,
50
- ...(profile.tokenEstimator === undefined
51
- ? {}
52
- : { tokenEstimator: profile.tokenEstimator }),
53
50
  },
54
51
  input.env,
55
52
  );