@cubicecho/agent-core 2.23.0 → 2.25.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 (71) hide show
  1. package/README.md +14 -9
  2. package/dist/agent-loop.d.ts +84 -204
  3. package/dist/agent-loop.js +416 -643
  4. package/dist/calibration.d.ts +14 -10
  5. package/dist/calibration.js +30 -22
  6. package/dist/capabilities.d.ts +140 -35
  7. package/dist/capabilities.js +345 -152
  8. package/dist/client.d.ts +104 -35
  9. package/dist/client.js +210 -78
  10. package/dist/compaction.d.ts +103 -60
  11. package/dist/compaction.js +163 -93
  12. package/dist/config.d.ts +48 -12
  13. package/dist/config.js +20 -1
  14. package/dist/continuation.d.ts +25 -17
  15. package/dist/continuation.js +70 -47
  16. package/dist/digest.d.ts +7 -0
  17. package/dist/digest.js +18 -0
  18. package/dist/errors.d.ts +24 -15
  19. package/dist/errors.js +23 -15
  20. package/dist/events.d.ts +108 -58
  21. package/dist/events.js +265 -128
  22. package/dist/guards.d.ts +53 -0
  23. package/dist/guards.js +55 -0
  24. package/dist/hook-events.d.ts +38 -0
  25. package/dist/hook-events.js +36 -0
  26. package/dist/hooks.d.ts +105 -83
  27. package/dist/hooks.js +224 -139
  28. package/dist/index.d.ts +30 -25
  29. package/dist/index.js +29 -23
  30. package/dist/ledger.d.ts +62 -30
  31. package/dist/ledger.js +163 -56
  32. package/dist/platform.d.ts +13 -0
  33. package/dist/platform.js +13 -0
  34. package/dist/preselect.d.ts +222 -0
  35. package/dist/preselect.js +290 -0
  36. package/dist/request-body.d.ts +32 -0
  37. package/dist/request-body.js +47 -0
  38. package/dist/reset.d.ts +1 -0
  39. package/dist/reset.js +7 -6
  40. package/dist/retry.d.ts +40 -143
  41. package/dist/retry.js +79 -285
  42. package/dist/run-calls.d.ts +123 -0
  43. package/dist/run-calls.js +351 -0
  44. package/dist/run-turn.d.ts +44 -16
  45. package/dist/run-turn.js +74 -37
  46. package/dist/runtime.d.ts +23 -17
  47. package/dist/runtime.js +34 -24
  48. package/dist/schema-compat.d.ts +51 -11
  49. package/dist/schema-compat.js +375 -193
  50. package/dist/scope.d.ts +28 -5
  51. package/dist/scope.js +37 -10
  52. package/dist/side-task.d.ts +127 -39
  53. package/dist/side-task.js +189 -100
  54. package/dist/snapshot.d.ts +12 -7
  55. package/dist/snapshot.js +87 -65
  56. package/dist/spec.d.ts +66 -36
  57. package/dist/spec.js +514 -265
  58. package/dist/stream.d.ts +73 -16
  59. package/dist/stream.js +228 -98
  60. package/dist/thinking.d.ts +23 -8
  61. package/dist/thinking.js +96 -46
  62. package/dist/tokens.d.ts +171 -6
  63. package/dist/tokens.js +326 -6
  64. package/dist/tool-calls.d.ts +89 -15
  65. package/dist/tool-calls.js +205 -85
  66. package/dist/tool-loading.d.ts +109 -187
  67. package/dist/tool-loading.js +286 -372
  68. package/dist/wire.d.ts +81 -0
  69. package/dist/wire.js +73 -0
  70. package/llms.txt +87 -43
  71. package/package.json +4 -4
@@ -1,32 +1,36 @@
1
- import type OpenAI from "openai";
2
- import type { Capabilities } from "./capabilities.ts";
1
+ import type OpenAI from 'openai';
2
+ import type { Capabilities } from './capabilities.ts';
3
3
  /**
4
4
  * The characters per token to size a request to this model with, `CHARS_PER_TOKEN` until a turn
5
5
  * has reported one.
6
6
  *
7
+ * @param supports - The endpoint, as `capabilitiesFor` hands it over.
8
+ * @param model - The name the endpoint knows the model as, as it goes in the body.
9
+ * @returns The highest of the readings held for the model in the current runtime, or
10
+ * `CHARS_PER_TOKEN` where it holds none.
11
+ *
12
+ * @remarks
7
13
  * The highest of the model's last few readings rather than their mean. The estimate guards a
8
14
  * window, and `estimateTokens` says which side of wrong that should be on: a count that comes out
9
15
  * high refuses a run that would have fit, one that comes out low only costs the round trip the
10
16
  * guard was saving. The highest ratio is the lowest count, and the last few rather than all of
11
17
  * them because a run's transcript grows by appending, so the latest requests are the best
12
18
  * likeness of the next.
13
- *
14
- * @param supports The endpoint, as `capabilitiesFor` hands it over.
15
- * @param model The name the endpoint knows the model as, as it goes in the body.
16
19
  */
17
20
  export declare function charsPerTokenFor(supports: Capabilities, model: string): number;
18
21
  /**
19
22
  * Takes one reading from a request that was answered, so the next one to this model is sized by it.
20
23
  *
24
+ * @param supports - The endpoint the request went to.
25
+ * @param body - The request as it was last sent, which names the model.
26
+ * @param promptTokens - The prompt count the endpoint reported for it. Zero or less is no report.
27
+ * @returns The ratio now in force for the model.
28
+ *
29
+ * @remarks
21
30
  * `runTurn` calls it after every turn whose prompt was reported, so a caller using that has
22
31
  * nothing to do. A request carrying an image or audio is not read: a vision model charges a
23
32
  * picture hundreds of tokens its characters say nothing about. Neither is a reading outside what a
24
33
  * tokenizer could produce, which is a miscount and not a tokenizer.
25
- *
26
- * @param supports The endpoint the request went to.
27
- * @param body The request as it was last sent, which names the model.
28
- * @param promptTokens The prompt count the endpoint reported for it. Zero or less is no report.
29
- * @returns The ratio now in force for the model.
30
34
  */
31
35
  export declare function calibrate(supports: Capabilities, body: OpenAI.ChatCompletionCreateParamsStreaming, promptTokens: number): number;
32
36
  /** Forgets every reading, so the next request is sized at `CHARS_PER_TOKEN` again. */
@@ -1,5 +1,7 @@
1
- import { CHARS_PER_TOKEN, requestChars, toolsChars } from "./retry.js";
2
- import { scoped } from "./scope.js";
1
+ import { getOrCreate } from './guards.js';
2
+ import { scoped } from './scope.js';
3
+ import { CHARS_PER_TOKEN, requestChars, toolsChars } from './tokens.js';
4
+ import { PartType } from './wire.js';
3
5
  /**
4
6
  * How many characters a token is worth on one model, learned from what its endpoint reports.
5
7
  *
@@ -33,53 +35,59 @@ const readings = scoped(() => new WeakMap());
33
35
  * The characters per token to size a request to this model with, `CHARS_PER_TOKEN` until a turn
34
36
  * has reported one.
35
37
  *
38
+ * @param supports - The endpoint, as `capabilitiesFor` hands it over.
39
+ * @param model - The name the endpoint knows the model as, as it goes in the body.
40
+ * @returns The highest of the readings held for the model in the current runtime, or
41
+ * `CHARS_PER_TOKEN` where it holds none.
42
+ *
43
+ * @remarks
36
44
  * The highest of the model's last few readings rather than their mean. The estimate guards a
37
45
  * window, and `estimateTokens` says which side of wrong that should be on: a count that comes out
38
46
  * high refuses a run that would have fit, one that comes out low only costs the round trip the
39
47
  * guard was saving. The highest ratio is the lowest count, and the last few rather than all of
40
48
  * them because a run's transcript grows by appending, so the latest requests are the best
41
49
  * likeness of the next.
42
- *
43
- * @param supports The endpoint, as `capabilitiesFor` hands it over.
44
- * @param model The name the endpoint knows the model as, as it goes in the body.
45
50
  */
46
51
  export function charsPerTokenFor(supports, model) {
47
52
  const known = readings().get(supports)?.get(model);
48
53
  return known?.length ? Math.max(...known) : CHARS_PER_TOKEN;
49
54
  }
50
- /** Whether any message carries a part whose tokens its characters do not count. */
51
- const hasMedia = (messages) => messages.some(({ content }) => Array.isArray(content) &&
52
- content.some((part) => part.type !== "text" && part.type !== "refusal"));
55
+ /**
56
+ * Whether any message carries a part whose tokens its characters do not count.
57
+ *
58
+ * @param messages - A request's messages.
59
+ * @returns True where some message's content is an array holding a part that is neither text nor
60
+ * a refusal. Content that is a string never counts.
61
+ */
62
+ const hasMedia = (messages) => messages.some(({ content }) => Array.isArray(content) && content.some((part) => part.type !== PartType.Text && part.type !== PartType.Refusal));
53
63
  /**
54
64
  * Takes one reading from a request that was answered, so the next one to this model is sized by it.
55
65
  *
66
+ * @param supports - The endpoint the request went to.
67
+ * @param body - The request as it was last sent, which names the model.
68
+ * @param promptTokens - The prompt count the endpoint reported for it. Zero or less is no report.
69
+ * @returns The ratio now in force for the model.
70
+ *
71
+ * @remarks
56
72
  * `runTurn` calls it after every turn whose prompt was reported, so a caller using that has
57
73
  * nothing to do. A request carrying an image or audio is not read: a vision model charges a
58
74
  * picture hundreds of tokens its characters say nothing about. Neither is a reading outside what a
59
75
  * tokenizer could produce, which is a miscount and not a tokenizer.
60
- *
61
- * @param supports The endpoint the request went to.
62
- * @param body The request as it was last sent, which names the model.
63
- * @param promptTokens The prompt count the endpoint reported for it. Zero or less is no report.
64
- * @returns The ratio now in force for the model.
65
76
  */
66
77
  export function calibrate(supports, body, promptTokens) {
67
- if (!(promptTokens > 0) || hasMedia(body.messages))
78
+ if (!(promptTokens > 0) || hasMedia(body.messages)) {
68
79
  return charsPerTokenFor(supports, body.model);
80
+ }
69
81
  const ratio = (requestChars(body) + toolsChars(body.tools ?? [])) / promptTokens;
70
82
  if (ratio < PLAUSIBLE.least || ratio > PLAUSIBLE.most) {
71
83
  return charsPerTokenFor(supports, body.model);
72
84
  }
73
- let models = readings().get(supports);
74
- if (!models) {
75
- models = new Map();
76
- readings().set(supports, models);
77
- }
78
- const known = models.get(body.model) ?? [];
85
+ const models = getOrCreate(readings(), supports, () => new Map());
86
+ const known = getOrCreate(models, body.model, () => []);
79
87
  known.push(ratio);
80
- if (known.length > READINGS)
88
+ if (known.length > READINGS) {
81
89
  known.shift();
82
- models.set(body.model, known);
90
+ }
83
91
  return charsPerTokenFor(supports, body.model);
84
92
  }
85
93
  /** Forgets every reading, so the next request is sized at `CHARS_PER_TOKEN` again. */
@@ -1,4 +1,5 @@
1
- import type { Produced } from "./stream.ts";
1
+ import type { EndpointIdentity } from './config.ts';
2
+ import type { Produced } from './stream.ts';
2
3
  /**
3
4
  * What an endpoint turned out not to support, and answering it when it says so.
4
5
  *
@@ -39,6 +40,7 @@ export interface Capabilities {
39
40
  * When this entry was opened, as epoch milliseconds: first contact with the endpoint in this
40
41
  * process, or the age an imported snapshot gave it. What `expireCapabilities` measures.
41
42
  *
43
+ * @remarks
42
44
  * Not when a flag latched. A flag that latches at all almost always does so on the first
43
45
  * request or two, and an entry that latched nothing has nothing to expire, so the difference
44
46
  * costs at most one retried field somewhat earlier than it was due — which is the direction to
@@ -51,6 +53,7 @@ export interface Capabilities {
51
53
  * What one model on that endpoint turned out not to support. All start optimistic and only ever
52
54
  * latch off, the same as the endpoint's own.
53
55
  *
56
+ * @remarks
54
57
  * These arrive through the same channel as the endpoint's — an error string on a chat
55
58
  * completion — which is why they are negotiated by the same loop rather than a second one. What
56
59
  * makes them the model's is that the answer differs between two models the same key reaches.
@@ -65,6 +68,7 @@ export interface ModelCapabilities {
65
68
  * Spells its ceiling `max_tokens`. The reasoning models want `max_completion_tokens` instead,
66
69
  * and they are exactly the models anyone sets an effort on.
67
70
  *
71
+ * @remarks
68
72
  * Read it as `=== false` rather than for truthiness. This is the one flag whose two answers are
69
73
  * both a field to send, so a caller that named no model — and was told that changes nothing —
70
74
  * gets `undefined` here and, on a truthiness test, sends `max_completion_tokens` to a server
@@ -81,6 +85,7 @@ export interface ModelCapabilities {
81
85
  * builder leaves out of what `extraBody` asks for. Only ever grows, which is this set's way of
82
86
  * latching off.
83
87
  *
88
+ * @remarks
84
89
  * Only the fields a caller offered as droppable reach it (see `NegotiateOptions.droppable`),
85
90
  * because a name read out of an error string is not otherwise something to stop sending: a
86
91
  * refusal naming `messages` is a broken request, not a field the model can do without.
@@ -97,6 +102,7 @@ export interface ModelCapabilities {
97
102
  * Efforts this model refused by value rather than by field — a request that named `none` on a
98
103
  * model whose list starts at `minimal`. Only ever grows, which is this set's way of latching.
99
104
  *
105
+ * @remarks
100
106
  * Separate from `reasoningEffort` because the two refusals mean opposite things: that one says
101
107
  * the model cannot reason and the field must go, this one says it reasons and was handed an
102
108
  * effort off a list this package does not know. Dropping the field there would run at the
@@ -104,7 +110,7 @@ export interface ModelCapabilities {
104
110
  */
105
111
  refusedEfforts: Set<string>;
106
112
  /**
107
- * The efforts a refusal published as this model's, in ladder order, filtered to the ones
113
+ * The efforts a refusal published as this model's, in the order it gave them, filtered to the ones
108
114
  * `EFFORT_LADDER` can place. Absent until a refusal lists them, and a model that lists nothing
109
115
  * is walked up the ladder a rung per refusal instead.
110
116
  */
@@ -118,59 +124,119 @@ export interface ModelCapabilities {
118
124
  */
119
125
  assistantPrefill: boolean;
120
126
  }
127
+ /** The name of one of a model's on/off flags: every `boolean` on `ModelCapabilities`. */
128
+ export type ModelFlag = {
129
+ [Key in keyof ModelCapabilities]-?: ModelCapabilities[Key] extends boolean ? Key : never;
130
+ }[keyof ModelCapabilities];
121
131
  /**
122
- * What this endpoint is known not to support. The same object every time, so what `negotiate`
123
- * latches off stays off.
132
+ * A model's on/off flags as it starts out, every one optimistic.
124
133
  *
125
- * @param baseUrl Where the endpoint is. The two flags on it are per-server; what is per-model
134
+ * @remarks
135
+ * A record over every boolean on `ModelCapabilities`, so a flag added there does not compile until
136
+ * it is listed here — and this is the list a snapshot is exported, imported and judged empty by,
137
+ * where each of those used to name the flags again and one could be missed.
138
+ */
139
+ export declare const OPTIMISTIC_MODEL: Readonly<Record<ModelFlag, true>>;
140
+ /** The names of those flags, for a caller that reads or latches each in turn. */
141
+ export declare const MODEL_FLAGS: ModelFlag[];
142
+ /**
143
+ * What each endpoint cannot do, remembered for the life of the process.
144
+ *
145
+ * @remarks
146
+ * Keyed by `endpointId` — `endpointKey` hashed, so a snapshot holds no key — because these are
147
+ * facts about the server on the other end and not about this one. A llama.cpp box that cannot
148
+ * compile a grammar and a cloud API that can are both reachable from one settings row over its
149
+ * lifetime — an operator retargets it from Ollama this afternoon to OpenAI this evening — and the
150
+ * first one's refusal must not quietly strip pattern/format from the second one's requests, or
151
+ * silently cost it its token counts, for the rest of the process. Bounded by the number of
152
+ * endpoints ever configured, which is a settings row's worth.
153
+ *
154
+ * The API key is part of that identity, the same as it is for the client pool and the model
155
+ * listings. A router — LiteLLM, OpenRouter, a gateway with several boxes behind it — is free to
156
+ * send two keys to two different backends, and then what one of them refused is not a fact about
157
+ * the other. Keyed on the URL alone the first caller through the gateway latched for everyone
158
+ * behind it, including the `models` map underneath, which is the level where two keys through
159
+ * one host are most likely to differ at all.
160
+ */
161
+ /**
162
+ * Hears one line of operator text: what was given up on, what is being waited out, what was chosen.
163
+ *
164
+ * @remarks
165
+ * One name for it because the same listener is handed down from the loop through a turn to
166
+ * `negotiate` and across to every side task, and each had spelled the signature out.
167
+ */
168
+ export type OnNotice = (message: string) => void;
169
+ /**
170
+ * What this endpoint is known not to support.
171
+ *
172
+ * @param baseUrl - Where the endpoint is. The two flags on it are per-server; what is per-model
126
173
  * hangs off `models`, which `modelCapabilitiesFor` reads.
127
- * @param apiKey The rest of the endpoint's identity. Optional, because it changes nothing for
174
+ * @param [apiKey] - The rest of the endpoint's identity. Optional, because it changes nothing for
128
175
  * the ordinary case of one key per base URL and a caller with one need not thread it through;
129
176
  * absent reads as `NO_KEY`, exactly as it does in `getClient`, so an endpoint with no key and
130
177
  * one that passes `undefined` share an entry rather than holding two.
178
+ * @returns The endpoint's live entry, made with both flags on and no models the first time it is
179
+ * asked for.
180
+ *
181
+ * @remarks
182
+ * The same object every time, so what `negotiate` latches off stays off.
131
183
  */
132
184
  export declare function capabilitiesFor(baseUrl: string, apiKey?: string): Capabilities;
133
185
  /**
134
186
  * What this model on this endpoint is known not to support. The same object every time, so what
135
187
  * `negotiate` latches off stays off.
136
188
  *
189
+ * @param supports - The endpoint's own, as `capabilitiesFor` hands it over.
190
+ * @param model - The name the endpoint knows the model as — whatever goes in the request body,
191
+ * since that is the only name the refusal is about.
192
+ * @returns The model's live entry under `supports.models`, made with every flag on and nothing
193
+ * refused the first time it is asked for.
194
+ *
195
+ * @remarks
137
196
  * Nested under the endpoint rather than keyed by name alone, because `gpt-4o` at OpenAI and
138
197
  * `gpt-4o` behind a proxy need not be the same weights — and a proxy is free to answer to a name
139
198
  * it does not really serve. One that refused a reasoning effort must not speak for the other.
140
199
  * Bounded by the models actually asked for on that endpoint, which is what a dropdown holds.
141
- *
142
- * @param supports The endpoint's own, as `capabilitiesFor` hands it over.
143
- * @param model The name the endpoint knows the model as — whatever goes in the request body,
144
- * since that is the only name the refusal is about.
145
200
  */
146
201
  export declare function modelCapabilitiesFor(supports: Capabilities, model: string): ModelCapabilities;
147
- /** Every endpoint's capabilities by `endpointId`, the live objects, for `exportCapabilities`. */
202
+ /**
203
+ * Every endpoint's capabilities by `endpointId`, the live objects, for `exportCapabilities`.
204
+ *
205
+ * @returns The map itself rather than a copy, read-only by type alone: what is latched or forgotten
206
+ * afterwards shows through it.
207
+ */
148
208
  export declare const knownCapabilities: () => ReadonlyMap<string, Capabilities>;
149
209
  /**
150
210
  * The capabilities of the endpoint with this `endpointId`, created optimistic if unseen — the way
151
211
  * `importCapabilities` reaches an endpoint it has only a digest for.
212
+ *
213
+ * @param id - The digest `endpointId` gives. Not checked: any string opens an entry under itself.
214
+ * @returns The live entry, the same object `capabilitiesFor` hands over for that endpoint.
152
215
  */
153
216
  export declare function capabilitiesById(id: string): Capabilities;
154
217
  /**
155
218
  * Forgets what one endpoint refused, or every endpoint's when told none.
156
219
  *
220
+ * @param [endpoint] - Whose to forget, by the same identity `capabilitiesFor` takes. Absent clears
221
+ * every endpoint, which is what tests and `resetAll` mean by it.
222
+ * @returns Whether there was anything to forget.
223
+ *
224
+ * @remarks
157
225
  * A latch never unlatches on its own, so a server upgraded behind the same URL — a newer
158
226
  * llama.cpp that compiles the grammar, a proxy that has learned `stream_options` — keeps being
159
227
  * sent the downgraded request for the life of the process. This is the seam for a consumer that
160
228
  * *knows* it changed: a settings row saved, a health check that reads a new build string, an
161
229
  * operator pressing a button. See `expireCapabilities` for the case where nobody knows.
162
- *
163
- * @param endpoint Whose to forget, by the same identity `capabilitiesFor` takes. Absent clears
164
- * every endpoint, which is what tests and `resetAll` mean by it.
165
- * @returns Whether there was anything to forget.
166
230
  */
167
- export declare function resetCapabilities(endpoint?: {
168
- baseUrl: string;
169
- apiKey?: string;
170
- }): boolean;
231
+ export declare function resetCapabilities(endpoint?: EndpointIdentity): boolean;
171
232
  /**
172
233
  * Forgets every endpoint whose entry is older than this, so the next request finds out again.
173
234
  *
235
+ * @param maxAgeMs - How old an entry may be. Zero or less expires everything.
236
+ * @param [now] - The clock, for tests.
237
+ * @returns How many endpoints were forgotten.
238
+ *
239
+ * @remarks
174
240
  * The other half of the problem `resetCapabilities` solves: a server upgraded behind the same URL
175
241
  * with nobody to notice. What an expiry costs is one round trip per endpoint and model — the next
176
242
  * request carries the field again, and a server that still refuses it refuses it once and
@@ -180,15 +246,16 @@ export declare function resetCapabilities(endpoint?: {
180
246
  *
181
247
  * Nothing calls this on a timer. When to sweep is the consumer's, the same way how stale a
182
248
  * snapshot is too stale is, and a sweep costs a walk of one settings row's worth of entries.
183
- *
184
- * @param maxAgeMs How old an entry may be. Zero or less expires everything.
185
- * @param now The clock, for tests.
186
- * @returns How many endpoints were forgotten.
187
249
  */
188
250
  export declare function expireCapabilities(maxAgeMs: number, now?: number): number;
251
+ /** The setting that asks for no `reasoning_effort` field at all. Ours, and never sent. */
252
+ export declare const EFFORT_OFF: 'off';
253
+ /** The lowest effort the wire has a word for, which is what a side task asks for. */
254
+ export declare const EFFORT_NONE: 'none';
189
255
  /**
190
256
  * The efforts this package can place, cheapest first. A substitution only ever walks *up* it.
191
257
  *
258
+ * @remarks
192
259
  * Not a list of what any model takes — `medium` is refused by a model whose list is
193
260
  * `minimal, low, high`, and `xhigh` is real and deliberately absent. It is the order the values
194
261
  * OpenAI has shipped stand in, which is all a step needs to know. A value not on it cannot be
@@ -196,25 +263,56 @@ export declare function expireCapabilities(maxAgeMs: number, now?: number): numb
196
263
  * refused `xhigh` to `high` would quietly answer a question with less deliberation than whoever
197
264
  * typed it asked for, where stepping up from `none` to `minimal` only costs tokens and says so.
198
265
  */
199
- export declare const EFFORT_LADDER: readonly ["none", "minimal", "low", "medium", "high"];
266
+ export declare const EFFORT_LADDER: readonly ["none", 'minimal', 'low', 'medium', 'high'];
200
267
  /**
201
268
  * What to actually put in `reasoning_effort` for a model that has refused the value asked for.
202
269
  *
270
+ * @param refused - What the model has refused, as `negotiate` hands it over. Absent is a model that
271
+ * has refused nothing.
272
+ * @param asked - What the config asks for. `"off"` and absent mean no effort.
273
+ * @returns The value for the field, or the empty string for no field. `asked` itself, as written,
274
+ * where the model has not refused it — and where it has but there is no rung left to step to.
275
+ *
276
+ * @remarks
203
277
  * The caller's own value, until this model has said that value is not one of its own; then the
204
278
  * cheapest it will take that is at least as much deliberation. A model that takes no effort at
205
279
  * all, and an absent or `"off"` setting, both answer the empty string, which is the body
206
280
  * builders' signal to send no field.
281
+ */
282
+ export declare function effortFor(refused: ModelCapabilities | undefined, asked: string | undefined): string;
283
+ /**
284
+ * The reply ceiling and the temperature as this model takes them, to spread into a request body.
207
285
  *
208
- * @param refused What the model has refused, as `negotiate` hands it over. Absent is a model that
286
+ * @param refused - What the model has refused, as `negotiate` hands it over. Absent is a model that
209
287
  * has refused nothing.
210
- * @param asked What the config asks for. `"off"` and absent mean no effort.
288
+ * @param maxTokens - The ceiling. Zero or less sends none and leaves it to the server: a zero sent
289
+ * as `max_tokens: 0` asks for an empty reply.
290
+ * @param temperature - What to sample at, where the model takes one that was picked for it.
291
+ * @returns A fresh object holding the ceiling under one of its two names, or under neither, and
292
+ * `temperature` unless the model refused ours.
293
+ *
294
+ * @remarks
295
+ * The reasoning models want the ceiling spelled the other way, and one that will only run at the
296
+ * temperature it was built with is sent none. Both are tested `=== false`, so a caller that named
297
+ * no model sends what one that has refused nothing is sent; see `ModelCapabilities.legacyTokenLimit`.
211
298
  */
212
- export declare function effortFor(refused: ModelCapabilities | undefined, asked: string | undefined): string;
299
+ export declare const ceilingAndTemperature: (refused: ModelCapabilities | undefined, maxTokens: number, temperature: number) => {
300
+ max_completion_tokens: number;
301
+ max_tokens?: undefined;
302
+ temperature?: number | undefined;
303
+ } | {
304
+ max_completion_tokens?: undefined;
305
+ max_tokens: number;
306
+ temperature?: number | undefined;
307
+ } | {
308
+ temperature?: number | undefined;
309
+ };
213
310
  /** What `negotiate` takes besides the request. All optional. */
214
311
  export interface NegotiateOptions {
215
312
  /**
216
313
  * The flag `send` will be given, for a caller that has to read it after `negotiate` returns.
217
314
  *
315
+ * @remarks
218
316
  * An outer retry loop needs it: nothing is retried once the server has started answering, and
219
317
  * by the time a rejected promise is in hand the turn is over. Callers without one can leave
220
318
  * this out and take the flag from `send`'s second argument, which is the same object.
@@ -223,6 +321,7 @@ export interface NegotiateOptions {
223
321
  /**
224
322
  * Told what was given up on, for a watcher who would otherwise see an unexplained pause.
225
323
  *
324
+ * @remarks
226
325
  * Each message opens with the thing that refused, so a reader tells the two levels apart
227
326
  * without parsing: `server` for the endpoint's own, and the model's own name — as `model` was
228
327
  * given it — for the three that are the model's. That matters because these latch for the
@@ -231,10 +330,11 @@ export interface NegotiateOptions {
231
330
  * line for every model on the endpoint, and the operator who later asks why the setting still
232
331
  * reads `high` has no way to tell which one it was about.
233
332
  */
234
- onNotice?: (message: string) => void;
333
+ onNotice?: OnNotice;
235
334
  /**
236
335
  * Which model this request is for, by the name the endpoint knows it as.
237
336
  *
337
+ * @remarks
238
338
  * Given one, the refusals that are about the model rather than the server are answered too,
239
339
  * and `send` is handed what that model has already refused. Left out, nothing changes — which
240
340
  * is the point of it being here rather than a third positional argument: a caller with one
@@ -246,6 +346,7 @@ export interface NegotiateOptions {
246
346
  * them — what `extraBody` added, ordinarily. Such a name is latched into the model's
247
347
  * `refusedFields` and the request is sent again, which `send` is expected to build without it.
248
348
  *
349
+ * @remarks
249
350
  * Opt-in by name, because the answer is to stop sending the field, and a name out of an error
250
351
  * string that `send` does not know how to leave out would only be refused again. Needs `model`,
251
352
  * since the latch is the model's.
@@ -257,6 +358,17 @@ export interface NegotiateOptions {
257
358
  * request can do without. Returns once the endpoint has answered, or throws if the refusal is
258
359
  * not one of ours.
259
360
  *
361
+ * @typeParam T - What `send` resolves, which is handed back as it came.
362
+ * @param supports - What this endpoint has already refused. Latched off further as it refuses more.
363
+ * @param send - Builds and sends the request. Called again per downgrade, never once tokens
364
+ * have arrived. Its third argument is what the named model has refused, absent when no model
365
+ * was named.
366
+ * @param [options] - `produced` for a caller with its own retry budget, `onNotice` for a watcher,
367
+ * `model` to negotiate the model's refusals alongside the endpoint's.
368
+ * @returns What the first `send` to succeed resolved. A rejection is `send`'s own error, rethrown
369
+ * as it was caught.
370
+ *
371
+ * @remarks
260
372
  * A loop rather than one retry. A server that has heard of neither `stream_options` nor a
261
373
  * grammar keyword complains about them one at a time, and answering only the first leaves the
262
374
  * second to fail the request — so the first run against such an endpoint is spent discovering
@@ -278,12 +390,5 @@ export interface NegotiateOptions {
278
390
  * ever one flag in a turn — the same box `streamTurn` sets and the re-send below reads — and a
279
391
  * caller that passed it to only one of the two got a turn that had already streamed tokens sent
280
392
  * again, silently, with the watcher seeing every one of them twice.
281
- *
282
- * @param supports What this endpoint has already refused. Latched off further as it refuses more.
283
- * @param send Builds and sends the request. Called again per downgrade, never once tokens
284
- * have arrived. Its third argument is what the named model has refused, absent when no model
285
- * was named.
286
- * @param options `produced` for a caller with its own retry budget, `onNotice` for a watcher,
287
- * `model` to negotiate the model's refusals alongside the endpoint's.
288
393
  */
289
394
  export declare function negotiate<T>(supports: Capabilities, send: (supports: Capabilities, produced: Produced, model: ModelCapabilities | undefined) => Promise<T>, { produced, onNotice, model: name, droppable }?: NegotiateOptions): Promise<T>;