melchizedek-agents 1.1.0 → 1.3.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 (146) hide show
  1. package/.env.example +19 -0
  2. package/AGENT_SETUP.md +3 -1
  3. package/CHANGELOG.md +373 -0
  4. package/DOCUMENTATION.md +193 -21
  5. package/ONBOARDING.md +378 -0
  6. package/QUICKSTART.md +15 -2
  7. package/README.md +26 -3
  8. package/config/agents/examples/connectors.yaml +82 -0
  9. package/config/agents/examples/council.yaml +1 -1
  10. package/config/agents/examples/critic.yaml +8 -8
  11. package/config/agents/examples/image_production.yaml +4 -3
  12. package/config/agents/syndicate.schema.json +389 -33
  13. package/config/agents/syndicateSchema.yaml +18 -6
  14. package/config/agents/templates/intake_extractor.yaml +3 -2
  15. package/config/agents/templates/systems_operator.yaml +5 -2
  16. package/dist/lib/a2a/app.d.ts +12 -0
  17. package/dist/lib/a2a/app.js +25 -11
  18. package/dist/lib/a2a/executor.js +14 -6
  19. package/dist/lib/a2a/oauthSetup.d.ts +16 -0
  20. package/dist/lib/a2a/oauthSetup.js +77 -8
  21. package/dist/lib/agentDialect.d.ts +18 -8
  22. package/dist/lib/agentDialect.js +36 -19
  23. package/dist/lib/chatgpt/adapter.d.ts +50 -0
  24. package/dist/lib/chatgpt/adapter.js +107 -0
  25. package/dist/lib/chatgpt/cli.d.ts +23 -0
  26. package/dist/lib/chatgpt/cli.js +77 -0
  27. package/dist/lib/chatgpt/oauth.d.ts +103 -0
  28. package/dist/lib/chatgpt/oauth.js +448 -0
  29. package/dist/lib/chatgpt/state.d.ts +98 -0
  30. package/dist/lib/chatgpt/state.js +145 -0
  31. package/dist/lib/chatgpt/store.d.ts +24 -0
  32. package/dist/lib/chatgpt/store.js +94 -0
  33. package/dist/lib/compile.d.ts +38 -7
  34. package/dist/lib/compile.js +97 -62
  35. package/dist/lib/compileNative.d.ts +11 -3
  36. package/dist/lib/compileNative.js +18 -1
  37. package/dist/lib/dispatch.js +6 -2
  38. package/dist/lib/doctor.d.ts +107 -0
  39. package/dist/lib/doctor.js +217 -7
  40. package/dist/lib/loadSyndicate.d.ts +15 -0
  41. package/dist/lib/mcp/server.d.ts +195 -0
  42. package/dist/lib/mcp/server.js +564 -0
  43. package/dist/lib/memory/supabaseMemoryService.js +2 -2
  44. package/dist/lib/models/adapterResolver.js +6 -1
  45. package/dist/lib/models/capabilities.d.ts +9 -0
  46. package/dist/lib/models/capabilities.js +24 -0
  47. package/dist/lib/models/chatCompletionsAdapter.d.ts +11 -3
  48. package/dist/lib/models/chatCompletionsAdapter.js +19 -5
  49. package/dist/lib/models/claudeAdapter.js +4 -0
  50. package/dist/lib/models/contract.d.ts +5 -2
  51. package/dist/lib/models/gateway.d.ts +3 -2
  52. package/dist/lib/models/gateway.js +8 -4
  53. package/dist/lib/models/gatewayAdapter.d.ts +7 -1
  54. package/dist/lib/models/gatewayAdapter.js +8 -0
  55. package/dist/lib/models/geminiAdapter.js +8 -2
  56. package/dist/lib/models/genaiMapping.d.ts +8 -4
  57. package/dist/lib/models/genaiMapping.js +23 -9
  58. package/dist/lib/models/gptAdapter.d.ts +4 -2
  59. package/dist/lib/models/gptAdapter.js +11 -4
  60. package/dist/lib/models/grokAdapter.d.ts +3 -1
  61. package/dist/lib/models/grokAdapter.js +11 -3
  62. package/dist/lib/models/kimiAdapter.d.ts +2 -1
  63. package/dist/lib/models/kimiAdapter.js +3 -3
  64. package/dist/lib/models/reasoning.d.ts +30 -2
  65. package/dist/lib/models/reasoning.js +56 -5
  66. package/dist/lib/onboarding.d.ts +135 -0
  67. package/dist/lib/onboarding.js +578 -0
  68. package/dist/lib/runtime/approvals.d.ts +2 -0
  69. package/dist/lib/runtime/approvals.js +4 -3
  70. package/dist/lib/runtime/credentials.d.ts +6 -0
  71. package/dist/lib/runtime/native/agentLoop.d.ts +5 -2
  72. package/dist/lib/runtime/native/agentLoop.js +2 -0
  73. package/dist/lib/runtime/native/checkpoint.d.ts +12 -5
  74. package/dist/lib/runtime/native/checkpoint.js +13 -19
  75. package/dist/lib/runtime/native/delegate.d.ts +82 -1
  76. package/dist/lib/runtime/native/delegate.js +157 -2
  77. package/dist/lib/runtime/native/interrupts.d.ts +88 -5
  78. package/dist/lib/runtime/native/interrupts.js +220 -30
  79. package/dist/lib/runtime/syndicateTurn.d.ts +4 -2
  80. package/dist/lib/runtime/syndicateTurn.js +324 -77
  81. package/dist/lib/runtime/turnControl.d.ts +41 -0
  82. package/dist/lib/session/transcript.d.ts +9 -0
  83. package/dist/lib/session/transcript.js +20 -1
  84. package/dist/lib/skills.d.ts +18 -0
  85. package/dist/lib/skills.js +73 -1
  86. package/dist/lib/storage/postgres/index.d.ts +6 -1
  87. package/dist/lib/storage/postgres/index.js +1 -1
  88. package/dist/lib/storage/postgres/taskQueue.d.ts +7 -3
  89. package/dist/lib/storage/postgres/taskQueue.js +10 -3
  90. package/dist/lib/syndicateSchema.d.ts +184 -9
  91. package/dist/lib/syndicateSchema.js +143 -29
  92. package/dist/lib/tools/auth.d.ts +13 -1
  93. package/dist/lib/tools/auth.js +10 -2
  94. package/dist/lib/tools/credentialHosts.d.ts +88 -0
  95. package/dist/lib/tools/credentialHosts.js +165 -0
  96. package/dist/lib/tools/credentialUses.d.ts +98 -0
  97. package/dist/lib/tools/credentialUses.js +120 -0
  98. package/dist/lib/tools/generateImageTool.js +2 -1
  99. package/dist/lib/tools/inspectImageTool.js +1 -1
  100. package/dist/lib/tools/mcpToolFactory.d.ts +29 -1
  101. package/dist/lib/tools/mcpToolFactory.js +72 -26
  102. package/dist/lib/tools/mcpTransports.d.ts +12 -0
  103. package/dist/lib/tools/mcpTransports.js +11 -0
  104. package/dist/lib/tools/oauthConsent.d.ts +18 -3
  105. package/dist/lib/tools/oauthConsent.js +57 -15
  106. package/dist/lib/tools/oauthDiscovery.d.ts +202 -0
  107. package/dist/lib/tools/oauthDiscovery.js +616 -0
  108. package/dist/lib/tools/oauthHosts.d.ts +2 -0
  109. package/dist/lib/tools/oauthHosts.js +2 -2
  110. package/dist/lib/tools/oauthTools.d.ts +54 -13
  111. package/dist/lib/tools/oauthTools.js +106 -23
  112. package/dist/lib/tools/openapiTools.d.ts +7 -0
  113. package/dist/lib/tools/openapiTools.js +49 -3
  114. package/dist/lib/tools/taskTools.d.ts +32 -2
  115. package/dist/lib/tools/taskTools.js +93 -33
  116. package/dist/lib/tools/xApiSearchTool.js +2 -2
  117. package/dist/lib/workflow/turn.d.ts +22 -3
  118. package/dist/lib/workflow/turn.js +151 -31
  119. package/dist/lib/workflow.d.ts +10 -7
  120. package/dist/lib/workflow.js +10 -7
  121. package/dist/scripts/a2a_server.d.ts +2 -2
  122. package/dist/scripts/a2a_server.js +19 -2
  123. package/dist/scripts/assistant_worker.d.ts +2 -0
  124. package/dist/scripts/assistant_worker.js +24 -5
  125. package/dist/scripts/doctor.d.ts +4 -2
  126. package/dist/scripts/doctor.js +6 -4
  127. package/dist/scripts/mcp_server.d.ts +12 -0
  128. package/dist/scripts/mcp_server.js +345 -0
  129. package/dist/scripts/setup.d.ts +25 -0
  130. package/dist/scripts/setup.js +181 -0
  131. package/dist/scripts/skills_install.d.ts +3 -1
  132. package/dist/scripts/skills_install.js +17 -2
  133. package/dist/scripts/syndicate_chat.js +12 -3
  134. package/dist/scripts/yaml_codemod.d.ts +12 -2
  135. package/dist/scripts/yaml_codemod.js +115 -33
  136. package/package.json +8 -2
  137. package/skills/README.md +11 -2
  138. package/skills/briefs/_shared.md +1 -1
  139. package/skills/briefs/melchizedek-models.md +4 -2
  140. package/skills/briefs/skills-readme.md +3 -1
  141. package/skills/melchizedek-models/SKILL.md +6 -2
  142. package/skills/melchizedek-onboard/SKILL.md +71 -0
  143. package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
  144. package/skills/melchizedek-onboard-keys/SKILL.md +47 -0
  145. package/skills/melchizedek-onboard-local/SKILL.md +41 -0
  146. package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
@@ -54,7 +54,7 @@
54
54
  * ADR 0053): llm.transport, llm.finish_reason, llm.http_status,
55
55
  * llm.retries, llm.retry_without_thinking, and the search and drop marks.
56
56
  */
57
- import type { ModelAdapter, ModelRequest, ModelResponse, ReasoningSetting, ToolChoiceMode, Usage } from './contract.ts';
57
+ import type { ModelAdapter, ModelRequest, ModelResponse, ReasoningLevel, ReasoningSetting, ToolChoiceMode, Usage } from './contract.ts';
58
58
  type ChatContentPart = {
59
59
  type: 'text';
60
60
  text: string;
@@ -190,10 +190,18 @@ export declare abstract class ChatCompletionsAdapter implements ModelAdapter {
190
190
  /**
191
191
  * The body fields for the request's reasoning. Default: `reasoning_effort`
192
192
  * as the effort word ADR 0047 gives this model (lib/models/reasoning.ts),
193
- * the field every chat-completions provider and gateway reads; nothing
194
- * when the request sets no reasoning.
193
+ * the field every chat-completions provider and gateway reads, held at
194
+ * `reasoningCeiling(model)`; nothing when the request sets no reasoning.
195
195
  */
196
196
  protected reasoningFields(model: string, setting: ReasoningSetting | undefined): Record<string, unknown>;
197
+ /**
198
+ * The highest reasoning level this adapter sends as asked for `model`
199
+ * (ADR 0117). Default: the direct provider's (lib/models/reasoning.ts
200
+ * effortCeiling); the gateway holds every id at `high`.
201
+ */
202
+ protected reasoningCeiling(model: string): ReasoningLevel;
203
+ /** The effort word for one setting, held at reasoningCeiling; a held level is marked on the span (`llm.reasoning.weakened`). */
204
+ protected effortFor(model: string, setting: ReasoningSetting): string;
197
205
  /** Provider-specific body fields, merged last. */
198
206
  protected extraBodyFields(_request: ModelRequest): Record<string, unknown>;
199
207
  /**
@@ -56,7 +56,7 @@
56
56
  */
57
57
  import { errorDecision, errorText, statusDecision } from './errorResponse.js';
58
58
  import { currentTurnStart, providerStateOf } from './providerState.js';
59
- import { reasoningConfig } from './reasoning.js';
59
+ import { effortCeiling as effortCeilingOf, effortWord } from './reasoning.js';
60
60
  import { fetchWithRetry } from './retry.js';
61
61
  import { toStrictJsonSchema } from './schemaNormalize.js';
62
62
  import { setLlmSpanAttribute } from '../observability/tracer.js';
@@ -327,12 +327,26 @@ export class ChatCompletionsAdapter {
327
327
  /**
328
328
  * The body fields for the request's reasoning. Default: `reasoning_effort`
329
329
  * as the effort word ADR 0047 gives this model (lib/models/reasoning.ts),
330
- * the field every chat-completions provider and gateway reads; nothing
331
- * when the request sets no reasoning.
330
+ * the field every chat-completions provider and gateway reads, held at
331
+ * `reasoningCeiling(model)`; nothing when the request sets no reasoning.
332
332
  */
333
333
  reasoningFields(model, setting) {
334
- const word = setting !== undefined ? reasoningConfig(model, setting).reasoningEffort : undefined;
335
- return word !== undefined ? { reasoning_effort: word } : {};
334
+ return setting !== undefined ? { reasoning_effort: this.effortFor(model, setting) } : {};
335
+ }
336
+ /**
337
+ * The highest reasoning level this adapter sends as asked for `model`
338
+ * (ADR 0117). Default: the direct provider's (lib/models/reasoning.ts
339
+ * effortCeiling); the gateway holds every id at `high`.
340
+ */
341
+ reasoningCeiling(model) {
342
+ return effortCeilingOf(model);
343
+ }
344
+ /** The effort word for one setting, held at reasoningCeiling; a held level is marked on the span (`llm.reasoning.weakened`). */
345
+ effortFor(model, setting) {
346
+ const { word, weakened } = effortWord(model, setting, this.reasoningCeiling(model));
347
+ if (weakened)
348
+ setLlmSpanAttribute('llm.reasoning.weakened', weakened);
349
+ return word;
336
350
  }
337
351
  /** Provider-specific body fields, merged last. */
338
352
  extraBodyFields(_request) {
@@ -46,6 +46,7 @@ import { claudeClientSpec, endpointFromEnv, endpointLabel, instantiateClient, na
46
46
  import { currentTurnStart, providerStateOf, withProviderState } from './providerState.js';
47
47
  import { errorDecision, errorText } from './errorResponse.js';
48
48
  import { adaptiveThinkingFor, claudeGeneration, claudeReasoningOf, claudeUrlImagesOn, THINKING_BINDING_BETA } from './claudeModels.js';
49
+ import { isAboveHigh } from './reasoning.js';
49
50
  /** The provider id this adapter reports and writes its state under (lib/models/providerMap.ts). */
50
51
  export const ANTHROPIC_PROVIDER = 'anthropic';
51
52
  /** The providerState kind this adapter writes: the signed blocks that preceded a part. */
@@ -393,6 +394,9 @@ export class ClaudeAdapter {
393
394
  else {
394
395
  const requestedBudget = reasoning.budget;
395
396
  thinkingConfigured = typeof requestedBudget === 'number' && requestedBudget > 0;
397
+ // The budget generations stop at `high`'s budget (ADR 0117).
398
+ if (isAboveHigh(request.reasoning))
399
+ setLlmSpanAttribute('llm.reasoning.weakened', request.reasoning);
396
400
  if (thinkingConfigured && continuesUnsignedToolLoop(messages)) {
397
401
  // The tool call this step answers carries no signed thinking (another
398
402
  // provider or model made it, or its state was lost), and Anthropic
@@ -181,9 +181,12 @@ export type ToolChoice = 'auto' | 'none' | 'required' | {
181
181
  /**
182
182
  * How hard an agent reasons, on any provider (ADR 0047): a level, or a token
183
183
  * budget. A budget of 0 is `none`, and `none` means as little reasoning as
184
- * the model allows.
184
+ * the model allows. `xhigh` and `max` are the levels above `high` that some
185
+ * models take (ADR 0117): an adapter whose model stops lower sends its
186
+ * highest setting and marks the span `llm.reasoning.weakened`; a budget
187
+ * never reads as either.
185
188
  */
186
- export type ReasoningLevel = 'none' | 'low' | 'medium' | 'high';
189
+ export type ReasoningLevel = 'none' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
187
190
  export type ReasoningSetting = ReasoningLevel | {
188
191
  budget_tokens: number;
189
192
  };
@@ -24,7 +24,7 @@
24
24
  * native search. lib/models/capabilities.ts turns that into a per-agent
25
25
  * report the doctor, the A2A startup log and the ledger all read.
26
26
  *
27
- * WHY a leaf (imports providerMap and endpoints only):
27
+ * WHY a leaf (imports providerMap, endpoints and lib/chatgpt/state.ts only):
28
28
  * The registry, the gateway adapter, the capability report and the
29
29
  * doctor all need this decision; keeping it free of adapter imports
30
30
  * avoids the cycle model-routing warns about.
@@ -70,7 +70,8 @@ export interface GatewayConfig {
70
70
  */
71
71
  export declare function gatewayConfig(): GatewayConfig | null;
72
72
  /** A human-readable misconfiguration, or undefined when the gateway is
73
- * either off or usable. */
73
+ * either off or usable. An unrecognised value is named as such and never
74
+ * repeated, since it may be a secret pasted into the wrong variable. */
74
75
  export declare function gatewayProblem(): string | undefined;
75
76
  /** True when a gateway is configured well enough to serve requests. */
76
77
  export declare function gatewayUsable(): boolean;
@@ -24,7 +24,7 @@
24
24
  * native search. lib/models/capabilities.ts turns that into a per-agent
25
25
  * report the doctor, the A2A startup log and the ledger all read.
26
26
  *
27
- * WHY a leaf (imports providerMap and endpoints only):
27
+ * WHY a leaf (imports providerMap, endpoints and lib/chatgpt/state.ts only):
28
28
  * The registry, the gateway adapter, the capability report and the
29
29
  * doctor all need this decision; keeping it free of adapter imports
30
30
  * avoids the cycle model-routing warns about.
@@ -37,6 +37,7 @@
37
37
  * MODEL_GATEWAY_MODEL_MAP optional "from=to,from=to" wire-name overrides
38
38
  * for ids the default mapper gets wrong
39
39
  */
40
+ import { chatGptSignInRoutesOpenAi } from '../chatgpt/state.js';
40
41
  import { providerReady } from './endpoints.js';
41
42
  import { PROVIDERS, providerForModel } from './providerMap.js';
42
43
  import { trimTrailingSlashes } from './urls.js';
@@ -81,14 +82,16 @@ export function gatewayConfig() {
81
82
  };
82
83
  }
83
84
  /** A human-readable misconfiguration, or undefined when the gateway is
84
- * either off or usable. */
85
+ * either off or usable. An unrecognised value is named as such and never
86
+ * repeated, since it may be a secret pasted into the wrong variable. */
85
87
  export function gatewayProblem() {
86
88
  const id = (process.env[GATEWAY_ENV] ?? '').trim();
87
89
  if (!id)
88
90
  return undefined;
89
91
  const cfg = gatewayConfig();
90
92
  if (!cfg) {
91
- return `${GATEWAY_ENV}=${id} is not a known gateway (expected one of: ${Object.keys(GATEWAYS).join(', ')})`;
93
+ // The value is never echoed: a key pasted into the wrong variable is still a secret.
94
+ return `${GATEWAY_ENV} is set to an unrecognised value (not shown); it must be one of: ${Object.keys(GATEWAYS).join(', ')}`;
92
95
  }
93
96
  if (!cfg.keyPresent)
94
97
  return `${GATEWAY_ENV}=${cfg.gateway.id} is set but ${GATEWAY_KEY_ENV} is not`;
@@ -146,7 +149,8 @@ export function gatewayWireModel(model, gateway) {
146
149
  export function planTransport(model, opts = {}) {
147
150
  const provider = providerForModel(model);
148
151
  const keyEnv = PROVIDERS[provider].keyEnv;
149
- const direct = providerReady(provider) || !!opts.callerKey;
152
+ // A Sign in with ChatGPT credential funds OpenAI directly when no key does (ADR 0126).
153
+ const direct = providerReady(provider) || !!opts.callerKey || (provider === 'openai' && chatGptSignInRoutesOpenAi());
150
154
  if (direct || provider === 'ollama') {
151
155
  return { provider, transport: 'direct', funded: true, keyEnv };
152
156
  }
@@ -28,7 +28,7 @@
28
28
  * adapter replays no providerState: a chat-completions wire has no place
29
29
  * for Claude's signed blocks or OpenAI's reasoning items.
30
30
  */
31
- import type { ToolChoiceMode } from './contract.ts';
31
+ import type { ReasoningLevel, ToolChoiceMode } from './contract.ts';
32
32
  import { ChatCompletionsAdapter } from './chatCompletionsAdapter.ts';
33
33
  import type { ChatFailure } from './chatCompletionsAdapter.ts';
34
34
  export interface GatewayAdapterOptions {
@@ -44,6 +44,12 @@ export declare class GatewayAdapter extends ChatCompletionsAdapter {
44
44
  protected endpointUrl(): string;
45
45
  protected headers(): Record<string, string>;
46
46
  protected wireModelName(model: string): string;
47
+ /**
48
+ * A gateway reads `reasoning_effort` up to `high` whatever the id: the
49
+ * words above it (`xhigh`, `max`) are not accepted across gateways, so
50
+ * they go as `high`, marked on the span (ADR 0117).
51
+ */
52
+ protected reasoningCeiling(): ReasoningLevel;
47
53
  /** A gateway passes tool_choice to the upstream as asked. */
48
54
  protected toolChoiceModes(): readonly ToolChoiceMode[];
49
55
  protected missingRequirement(): ChatFailure | undefined;
@@ -52,6 +52,14 @@ export class GatewayAdapter extends ChatCompletionsAdapter {
52
52
  wireModelName(model) {
53
53
  return this.#cfg ? gatewayWireModel(model, this.#cfg.gateway) : model;
54
54
  }
55
+ /**
56
+ * A gateway reads `reasoning_effort` up to `high` whatever the id: the
57
+ * words above it (`xhigh`, `max`) are not accepted across gateways, so
58
+ * they go as `high`, marked on the span (ADR 0117).
59
+ */
60
+ reasoningCeiling() {
61
+ return 'high';
62
+ }
55
63
  /** A gateway passes tool_choice to the upstream as asked. */
56
64
  toolChoiceModes() {
57
65
  return ['auto', 'none', 'required', 'named'];
@@ -50,7 +50,7 @@ import { endpointFromEnv, endpointProblems, platformModel } from './endpoints.js
50
50
  import { CARRIED_PARTS_KIND, GEMINI_PROVIDER, GENAI_PART_KIND, MINTED_CALL_ID_PREFIX, PLACEHOLDER_THOUGHT_SIGNATURE, THOUGHT_SIGNATURE_KIND, isCarriedWirePart, } from './geminiState.js';
51
51
  import { currentTurnStart, providerStateOf } from './providerState.js';
52
52
  import { classifyError, errorStatus, retryUntilFirstYield } from './retry.js';
53
- import { reasoningConfig } from './reasoning.js';
53
+ import { isAboveHigh, reasoningConfig } from './reasoning.js';
54
54
  import { setLlmSpanAttribute } from '../observability/tracer.js';
55
55
  /**
56
56
  * The provider id this adapter reports and writes its state under, and the
@@ -445,11 +445,17 @@ function toolConfigFor(request) {
445
445
  : undefined;
446
446
  return mode ? { functionCallingConfig: { mode } } : undefined;
447
447
  }
448
- /** ADR 0047's mapping for this model, with the thoughts asked for unless the setting is `none`. */
448
+ /**
449
+ * ADR 0047's mapping for this model, with the thoughts asked for unless the
450
+ * setting is `none`. Gemini's top is `HIGH` (or `high`'s budget on 2.x), so
451
+ * `xhigh` and `max` go as that, marked on the span (ADR 0117).
452
+ */
449
453
  function thinkingConfigFor(model, setting) {
450
454
  const mapped = reasoningConfig(model, setting).thinkingConfig;
451
455
  if (!mapped)
452
456
  return undefined;
457
+ if (isAboveHigh(setting))
458
+ setLlmSpanAttribute('llm.reasoning.weakened', setting);
453
459
  const none = setting === 'none' || (typeof setting === 'object' && setting.budget_tokens <= 0);
454
460
  return none ? { ...mapped } : { ...mapped, includeThoughts: true };
455
461
  }
@@ -271,11 +271,15 @@ export interface ModelRequestOptions {
271
271
  * which every adapter maps to what the budget maps to today (0 included).
272
272
  * - else `reasoningEffort`: a level word is that level, and `minimal` is
273
273
  * `none` (ADR 0047's rendering of it on the first GPT-5 generation).
274
+ * The words above `high` (`xhigh`, `max`) win over a thinkingConfig that
275
+ * says `high` (`HIGH`, or `high`'s budget of 16,384), which is what the
276
+ * compiler writes beside them (ADR 0117); a thinkingConfig that says less
277
+ * keeps its own reading.
274
278
  * Lossy, and absent: a budget of -1 (Gemini's dynamic thinking, which is
275
- * the provider's default), `THINKING_LEVEL_UNSPECIFIED`, the effort words
276
- * `xhigh` and `max`, and `includeThoughts`. The compiler writes the effort
277
- * word beside thinkingConfig from one setting, so preferring thinkingConfig
278
- * loses nothing it wrote; only the older spelling can set the two apart.
279
+ * the provider's default), `THINKING_LEVEL_UNSPECIFIED`, and
280
+ * `includeThoughts`. The compiler writes the effort word beside
281
+ * thinkingConfig from one setting, so preferring thinkingConfig loses
282
+ * nothing it wrote; only the older spelling can set the two apart.
279
283
  */
280
284
  export declare function reasoningOf(config: GenerateContentConfig | undefined): ReasoningSetting | undefined;
281
285
  /** The function-calling mode as a ToolChoice, and VALIDATED as strict tools. The native request builder reads an agent's config through it too. */
@@ -152,7 +152,7 @@
152
152
  import { FinishReason, FunctionCallingConfigMode } from '@google/genai';
153
153
  import { ERROR_RETRYABLE_KEY, ERROR_STATUS_KEY, errorText, withRetryVerdict } from './errorResponse.js';
154
154
  import { CARRIED_PARTS_KIND, GEMINI_PROVIDER, GENAI_PART_KIND, MINTED_CALL_ID_PREFIX, THOUGHT_SIGNATURE_KIND } from './geminiState.js';
155
- import { reasoningConfig } from './reasoning.js';
155
+ import { REASONING_BUDGETS, reasoningConfig } from './reasoning.js';
156
156
  import { contractToolDeclaration, nativeToolOf, toContractJsonSchema } from './schemaNormalize.js';
157
157
  import { MAX_VALUE_DEPTH, nestedDeeperThan } from '../runtime/valueDepth.js';
158
158
  /**
@@ -473,7 +473,15 @@ export function messagesToContents(history) {
473
473
  }
474
474
  // Maps, not object literals: a config value such as `constructor` must read as nothing.
475
475
  const THINKING_LEVELS = new Map([['MINIMAL', 'none'], ['LOW', 'low'], ['MEDIUM', 'medium'], ['HIGH', 'high']]);
476
- const EFFORT_LEVELS = new Map([['none', 'none'], ['minimal', 'none'], ['low', 'low'], ['medium', 'medium'], ['high', 'high']]);
476
+ const EFFORT_LEVELS = new Map([
477
+ ['none', 'none'],
478
+ ['minimal', 'none'],
479
+ ['low', 'low'],
480
+ ['medium', 'medium'],
481
+ ['high', 'high'],
482
+ ['xhigh', 'xhigh'],
483
+ ['max', 'max'],
484
+ ]);
477
485
  /**
478
486
  * The ReasoningSetting a generateContentConfig asks for, where one is exact:
479
487
  * - `thinkingConfig.thinkingLevel`: `MINIMAL` is `none` (ADR 0047's
@@ -482,24 +490,30 @@ const EFFORT_LEVELS = new Map([['none', 'none'], ['minimal', 'none'], ['low', 'l
482
490
  * which every adapter maps to what the budget maps to today (0 included).
483
491
  * - else `reasoningEffort`: a level word is that level, and `minimal` is
484
492
  * `none` (ADR 0047's rendering of it on the first GPT-5 generation).
493
+ * The words above `high` (`xhigh`, `max`) win over a thinkingConfig that
494
+ * says `high` (`HIGH`, or `high`'s budget of 16,384), which is what the
495
+ * compiler writes beside them (ADR 0117); a thinkingConfig that says less
496
+ * keeps its own reading.
485
497
  * Lossy, and absent: a budget of -1 (Gemini's dynamic thinking, which is
486
- * the provider's default), `THINKING_LEVEL_UNSPECIFIED`, the effort words
487
- * `xhigh` and `max`, and `includeThoughts`. The compiler writes the effort
488
- * word beside thinkingConfig from one setting, so preferring thinkingConfig
489
- * loses nothing it wrote; only the older spelling can set the two apart.
498
+ * the provider's default), `THINKING_LEVEL_UNSPECIFIED`, and
499
+ * `includeThoughts`. The compiler writes the effort word beside
500
+ * thinkingConfig from one setting, so preferring thinkingConfig loses
501
+ * nothing it wrote; only the older spelling can set the two apart.
490
502
  */
491
503
  export function reasoningOf(config) {
492
504
  const cfg = (config ?? {});
505
+ const word = EFFORT_LEVELS.get(cfg.reasoningEffort);
506
+ const above = word === 'xhigh' || word === 'max' ? word : undefined;
493
507
  const thinking = cfg.thinkingConfig;
494
508
  if (isObject(thinking)) {
495
509
  const level = THINKING_LEVELS.get(thinking.thinkingLevel);
496
510
  if (level)
497
- return level;
511
+ return above && level === 'high' ? above : level;
498
512
  const budget = thinking.thinkingBudget;
499
513
  if (typeof budget === 'number' && Number.isInteger(budget) && budget >= 0)
500
- return { budget_tokens: budget };
514
+ return above && budget === REASONING_BUDGETS.high ? above : { budget_tokens: budget };
501
515
  }
502
- return EFFORT_LEVELS.get(cfg.reasoningEffort);
516
+ return word;
503
517
  }
504
518
  function nativeToolsOf(llmRequest) {
505
519
  const tools = new Set();
@@ -187,8 +187,10 @@ export declare class GptAdapter implements ModelAdapter {
187
187
  * The `reasoning` request field for one setting (ADR 0047), or undefined to
188
188
  * send none. OpenAI's reasoning ids ask for summaries, plus the effort in
189
189
  * that model's own word (`reasoningConfig`: `none` is `minimal` on the
190
- * first GPT-5 generation and `low` on the o-series). Other ids: nothing. A
191
- * 400 on the field is retried once without it.
190
+ * first GPT-5 generation and `low` on the o-series), held at the model's
191
+ * ceiling (`xhigh` from GPT-5.2, else `high`; ADR 0117), a held level
192
+ * marked on the span. Other ids: nothing. A 400 on the field is retried
193
+ * once without it.
192
194
  */
193
195
  reasoningParam(setting: ReasoningSetting | undefined, model?: string): Record<string, unknown> | undefined;
194
196
  /**
@@ -45,7 +45,7 @@
45
45
  import { endpointFromEnv, entraTokenSource, nativeSearchOn, platformModel } from './endpoints.js';
46
46
  import { errorDecision, errorText, statusDecision } from './errorResponse.js';
47
47
  import { currentTurnStart, providerStateOf } from './providerState.js';
48
- import { reasoningConfig } from './reasoning.js';
48
+ import { effortWord } from './reasoning.js';
49
49
  import { toContractJsonSchema } from './schemaNormalize.js';
50
50
  import { setLlmSpanAttribute } from '../observability/tracer.js';
51
51
  /** The providerState kind the Responses adapters write: the reasoning output items that preceded a part (ADR 0046, ADR 0050). */
@@ -423,13 +423,20 @@ export class GptAdapter {
423
423
  * The `reasoning` request field for one setting (ADR 0047), or undefined to
424
424
  * send none. OpenAI's reasoning ids ask for summaries, plus the effort in
425
425
  * that model's own word (`reasoningConfig`: `none` is `minimal` on the
426
- * first GPT-5 generation and `low` on the o-series). Other ids: nothing. A
427
- * 400 on the field is retried once without it.
426
+ * first GPT-5 generation and `low` on the o-series), held at the model's
427
+ * ceiling (`xhigh` from GPT-5.2, else `high`; ADR 0117), a held level
428
+ * marked on the span. Other ids: nothing. A 400 on the field is retried
429
+ * once without it.
428
430
  */
429
431
  reasoningParam(setting, model = this.model) {
430
432
  if (!isOpenAiReasoningModel(model))
431
433
  return undefined;
432
- return { summary: 'auto', ...(setting !== undefined ? { effort: reasoningConfig(model, setting).reasoningEffort } : {}) };
434
+ if (setting === undefined)
435
+ return { summary: 'auto' };
436
+ const { word, weakened } = effortWord(model, setting);
437
+ if (weakened)
438
+ setLlmSpanAttribute('llm.reasoning.weakened', weakened);
439
+ return { summary: 'auto', effort: word };
433
440
  }
434
441
  /**
435
442
  * Whether this id carries its reasoning across the steps of a tool loop
@@ -63,7 +63,9 @@ export declare class GrokAdapter extends GptAdapter {
63
63
  * `high`; xAI's own default is `high`, and reasoning cannot be turned off).
64
64
  * The request's setting maps through ADR 0047's table, which sends `none`
65
65
  * as `low`; with none set, DEFAULT_GROK_REASONING_EFFORT (medium) is pinned,
66
- * since effort is part of the price. Other grok ids take no field.
66
+ * since effort is part of the price. grok-4.7 also takes `xhigh`; `max`,
67
+ * and `xhigh` on 4.5 and 4.6, go as the model's highest word, marked on the
68
+ * span (ADR 0117). Other grok ids take no field.
67
69
  */
68
70
  reasoningParam(setting: ReasoningSetting | undefined, model?: string): Record<string, unknown> | undefined;
69
71
  /**
@@ -26,8 +26,9 @@
26
26
  */
27
27
  import { DEFAULT_GROK_REASONING_EFFORT } from '../config.js';
28
28
  import { GptAdapter } from './gptAdapter.js';
29
- import { reasoningConfig } from './reasoning.js';
29
+ import { effortWord } from './reasoning.js';
30
30
  import { collectionIdsFromEnv, collectionsMaxResultsFromEnv, xaiWebSearchParamsFromEnv, xSearchParamsFromEnv, } from '../tools/xaiSearchParams.js';
31
+ import { setLlmSpanAttribute } from '../observability/tracer.js';
31
32
  export const XAI_BASE_URL = 'https://api.x.ai/v1';
32
33
  /**
33
34
  * The ids that take a reasoning effort and return encrypted reasoning:
@@ -81,12 +82,19 @@ export class GrokAdapter extends GptAdapter {
81
82
  * `high`; xAI's own default is `high`, and reasoning cannot be turned off).
82
83
  * The request's setting maps through ADR 0047's table, which sends `none`
83
84
  * as `low`; with none set, DEFAULT_GROK_REASONING_EFFORT (medium) is pinned,
84
- * since effort is part of the price. Other grok ids take no field.
85
+ * since effort is part of the price. grok-4.7 also takes `xhigh`; `max`,
86
+ * and `xhigh` on 4.5 and 4.6, go as the model's highest word, marked on the
87
+ * span (ADR 0117). Other grok ids take no field.
85
88
  */
86
89
  reasoningParam(setting, model = this.model) {
87
90
  if (!GROK_REASONING_IDS.test(model))
88
91
  return undefined;
89
- return { effort: setting === undefined ? DEFAULT_GROK_REASONING_EFFORT : reasoningConfig(model, setting).reasoningEffort };
92
+ if (setting === undefined)
93
+ return { effort: DEFAULT_GROK_REASONING_EFFORT };
94
+ const { word, weakened } = effortWord(model, setting);
95
+ if (weakened)
96
+ setLlmSpanAttribute('llm.reasoning.weakened', weakened);
97
+ return { effort: word };
90
98
  }
91
99
  /**
92
100
  * grok-4.5, grok-4.6 and grok-4.7 carry their reasoning across a tool loop
@@ -68,7 +68,8 @@
68
68
  * goes as its Kimi word (`medium` → `high`, a budget → the level that
69
69
  * covers it), `none` as `low` since K3 cannot switch thinking off, and a
70
70
  * request with none pins DEFAULT_KIMI_REASONING_EFFORT (lib/config.ts).
71
- * `max` has no contract level, so no request sends it.
71
+ * `reasoning: max` sends `max`, and `xhigh` rounds up to it (ADR 0117):
72
+ * Moonshot's costliest effort, and its default, which the pin avoids.
72
73
  * K2.x models take no reasoning_effort: a `thinking: { type }` switch
73
74
  * instead, so `none` (or a budget of 0) sends `disabled` and any other
74
75
  * setting sends nothing. K2.7 Code (and its highspeed variant) cannot
@@ -68,7 +68,8 @@
68
68
  * goes as its Kimi word (`medium` → `high`, a budget → the level that
69
69
  * covers it), `none` as `low` since K3 cannot switch thinking off, and a
70
70
  * request with none pins DEFAULT_KIMI_REASONING_EFFORT (lib/config.ts).
71
- * `max` has no contract level, so no request sends it.
71
+ * `reasoning: max` sends `max`, and `xhigh` rounds up to it (ADR 0117):
72
+ * Moonshot's costliest effort, and its default, which the pin avoids.
72
73
  * K2.x models take no reasoning_effort: a `thinking: { type }` switch
73
74
  * instead, so `none` (or a budget of 0) sends `disabled` and any other
74
75
  * setting sends nothing. K2.7 Code (and its highspeed variant) cannot
@@ -118,7 +119,6 @@
118
119
  */
119
120
  import { DEFAULT_KIMI_REASONING_EFFORT } from '../config.js';
120
121
  import { ChatCompletionsAdapter, reasonsNotAtAll } from './chatCompletionsAdapter.js';
121
- import { reasoningConfig } from './reasoning.js';
122
122
  import { trimTrailingSlashes } from './urls.js';
123
123
  export const MOONSHOT_BASE_URL = 'https://api.moonshot.ai/v1';
124
124
  /** The flagship takes `reasoning_effort`; the K2 generation takes a `thinking` switch. */
@@ -191,7 +191,7 @@ export class KimiAdapter extends ChatCompletionsAdapter {
191
191
  if (isKimiK3(model)) {
192
192
  if (setting === undefined)
193
193
  return { reasoning_effort: DEFAULT_KIMI_REASONING_EFFORT };
194
- const word = reasoningConfig(model, setting).reasoningEffort;
194
+ const word = this.effortFor(model, setting);
195
195
  return { reasoning_effort: word === 'none' ? 'low' : word }; // K3 cannot switch thinking off
196
196
  }
197
197
  // K2.7 Code always thinks, and Moonshot refuses `disabled` for it.
@@ -12,15 +12,43 @@ import type { ReasoningLevel, ReasoningSetting } from './contract.ts';
12
12
  /**
13
13
  * Thinking tokens per level, for the providers that take a budget (Claude,
14
14
  * Gemini 2.x). The same table rounds a `budget_tokens` value up to a level
15
- * for the providers that take only a level or an effort word.
15
+ * for the providers that take only a level or an effort word. `xhigh` and
16
+ * `max` take `high`'s budget (ADR 0117): above it a non-streaming Claude
17
+ * request passes the Anthropic SDK's ceiling (ADR 0047), so a budget path
18
+ * sends them as `high`, and a budget never rounds up to either.
16
19
  */
17
20
  export declare const REASONING_BUDGETS: Readonly<Record<ReasoningLevel, number>>;
21
+ /** The levels in order of how hard they ask the model to think. */
22
+ export declare const REASONING_ORDER: readonly ReasoningLevel[];
23
+ /** The levels above `high`, which only some models take (ADR 0117). */
24
+ export declare const isAboveHigh: (setting: ReasoningSetting | undefined) => setting is 'xhigh' | 'max';
25
+ /**
26
+ * The highest level the direct adapter for `model` sends as asked (ADR
27
+ * 0117); a level above it goes as this one. `max` where every level
28
+ * passes: Claude's adaptive generations (the Claude adapter sends its budget
29
+ * generations `high`'s budget) and Kimi K3. OpenAI's GPT-5.2 and later and
30
+ * grok-4.7 stop at `xhigh`; other OpenAI and Grok ids, Gemini and Ollama at
31
+ * `high`. A gateway stops at `high` whatever the id (gatewayAdapter.ts).
32
+ */
33
+ export declare function effortCeiling(model: string): ReasoningLevel;
34
+ /**
35
+ * The effort word an adapter sends for one setting: ADR 0047's word for the
36
+ * model, held at `ceiling` (default effortCeiling(model)). `weakened` is the
37
+ * level asked for when the word sent is below it; the adapter marks the span
38
+ * `llm.reasoning.weakened` with it.
39
+ */
40
+ export declare function effortWord(model: string, setting: ReasoningSetting, ceiling?: ReasoningLevel): {
41
+ word: string;
42
+ weakened?: ReasoningLevel;
43
+ };
18
44
  /**
19
45
  * The generateContentConfig fields a `reasoning:` setting becomes for one
20
46
  * model (ADR 0047). `reasoningEffort` is always set, so the gateway (which
21
47
  * may serve any id) carries the level too; the Claude adapter reads it first
22
48
  * on the adaptive generations (ADR 0049), and the genai SDK drops it from a
23
49
  * Gemini request. Claude and Gemini also get the `thinkingConfig` their
24
- * adapters read.
50
+ * adapters read. `xhigh` and `max` are written as asked (Kimi's `xhigh` as
51
+ * `max`), beside `high`'s thinkingConfig: the adapter holds the word at its
52
+ * model's ceiling (effortWord), so the setting reaches it intact (ADR 0117).
25
53
  */
26
54
  export declare function reasoningConfig(model: string, setting: ReasoningSetting): Record<string, unknown>;
@@ -2,10 +2,25 @@ import { providerForModel } from './providerMap.js';
2
2
  /**
3
3
  * Thinking tokens per level, for the providers that take a budget (Claude,
4
4
  * Gemini 2.x). The same table rounds a `budget_tokens` value up to a level
5
- * for the providers that take only a level or an effort word.
5
+ * for the providers that take only a level or an effort word. `xhigh` and
6
+ * `max` take `high`'s budget (ADR 0117): above it a non-streaming Claude
7
+ * request passes the Anthropic SDK's ceiling (ADR 0047), so a budget path
8
+ * sends them as `high`, and a budget never rounds up to either.
6
9
  */
7
- export const REASONING_BUDGETS = { none: 0, low: 2048, medium: 8192, high: 16384 };
8
- const GEMINI_THINKING_LEVEL = { none: 'MINIMAL', low: 'LOW', medium: 'MEDIUM', high: 'HIGH' };
10
+ export const REASONING_BUDGETS = { none: 0, low: 2048, medium: 8192, high: 16384, xhigh: 16384, max: 16384 };
11
+ const GEMINI_THINKING_LEVEL = { none: 'MINIMAL', low: 'LOW', medium: 'MEDIUM', high: 'HIGH', xhigh: 'HIGH', max: 'HIGH' };
12
+ /** The levels in order of how hard they ask the model to think. */
13
+ export const REASONING_ORDER = ['none', 'low', 'medium', 'high', 'xhigh', 'max'];
14
+ /** The levels above `high`, which only some models take (ADR 0117). */
15
+ export const isAboveHigh = (setting) => setting === 'xhigh' || setting === 'max';
16
+ /** An OpenAI id that takes `xhigh`: GPT-5.2 and later (the o-series and GPT-5 and 5.1 stop at `high`). */
17
+ function openAiTakesXhigh(model) {
18
+ const m = /^gpt-(\d{1,3})(?:\.(\d{1,3}))?/.exec(model);
19
+ if (!m)
20
+ return false;
21
+ const major = Number(m[1]);
22
+ return major > 5 || (major === 5 && Number(m[2] ?? 0) >= 2);
23
+ }
9
24
  /** The smallest level whose budget covers the setting: never less thought than asked. */
10
25
  function reasoningLevel(setting) {
11
26
  if (typeof setting === 'string')
@@ -29,18 +44,54 @@ function reasoningEffort(model, level) {
29
44
  case 'xai':
30
45
  return level === 'none' ? 'low' : level; // Grok 4.5/4.7 cannot stop reasoning
31
46
  case 'moonshot':
32
- return level === 'medium' ? 'high' : level; // K3 has low | high | max
47
+ // K3 has low | high | max: a missing word rounds up.
48
+ return level === 'medium' ? 'high' : level === 'xhigh' ? 'max' : level;
33
49
  default:
34
50
  return level;
35
51
  }
36
52
  }
53
+ /**
54
+ * The highest level the direct adapter for `model` sends as asked (ADR
55
+ * 0117); a level above it goes as this one. `max` where every level
56
+ * passes: Claude's adaptive generations (the Claude adapter sends its budget
57
+ * generations `high`'s budget) and Kimi K3. OpenAI's GPT-5.2 and later and
58
+ * grok-4.7 stop at `xhigh`; other OpenAI and Grok ids, Gemini and Ollama at
59
+ * `high`. A gateway stops at `high` whatever the id (gatewayAdapter.ts).
60
+ */
61
+ export function effortCeiling(model) {
62
+ switch (providerForModel(model)) {
63
+ case 'anthropic':
64
+ case 'moonshot':
65
+ return 'max';
66
+ case 'openai':
67
+ return openAiTakesXhigh(model) ? 'xhigh' : 'high';
68
+ case 'xai':
69
+ return /^grok-4\.7(?!\d)/.test(model) ? 'xhigh' : 'high';
70
+ default:
71
+ return 'high';
72
+ }
73
+ }
74
+ /**
75
+ * The effort word an adapter sends for one setting: ADR 0047's word for the
76
+ * model, held at `ceiling` (default effortCeiling(model)). `weakened` is the
77
+ * level asked for when the word sent is below it; the adapter marks the span
78
+ * `llm.reasoning.weakened` with it.
79
+ */
80
+ export function effortWord(model, setting, ceiling = effortCeiling(model)) {
81
+ const word = reasoningConfig(model, setting).reasoningEffort;
82
+ if (!isAboveHigh(setting) || REASONING_ORDER.indexOf(word) <= REASONING_ORDER.indexOf(ceiling))
83
+ return { word };
84
+ return { word: ceiling, weakened: setting };
85
+ }
37
86
  /**
38
87
  * The generateContentConfig fields a `reasoning:` setting becomes for one
39
88
  * model (ADR 0047). `reasoningEffort` is always set, so the gateway (which
40
89
  * may serve any id) carries the level too; the Claude adapter reads it first
41
90
  * on the adaptive generations (ADR 0049), and the genai SDK drops it from a
42
91
  * Gemini request. Claude and Gemini also get the `thinkingConfig` their
43
- * adapters read.
92
+ * adapters read. `xhigh` and `max` are written as asked (Kimi's `xhigh` as
93
+ * `max`), beside `high`'s thinkingConfig: the adapter holds the word at its
94
+ * model's ceiling (effortWord), so the setting reaches it intact (ADR 0117).
44
95
  */
45
96
  export function reasoningConfig(model, setting) {
46
97
  const level = reasoningLevel(setting);