@exellix/ai-tasks 8.2.5 → 8.4.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 (124) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +17 -13
  3. package/RUNTASK_REQUEST.md +3 -3
  4. package/dist/aiSkillsUpstreamExports.d.ts +1 -1
  5. package/dist/aiSkillsUpstreamExports.d.ts.map +1 -1
  6. package/dist/aiSkillsUpstreamExports.js +1 -1
  7. package/dist/aiSkillsUpstreamExports.js.map +1 -1
  8. package/dist/analysis/analyzeRunTaskRequest.d.ts +2 -2
  9. package/dist/analysis/analyzeRunTaskRequest.d.ts.map +1 -1
  10. package/dist/analysis/analyzeRunTaskRequest.js +11 -4
  11. package/dist/analysis/analyzeRunTaskRequest.js.map +1 -1
  12. package/dist/builders/task-request-builder.d.ts +9 -12
  13. package/dist/builders/task-request-builder.d.ts.map +1 -1
  14. package/dist/builders/task-request-builder.js +22 -15
  15. package/dist/builders/task-request-builder.js.map +1 -1
  16. package/dist/compile/compileTaskConfiguration.d.ts.map +1 -1
  17. package/dist/compile/compileTaskConfiguration.js +4 -3
  18. package/dist/compile/compileTaskConfiguration.js.map +1 -1
  19. package/dist/core/task-sdk.d.ts.map +1 -1
  20. package/dist/core/task-sdk.js +10 -11
  21. package/dist/core/task-sdk.js.map +1 -1
  22. package/dist/errors/modelConfigRequiredError.d.ts +9 -0
  23. package/dist/errors/modelConfigRequiredError.d.ts.map +1 -0
  24. package/dist/errors/modelConfigRequiredError.js +20 -0
  25. package/dist/errors/modelConfigRequiredError.js.map +1 -0
  26. package/dist/errors/runTaskExecutionError.d.ts +2 -1
  27. package/dist/errors/runTaskExecutionError.d.ts.map +1 -1
  28. package/dist/errors/runTaskExecutionError.js +1 -1
  29. package/dist/errors/runTaskExecutionError.js.map +1 -1
  30. package/dist/index.d.ts +4 -4
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +3 -3
  33. package/dist/index.js.map +1 -1
  34. package/dist/internal/resolveLlmCallForXynthesis.d.ts +17 -40
  35. package/dist/internal/resolveLlmCallForXynthesis.d.ts.map +1 -1
  36. package/dist/internal/resolveLlmCallForXynthesis.js +15 -55
  37. package/dist/internal/resolveLlmCallForXynthesis.js.map +1 -1
  38. package/dist/internal/runPostStepLlmCall.d.ts +3 -17
  39. package/dist/internal/runPostStepLlmCall.d.ts.map +1 -1
  40. package/dist/internal/runPostStepLlmCall.js +31 -30
  41. package/dist/internal/runPostStepLlmCall.js.map +1 -1
  42. package/dist/invocation/defaultAiProfilesResolveOptions.d.ts +27 -0
  43. package/dist/invocation/defaultAiProfilesResolveOptions.d.ts.map +1 -0
  44. package/dist/invocation/defaultAiProfilesResolveOptions.js +43 -0
  45. package/dist/invocation/defaultAiProfilesResolveOptions.js.map +1 -0
  46. package/dist/invocation/resolveProfileInvocationRouting.d.ts +5 -3
  47. package/dist/invocation/resolveProfileInvocationRouting.d.ts.map +1 -1
  48. package/dist/invocation/resolveProfileInvocationRouting.js +30 -19
  49. package/dist/invocation/resolveProfileInvocationRouting.js.map +1 -1
  50. package/dist/logxer/packageLogxers.d.ts +0 -1
  51. package/dist/logxer/packageLogxers.d.ts.map +1 -1
  52. package/dist/observability/classifyRunTaskFailure.js +2 -2
  53. package/dist/observability/classifyRunTaskFailure.js.map +1 -1
  54. package/dist/observability/logLlmProviderInvocation.d.ts +1 -2
  55. package/dist/observability/logLlmProviderInvocation.d.ts.map +1 -1
  56. package/dist/observability/logLlmProviderInvocation.js +3 -3
  57. package/dist/observability/logLlmProviderInvocation.js.map +1 -1
  58. package/dist/packaged-tasks-client.js +1 -1
  59. package/dist/packaged-tasks-client.js.map +1 -1
  60. package/dist/post-steps/audit/runAudit.d.ts.map +1 -1
  61. package/dist/post-steps/audit/runAudit.js +6 -16
  62. package/dist/post-steps/audit/runAudit.js.map +1 -1
  63. package/dist/post-steps/polish/runPolish.d.ts +2 -0
  64. package/dist/post-steps/polish/runPolish.d.ts.map +1 -1
  65. package/dist/post-steps/polish/runPolish.js +5 -9
  66. package/dist/post-steps/polish/runPolish.js.map +1 -1
  67. package/dist/post-steps/resolvePostStepConfig.d.ts +4 -35
  68. package/dist/post-steps/resolvePostStepConfig.d.ts.map +1 -1
  69. package/dist/post-steps/resolvePostStepConfig.js +9 -36
  70. package/dist/post-steps/resolvePostStepConfig.js.map +1 -1
  71. package/dist/strategies/direct-execution-strategy.d.ts.map +1 -1
  72. package/dist/strategies/direct-execution-strategy.js +6 -3
  73. package/dist/strategies/direct-execution-strategy.js.map +1 -1
  74. package/dist/synthesis/runStructuredSynthesisRobust.d.ts +0 -1
  75. package/dist/synthesis/runStructuredSynthesisRobust.d.ts.map +1 -1
  76. package/dist/synthesis/runStructuredSynthesisRobust.js +20 -58
  77. package/dist/synthesis/runStructuredSynthesisRobust.js.map +1 -1
  78. package/dist/types/index.d.ts +1 -1
  79. package/dist/types/index.d.ts.map +1 -1
  80. package/dist/types/llmCall.d.ts +18 -47
  81. package/dist/types/llmCall.d.ts.map +1 -1
  82. package/dist/types/llmCall.js +1 -10
  83. package/dist/types/llmCall.js.map +1 -1
  84. package/dist/types/model-config.d.ts +22 -9
  85. package/dist/types/model-config.d.ts.map +1 -1
  86. package/dist/types/model-config.js +62 -13
  87. package/dist/types/model-config.js.map +1 -1
  88. package/dist/types/task-types.d.ts +16 -12
  89. package/dist/types/task-types.d.ts.map +1 -1
  90. package/dist/types/task-types.js.map +1 -1
  91. package/dist/utilities/runUtility.d.ts.map +1 -1
  92. package/dist/utilities/runUtility.js +8 -11
  93. package/dist/utilities/runUtility.js.map +1 -1
  94. package/dist/utils/aiProfileModelFormat.d.ts.map +1 -1
  95. package/dist/utils/aiProfileModelFormat.js +8 -65
  96. package/dist/utils/aiProfileModelFormat.js.map +1 -1
  97. package/dist/utils/resolveAiProfileModel.d.ts +5 -2
  98. package/dist/utils/resolveAiProfileModel.d.ts.map +1 -1
  99. package/dist/utils/resolveAiProfileModel.js +3 -1
  100. package/dist/utils/resolveAiProfileModel.js.map +1 -1
  101. package/dist/utils/resolveRunTaskModelReferences.d.ts +1 -1
  102. package/dist/utils/resolveRunTaskModelReferences.d.ts.map +1 -1
  103. package/dist/utils/resolveRunTaskModelReferences.js +8 -3
  104. package/dist/utils/resolveRunTaskModelReferences.js.map +1 -1
  105. package/dist/utils/routeModelConfigSlots.d.ts +1 -1
  106. package/dist/utils/routeModelConfigSlots.d.ts.map +1 -1
  107. package/dist/utils/routeModelConfigSlots.js +1 -1
  108. package/dist/utils/routeModelConfigSlots.js.map +1 -1
  109. package/dist/validation/helpers.d.ts +5 -2
  110. package/dist/validation/helpers.d.ts.map +1 -1
  111. package/dist/validation/helpers.js +65 -22
  112. package/dist/validation/helpers.js.map +1 -1
  113. package/dist/validation/types.d.ts +1 -1
  114. package/dist/validation/types.d.ts.map +1 -1
  115. package/dist/validation/validateRunTaskConfig.d.ts.map +1 -1
  116. package/dist/validation/validateRunTaskConfig.js +2 -1
  117. package/dist/validation/validateRunTaskConfig.js.map +1 -1
  118. package/documenations/upstream-feature-requests/README.md +21 -0
  119. package/documenations/upstream-feature-requests/ai-skills-orchestrator-invoke-contract-5.9.md +151 -0
  120. package/documenations/upstream-feature-requests/ai-tasks-wrap-up-after-upstream.md +126 -0
  121. package/documenations/upstream-feature-requests/funcx-openrouter-model-id-pass-through.md +105 -0
  122. package/documenations/upstream-feature-requests/xynthesis-openrouter-wire-model-double-prefix-bug.md +191 -0
  123. package/documenations/upstream-feature-requests/xynthesis-orchestrator-invoke-contract-4.2.md +175 -0
  124. package/package.json +6 -5
@@ -0,0 +1,21 @@
1
+ # Upstream feature requests (from `@exellix/ai-tasks`)
2
+
3
+ Fix reports filed for sibling packages. Hand these to the package owners; after they ship, complete the ai-tasks wrap-up.
4
+
5
+ ## Invoke contract (8.4+) — **current priority**
6
+
7
+ | Package | Document | Status |
8
+ |---------|----------|--------|
9
+ | `@exellix/ai-skills` ≥ 5.9 | [ai-skills-orchestrator-invoke-contract-5.9.md](./ai-skills-orchestrator-invoke-contract-5.9.md) | **open** — `reasoningEffort` on request |
10
+ | `@exellix/xynthesis` ≥ 4.2 | [xynthesis-orchestrator-invoke-contract-4.2.md](./xynthesis-orchestrator-invoke-contract-4.2.md) | **open** — `reasoningEffort`, temperature/topP audit, docs |
11
+ | `@exellix/ai-tasks` | [ai-tasks-wrap-up-after-upstream.md](./ai-tasks-wrap-up-after-upstream.md) | **blocked** on above |
12
+
13
+ ## Older / parallel tracks
14
+
15
+ | Package | Document |
16
+ |---------|----------|
17
+ | `@exellix/ai-skills` | [ai-skills-llm-observability.md](./ai-skills-llm-observability.md) |
18
+ | `@exellix/xynthesis` | [xynthesis-llm-observability.md](./xynthesis-llm-observability.md) |
19
+ | `@x12i/logxer` | [logxer-failure-classification-and-causal-diagnostics.md](./logxer-failure-classification-and-causal-diagnostics.md) |
20
+ | `@x12i/funcx` | [funcx-openrouter-model-id-pass-through.md](./funcx-openrouter-model-id-pass-through.md) |
21
+ | `@exellix/xynthesis` | [xynthesis-openrouter-wire-model-double-prefix-bug.md](./xynthesis-openrouter-wire-model-double-prefix-bug.md) |
@@ -0,0 +1,151 @@
1
+ # `@exellix/ai-skills` — orchestrator invoke contract fixes (≥ 5.9)
2
+
3
+ Status: **open** — blocks clean `@exellix/ai-tasks` 8.4+ integration
4
+ Owner: `@exellix/ai-skills`
5
+ Filed by: `@exellix/ai-tasks`
6
+ Consumer: `@exellix/ai-tasks` 8.4.0, graph-engine 7.x (`RunTaskRequest.modelConfig` triplet)
7
+ Pinned in ai-tasks today: `@exellix/ai-skills@5.9.11`
8
+
9
+ Related (older, partially superseded): [`ai-skills-llm-observability.md`](./ai-skills-llm-observability.md)
10
+
11
+ ---
12
+
13
+ ## Summary
14
+
15
+ **MAIN** skill invocation should follow the same contract as xynthesis 4.2+:
16
+
17
+ | Orchestrator sends | Package owns internally |
18
+ |--------------------|-------------------------|
19
+ | Wire **model** (`model` / `modelId`) | — |
20
+ | **temperature**, **topP**, other sampling knobs | — |
21
+ | **reasoningEffort** (for Optimixer / provider) | — |
22
+ | — | **max completion tokens** (Optimixer + Catalox catalog) |
23
+
24
+ ai-skills **5.9.11 already enforces the right side** (`ModelConfig.maxTokens?: never`, Optimixer merge before invoke). The remaining gap is the **left side**: the public request must **ask for `reasoningEffort`**, not only read it from Catalox.
25
+
26
+ ---
27
+
28
+ ## Already correct in 5.9.11 (no change needed)
29
+
30
+ ### 1. Caller must not set completion token caps on `ModelConfig`
31
+
32
+ - `ModelConfig.maxTokens` is typed `never` and validated/rejected at invoke.
33
+ - `mergeOptimixerPredictionIntoModelConfig` / `skills-optimixer` set the effective cap.
34
+ - Trace echo uses `maxTokensRequested` on `SkillDiagnosticsTrace`, not `invokeRequest.modelConfig.maxTokens`.
35
+
36
+ **Acceptance (already met):** Hosts that pass `modelConfig: { model, temperature, topP }` without `maxTokens` succeed; hosts that pass `maxTokens` get a clear rejection or strip + anomaly log.
37
+
38
+ ### 2. Catalox catalog carries Optimixer metadata
39
+
40
+ - `SkillOptimixerCatalogSpec` includes mandatory `reasoningEffort`, `outputIntent`, etc. (`skill-optimixer-catalog.ts`).
41
+
42
+ ---
43
+
44
+ ## Required fixes
45
+
46
+ ### 1. Accept `reasoningEffort` on `RunSkillRequest` (or explicit `ModelConfig` extension)
47
+
48
+ #### Today
49
+
50
+ - `reasoningEffort` exists only on the **Catalox `ai-skills` catalog row** (`SkillOptimixerCatalogSpec.reasoningEffort`).
51
+ - `RunSkillRequest` / `ModelConfig` expose `model`, `temperature`, `topP`, … but **no `reasoningEffort`**.
52
+ - `@exellix/ai-tasks` has nothing to forward from graph-engine / `llmCall` even when the operator configures it per task.
53
+
54
+ #### Why it hurts
55
+
56
+ Orchestrators (graph-engine, ai-tasks) are the **source of truth** for per-node tuning. Catalog defaults are not enough when one skill key is reused with different reasoning depth per graph node. xynthesis 4.2 already threads `reasoningEffort` into Optimixer predict input internally; ai-skills should expose the same knob on the **host-facing** API.
57
+
58
+ #### Asked behavior
59
+
60
+ Add optional:
61
+
62
+ ```typescript
63
+ import type { OptimixerReasoningEffort } from "@x12i/optimixer";
64
+
65
+ // Option A — on ModelConfig (preferred for parity with temperature/topP):
66
+ interface ModelConfig {
67
+ // ...existing fields...
68
+ reasoningEffort?: OptimixerReasoningEffort;
69
+ }
70
+
71
+ // Option B — top-level on RunSkillRequest:
72
+ interface RunSkillRequest {
73
+ reasoningEffort?: OptimixerReasoningEffort;
74
+ }
75
+ ```
76
+
77
+ **Resolution order** (document and implement consistently):
78
+
79
+ 1. Request override (`modelConfig.reasoningEffort` or `RunSkillRequest.reasoningEffort`)
80
+ 2. Catalox `SkillOptimixerCatalogSpec.reasoningEffort`
81
+ 3. Package default (e.g. `"not-applicable"`)
82
+
83
+ Forward the resolved value into `buildAiMaxTokensPredictionInput` / Optimixer predict (same as xynthesis `resolveEffectiveMaxTokens`).
84
+
85
+ #### Acceptance
86
+
87
+ - `runSkill({ modelConfig: { model: "…", reasoningEffort: "high" } })` uses `"high"` in Optimixer predict even when catalog says `"medium"`.
88
+ - Trace / `invokeRequest` echo includes effective `reasoningEffort` (or Optimixer diagnostics slice).
89
+ - Omitting the field keeps today’s catalog-only behavior unchanged.
90
+
91
+ ---
92
+
93
+ ### 2. Document the orchestrator ↔ MAIN invoke contract (README / BREAKING)
94
+
95
+ Add a short **“Host responsibilities”** section:
96
+
97
+ - **Send:** concrete or resolved wire `model`, optional `temperature`, `topP`, `reasoningEffort`, `timeoutMs`.
98
+ - **Do not send:** `maxTokens`, `max_completion_tokens`, `max_output_tokens` on `modelConfig` (rejected).
99
+ - **Token budget:** owned by Optimixer from catalog + prompts; echo `usage.maxTokensRequested` / `metadata.maxTokensRequested` after invoke.
100
+
101
+ Point `@exellix/ai-tasks` integrators to this section instead of legacy `modelConfig.maxTokens` docs.
102
+
103
+ ---
104
+
105
+ ### 3. Update §2 in `ai-skills-llm-observability.md` (stale)
106
+
107
+ The open item **“`outputExpectation` passthrough + xynthesis-style auto-sizer on RunSkillRequest”** assumed hosts still set `modelConfig.maxTokens`. With 5.9 Optimixer, revise to:
108
+
109
+ - **Optional:** `outputExpectation` on `RunSkillRequest` to steer Optimixer output intent (parity with xynthesis), **not** to supply a literal token count.
110
+ - **Not asked:** restoring caller `modelConfig.maxTokens`.
111
+
112
+ (Can be a separate FR or folded into §1 if output intent is also catalog-only today.)
113
+
114
+ ---
115
+
116
+ ## Integration note (5.9.12 — ai-tasks)
117
+
118
+ ### `analyzeSkillRequest` root export removed
119
+
120
+ - **Today:** `analyzeSkillRequest(catalox, …)` exists in `dist/analysis/` but is **not** re-exported from `@exellix/ai-skills` package root. Use **`ExellixSkillsClient.analyzeSkillRequest`** (or export the function again from `index.js` for Catalox-only callers).
121
+ - **ai-tasks:** `analyzeRunTaskRequest` uses a short-lived `ExellixSkillsClient` until root export returns.
122
+
123
+ ### `testContentRegistryConnection` renamed
124
+
125
+ - Use **`ExellixSkillsClient.testCatalogConnection()`** (Catalox reachability; replaces gateway content-registry check).
126
+
127
+ ---
128
+
129
+ ## Optional (lower priority)
130
+
131
+ ### 4. `SkillExecutionError` in default mode
132
+
133
+ Still valuable; see [`ai-skills-llm-observability.md` §1](./ai-skills-llm-observability.md). Independent of token / reasoning contract.
134
+
135
+ ---
136
+
137
+ ## Verification checklist (for ai-skills PR)
138
+
139
+ - [ ] TypeScript: `ModelConfig` / `RunSkillRequest` exposes `reasoningEffort`.
140
+ - [ ] Unit test: request override beats catalog default in Optimixer predict input.
141
+ - [ ] Unit test: `modelConfig.maxTokens` still rejected.
142
+ - [ ] README/BREAKING: host must not send max token caps on `modelConfig`.
143
+
144
+ ---
145
+
146
+ ## After this ships — ai-tasks follow-up
147
+
148
+ See [`ai-tasks-wrap-up-after-upstream.md`](./ai-tasks-wrap-up-after-upstream.md):
149
+
150
+ - Add `reasoningEffort` to `LlmCallConfig` / tuning passthrough → `buildAiSkillsModelConfigForMain`.
151
+ - Stop forwarding any `maxTokens` / `maxTokensCap` to `runSkill`.
@@ -0,0 +1,126 @@
1
+ # `@exellix/ai-tasks` — wrap-up after upstream invoke-contract fixes
2
+
3
+ Status: **blocked** on upstream PRs until both packages ship items in:
4
+
5
+ - [`ai-skills-orchestrator-invoke-contract-5.9.md`](./ai-skills-orchestrator-invoke-contract-5.9.md)
6
+ - [`xynthesis-orchestrator-invoke-contract-4.2.md`](./xynthesis-orchestrator-invoke-contract-4.2.md)
7
+
8
+ Owner: `@exellix/ai-tasks`
9
+ Target release: **8.4.x** (or **8.5.0** if breaking `llmCall` surface)
10
+
11
+ ---
12
+
13
+ ## Contract (locked with product)
14
+
15
+ **ai-tasks sends to lower layers:**
16
+
17
+ - Model ids via `RunTaskModelConfig` triplet (`preActionModel`, `skillModel`, `postActionModel`) + optional `llmCall.model` overrides.
18
+ - `temperature`, `topP`, **`reasoningEffort`** (once upstream exposes it).
19
+ - **`outputExpectation`** on xynthesis-backed hops only (sizing intent — **not** a token count).
20
+
21
+ **ai-tasks does not send:**
22
+
23
+ - `maxTokens` / `maxTokensCap` on `modelConfig`, `runSkill`, `executeXynthesisAction`, or direct gateway repair (unless product later revives an explicit ceiling knob with a new name).
24
+
25
+ **Lower layers own:**
26
+
27
+ - MAIN caps → `@exellix/ai-skills` Optimixer + Catalox skill catalog.
28
+ - PRE/POST/scoping/utility caps → `@exellix/xynthesis` `resolveEffectiveMaxTokens` inside `executeXynthesisAction`.
29
+
30
+ ---
31
+
32
+ ## Already done in ai-tasks (this branch)
33
+
34
+ - [x] `RunTaskRequest.modelConfig` typed as `RunTaskModelConfig` (not ai-skills `ModelConfig` with `maxTokens`).
35
+ - [x] Removed `maxTokens` from `RunTaskModelConfig`; strip legacy key at ai-skills boundary.
36
+ - [x] Stopped mapping `llmCall.maxTokensCap` → `modelConfig.maxTokens` in `buildAiSkillsModelConfigForMain`.
37
+ - [x] Trace repair: `mapGatewayInvokeToTrace` uses `maxTokensRequested` only (not `modelConfig.maxTokens`).
38
+ - [x] README / BREAKING-CHANGES partial updates for modelConfig + MAIN Optimixer.
39
+
40
+ ---
41
+
42
+ ## TODO after upstream ships
43
+
44
+ ### 1. Remove broken / duplicate token budgeting
45
+
46
+ | File / area | Change |
47
+ |-------------|--------|
48
+ | `src/internal/resolveLlmCallForXynthesis.ts` | Remove `resolveMaxTokens` import; either delete token resolution or reduce to **outputExpectation-only** helper (rename e.g. `resolveLlmOutputExpectationForXynthesis`). |
49
+ | `src/internal/runPostStepLlmCall.ts` | Drop `maxTokens` / `resolveLlmCallForXynthesis` pre-resolution; pass **`outputExpectation`** (required). |
50
+ | `src/synthesis/runStructuredSynthesisRobust.ts` | Repair path: stop `resolveMaxTokens`; do not push `maxTokens` into raw AIGateway `config` unless gateway documents a non-Optimixer escape hatch. |
51
+ | `src/index.ts` | Stop re-exporting `resolveMaxTokens`; export `resolveEffectiveMaxTokens` only if graph-engine needs it (prefer not). |
52
+ | `src/utilities/runUtility.ts` | Remove `maxTokens` forward to xynthesis finalize. |
53
+ | `src/post-steps/resolvePostStepConfig.ts` | Remove env `*_MAX_TOKENS_CAP` wiring if contract is zero host caps (or keep env as deprecated no-op one release). |
54
+
55
+ ### 2. Add `reasoningEffort` to ai-tasks surface
56
+
57
+ | File | Change |
58
+ |------|--------|
59
+ | `src/types/llmCall.ts` | `reasoningEffort?: OptimixerReasoningEffort` (re-export type from `@x12i/optimixer` or xynthesis). |
60
+ | `src/types/model-config.ts` | Optional tuning field; passthrough in `modelConfigTuningPassthrough` (not stripped). |
61
+ | `src/types/task-types.ts` | Document on `llmCall` and post-step configs. |
62
+ | `buildAiSkillsModelConfigForMain` | Forward to ai-skills `modelConfig` when upstream §1 ships. |
63
+ | `runPostStepLlmCall` / `task-sdk` synthesis | Forward to `executeXynthesisAction` / structured params. |
64
+ | `src/validation/helpers.ts` | Optional enum validation if finite set. |
65
+ | `src/builders/task-request-builder.ts` | Document in `withLlmCall` JSDoc. |
66
+
67
+ ### 3. Fix xynthesis call sites (no new upstream needed except §1–2)
68
+
69
+ | Call site | Pass |
70
+ |-----------|------|
71
+ | `runPostStepLlmCall` → `executeXynthesisAction` | `outputExpectation`, `temperature`, `topP`, `sidekickAction` / `activixActionType`, `reasoningEffort` (when exists) |
72
+ | PRE structured / markdown synthesis (`task-sdk`) | Same via gateway / `runStructuredSynthesisGatewayCall` params |
73
+ | AI scoping | `outputExpectation` (already defaulted in `runScopingCall`) |
74
+
75
+ ### 4. Remove / deprecate `maxTokensCap` on `LlmCallConfig`
76
+
77
+ - **Breaking:** remove `maxTokensCap` from `LlmCallConfig`, `RunUtilityRequest.exec`, post-step env fallbacks, validation, README.
78
+ - **Or soft:** mark deprecated in 8.4.1, ignore at runtime, remove in 8.5.0.
79
+
80
+ Confirm with graph-engine before hard removal.
81
+
82
+ ### 5. Tests & docs
83
+
84
+ - [ ] `npm run build` + `tsc -p tsconfig.test.json` green (no `resolveMaxTokens` import).
85
+ - [ ] `npm run test` green (mocks via `setSynthesisInvoker` unchanged).
86
+ - [ ] Update [`README.md`](../../README.md) token-budget section (remove `llmCall.maxTokensCap` → `modelConfig.maxTokens` overlay story).
87
+ - [ ] Update [`ai-skills-llm-observability.md`](./ai-skills-llm-observability.md) §2 (stale maxTokens wording).
88
+ - [ ] Update [`xynthesis-llm-observability.md`](./xynthesis-llm-observability.md) superseded sections.
89
+ - [ ] Link all three contract docs from [`BREAKING-CHANGES.md`](../../BREAKING-CHANGES.md) 8.4 section.
90
+
91
+ ---
92
+
93
+ ## Dependency order
94
+
95
+ ```mermaid
96
+ flowchart LR
97
+ A[ai-skills: reasoningEffort on RunSkillRequest]
98
+ B[xynthesis: reasoningEffort on ExecuteXynthesisActionRequest]
99
+ C[xynthesis: verify temperature topP all paths]
100
+ D[ai-tasks: remove maxTokens forwarding]
101
+ E[ai-tasks: wire reasoningEffort + outputExpectation]
102
+ A --> E
103
+ B --> E
104
+ C --> E
105
+ D --> E
106
+ ```
107
+
108
+ Recommended sequence:
109
+
110
+ 1. Merge **xynthesis** + **ai-skills** contract PRs (can be parallel).
111
+ 2. Bump `package.json` minimums in ai-tasks.
112
+ 3. Execute **this wrap-up** checklist in one ai-tasks PR.
113
+ 4. Release note in **8.4.x** or **8.5.0** with graph-engine migration (drop `maxTokensCap` on task payloads if removed).
114
+
115
+ ---
116
+
117
+ ## Quick smoke test after wrap-up
118
+
119
+ ```bash
120
+ npm run build
121
+ npm run test
122
+ # Optional live:
123
+ # npm run test:e2e:synthesis
124
+ ```
125
+
126
+ Expect: no import of `resolveMaxTokens` from `@exellix/xynthesis/ai-actions`; live POST/PRE calls succeed with `outputExpectation` only (no `maxTokens` on request).
@@ -0,0 +1,105 @@
1
+ # `@x12i/funcx` — OpenRouter `model` pass-through (no defect; optional hardening)
2
+
3
+ Status: **informational — not a funcx bug; xynthesis fix in 4.1.8 verified**
4
+ Filed by: `@exellix/ai-tasks` (8.2.x)
5
+ Context: investigation of OpenRouter **400 invalid model ID** on xynthesis PRE/POST hops (resolved upstream)
6
+ Primary report: [`xynthesis-openrouter-wire-model-double-prefix-bug.md`](./xynthesis-openrouter-wire-model-double-prefix-bug.md)
7
+
8
+ ---
9
+
10
+ ## Summary
11
+
12
+ During live E2E (`test/e2e/ai-profiles-live.test.ts`), OpenRouter rejected:
13
+
14
+ ```text
15
+ openrouter/google/gemini-2.5-flash-lite is not a valid model ID
16
+ ```
17
+
18
+ Tracing the call stack shows **funcx sends exactly the `model` string it receives** from `@exellix/xynthesis` **`FuncxInvoker.askFuncx`** — no transformation, strip, or re-prefix inside funcx.
19
+
20
+ **Fix belongs in xynthesis** (`wireModelId` formatting). **Do not** add a funcx workaround that strips `openrouter/` unless funcx explicitly documents that callers may send gateway-shaped ids (that would hide upstream bugs).
21
+
22
+ ---
23
+
24
+ ## Call chain (verified)
25
+
26
+ | Layer | Responsibility |
27
+ |-------|----------------|
28
+ | ai-tasks | Passes ai-profiles **alias** on `llmCall.model` / `executeXynthesisAction({ model: "cheap" })` |
29
+ | xynthesis | `resolveXynthesisModel` → **`wireModelId`** (bug: double `openrouter/` prefix) |
30
+ | xynthesis `FuncxInvoker` | `client.ask(userPrompt, { model: resolved.wireModelId, … })` |
31
+ | funcx | Forwards `model` to `https://openrouter.ai/api/v1/chat/completions` |
32
+ | OpenRouter | Expects slug like `google/gemini-2.5-flash-lite` |
33
+
34
+ Funcx activity log (from live run) shows the invalid model in the **request body** — same value xynthesis supplied:
35
+
36
+ ```json
37
+ {
38
+ "url": "https://openrouter.ai/api/v1/chat/completions",
39
+ "body": {
40
+ "model": "openrouter/google/gemini-2.5-flash-lite"
41
+ }
42
+ }
43
+ ```
44
+
45
+ ---
46
+
47
+ ## Why this is not a funcx defect
48
+
49
+ 1. **Contract:** Funcx OpenRouter backend accepts a **provider model slug** for the REST API. It is not funcx’s job to guess whether the caller meant gateway notation vs API notation.
50
+
51
+ 2. **Correct upstream already exists:** `@x12i/ai-profiles` **≥ 1.8.0** exposes the correct slug on **`ResolvedAIProfile.invocation.openrouter.modelId`**. Xynthesis should use that for Funcx, not re-wrap with `openrouter/`.
52
+
53
+ 3. **Silent correction in funcx would be harmful:** Stripping `openrouter/` inside funcx could mask bugs in other callers and make debugging harder.
54
+
55
+ ---
56
+
57
+ ## Optional enhancements (funcx — low priority, not required for this incident)
58
+
59
+ If funcx wants better operator experience **without** changing wire behavior:
60
+
61
+ ### O1 — Document OpenRouter `model` field
62
+
63
+ In README / `Client.ask` JSDoc, state explicitly:
64
+
65
+ - OpenRouter backend: **`model`** must be the **OpenRouter API model id** (e.g. `google/gemini-2.5-flash-lite`, `anthropic/claude-sonnet-4.5`).
66
+ - **Not** the ai-skills gateway form `openrouter/<vendor>/<model>`.
67
+
68
+ ### O2 — Fail fast on obvious gateway-shaped ids (optional)
69
+
70
+ When `backend === "openrouter"` and `model.startsWith("openrouter/")`, throw a **clear, named error** before HTTP:
71
+
72
+ ```text
73
+ Funcx OpenRouter backend: model must be a bare OpenRouter slug (got gateway-shaped "openrouter/…").
74
+ Resolve via @x12i/ai-profiles invocation.openrouter.modelId or fix upstream wire formatting.
75
+ ```
76
+
77
+ **Pros:** Surfaces misconfiguration immediately; points to xynthesis/ai-skills boundary.
78
+ **Cons:** Breaking if any caller intentionally relied on funcx to accept gateway ids (none known).
79
+
80
+ ### O3 — Include requested model in `AI_ACTIVITY_FAILED` diagnostics
81
+
82
+ Already partially present in activity logs. Ensure **`request.body.model`** is always in structured diagnostics for 400 responses (helps cross-package RCA).
83
+
84
+ ---
85
+
86
+ ## Paste-ready GitHub issue (funcx repo) — **optional, enhancement only**
87
+
88
+ **Title:** Document OpenRouter `ask({ model })` must use bare API slug; optional guard against `openrouter/` prefix
89
+
90
+ **Type:** Documentation (+ optional DX guard)
91
+
92
+ **Body:** Summarize O1–O2 above. Link xynthesis fix as primary resolution.
93
+
94
+ **Acceptance criteria:**
95
+
96
+ - [ ] README documents valid OpenRouter model id format.
97
+ - [ ] (Optional) Named error when `model.startsWith("openrouter/")` on OpenRouter backend.
98
+ - [ ] No automatic rewrite/strip of model ids (avoid masking upstream bugs).
99
+
100
+ ---
101
+
102
+ ## Related
103
+
104
+ - [`xynthesis-openrouter-wire-model-double-prefix-bug.md`](./xynthesis-openrouter-wire-model-double-prefix-bug.md) — **fix here first**
105
+ - [`funcx-upstream-github-issues-draft.md`](../funcx-upstream-github-issues-draft.md) — other funcx tracking
@@ -0,0 +1,191 @@
1
+ # `@exellix/xynthesis` — OpenRouter wire model double-prefix (400 invalid model ID)
2
+
3
+ Status: **fixed in `@exellix/xynthesis` ≥ 4.1.8**
4
+ Filed by: `@exellix/ai-tasks` (8.2.x)
5
+ Reproduced with: `@x12i/ai-profiles` **≥ 1.8.0**, `@exellix/xynthesis` **4.1.7** (broken), **4.1.8+** (fixed), `@x12i/funcx` **4.3.0**
6
+ Live test: `test/e2e/ai-profiles-live.test.ts` → `npm run test:live`
7
+ Verified: **2026-05-31** — all 8 live ai-profiles tests pass on **xynthesis 4.1.8** (wire id `google/gemini-2.5-flash-lite`, real POST-step LLM OK).
8
+
9
+ ---
10
+
11
+ ## Summary
12
+
13
+ When **`PREFER_OPENROUTER`** routing is active, xynthesis PRE/POST hops resolve an ai-profiles alias (e.g. `cheap`) and pass a **wire model id** to **`FuncxInvoker`** → **`client.ask({ model })`**. That wire id is incorrectly formatted as **`openrouter/<openrouter-api-slug>`** instead of the **bare OpenRouter API slug** returned by ai-profiles.
14
+
15
+ OpenRouter responds **400**:
16
+
17
+ ```json
18
+ {
19
+ "error": {
20
+ "message": "openrouter/google/gemini-2.5-flash-lite is not a valid model ID",
21
+ "code": 400
22
+ }
23
+ }
24
+ ```
25
+
26
+ Valid slug for that profile choice: **`google/gemini-2.5-flash-lite`**.
27
+
28
+ **Not an ai-profiles bug.** **Not an ai-tasks bug** (ai-tasks passes aliases; xynthesis resolves at invoke time). **Not a funcx bug** (funcx forwards the model string it receives).
29
+
30
+ ---
31
+
32
+ ## Impact
33
+
34
+ | Path | Symptom |
35
+ |------|---------|
36
+ | `executeXynthesisAction` with alias `model` (`cheap`, `balanced`, …) + OpenRouter key | **400** from OpenRouter; POST audit/polish/scoping/PRE synthesis fails |
37
+ | `@exellix/ai-tasks` `runPostStepLlmCall` | Same — all xynthesis-backed post steps |
38
+ | MAIN skill via `@exellix/ai-skills` | **Unaffected** — ai-skills expects gateway shape `openrouter/<vendor>/<model>` |
39
+
40
+ ---
41
+
42
+ ## Reproduction
43
+
44
+ ### Minimal (ai-tasks live test)
45
+
46
+ ```bash
47
+ # Requires OPENROUTER_API_KEY (or OPEN_ROUTER_KEY) and network
48
+ cd ai-tasks
49
+ npm run test:live
50
+ # Fails: diagnostic + real LLM tests until xynthesis is fixed
51
+ ```
52
+
53
+ Or:
54
+
55
+ ```bash
56
+ RUN_LIVE_AI_E2E=1 node --test dist-test/test/e2e/ai-profiles-live.test.js
57
+ ```
58
+
59
+ ### Resolution chain (alias `cheap`, OpenRouter routing)
60
+
61
+ | Step | Component | Value |
62
+ |------|-----------|-------|
63
+ | 1 | `@x12i/ai-profiles` `resolveAIProfile("cheap")` → `invocation.openrouter.modelId` | `google/gemini-2.5-flash-lite` ✓ |
64
+ | 2 | `@exellix/xynthesis` `resolveXynthesisModel("cheap").wireModelId` | `openrouter/google/gemini-2.5-flash-lite` ✗ |
65
+ | 3 | `@x12i/funcx` HTTP body `model` | `openrouter/google/gemini-2.5-flash-lite` (pass-through) |
66
+ | 4 | OpenRouter API | **400 invalid model ID** |
67
+
68
+ Observed Funcx request body (from live run):
69
+
70
+ ```json
71
+ {
72
+ "model": "openrouter/google/gemini-2.5-flash-lite",
73
+ "messages": [ … ]
74
+ }
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Root cause (xynthesis `@exellix/xynthesis` **4.1.7**)
80
+
81
+ **File:** `src/resolveAiProfileModel.ts`
82
+
83
+ 1. `openrouterModelIdFromInvocation()` correctly reads ai-profiles **`invocation.openrouter.modelId`** (bare slug when it contains `/`).
84
+
85
+ 2. **`openrouterWireModelId()`** then **always prepends** `openrouter/`:
86
+
87
+ ```ts
88
+ function openrouterWireModelId(resolved: ResolvedAIProfile): string | undefined {
89
+ const or = openrouterModelIdFromInvocation(resolved);
90
+ if (!or) return undefined;
91
+ return or.startsWith("openrouter/") ? or : `openrouter/${or}`; // ← bug
92
+ }
93
+ ```
94
+
95
+ 3. **`xynthesisWireModelIdFromResolved()`** uses that helper for OpenRouter-routed profiles:
96
+
97
+ ```ts
98
+ export function xynthesisWireModelIdFromResolved(resolved: ResolvedAIProfile): string {
99
+ if (resolved.routing === "openrouter") {
100
+ const orWire = openrouterWireModelId(resolved);
101
+ if (orWire) return orWire;
102
+ }
103
+ return toOpenRouterModelId(resolved.provider, resolved.modelId);
104
+ }
105
+ ```
106
+
107
+ 4. **`FuncxInvoker.askFuncx`** passes `wireModelId` directly to OpenRouter:
108
+
109
+ ```ts
110
+ return client.ask(opts.userPrompt, {
111
+ model: resolved.wireModelId, // must be bare OR slug, not openrouter/…
112
+
113
+ });
114
+ ```
115
+
116
+ ### Why two wire shapes exist
117
+
118
+ | Consumer | Expected model shape | Example |
119
+ |----------|---------------------|---------|
120
+ | **ai-skills MAIN** (gateway) | `openrouter/<vendor>/<model>` | `openrouter/anthropic/claude-sonnet-4.5` |
121
+ | **Funcx OpenRouter backend** (REST API) | bare OpenRouter slug | `google/gemini-2.5-flash-lite` |
122
+
123
+ Xynthesis conflates the **gateway** prefix rule with the **OpenRouter API** slug rule on the Funcx path.
124
+
125
+ ---
126
+
127
+ ## Recommended fix (xynthesis)
128
+
129
+ **In `xynthesisWireModelIdFromResolved` (Funcx / PRE/POST path):**
130
+
131
+ - Use **`resolved.invocation.openrouter.modelId`** as-is when present.
132
+ - Fallback: when `resolved.routing === "openrouter"` and `resolved.modelId` already contains `/`, return **`resolved.modelId`** (not `openrouter/${modelId}`).
133
+ - Only use `toOpenRouterModelId(provider, modelId)` for **vendor-direct** routing.
134
+
135
+ **Do not** prepend `openrouter/` on the Funcx invoke path.
136
+
137
+ **Remove or restrict** `openrouterWireModelId()` so it is not used for xynthesis wire ids (it may remain useful only if a future gateway-shaped export is needed elsewhere).
138
+
139
+ ### Tests to add (xynthesis)
140
+
141
+ In `test/resolveAiProfileModel.unit.ts` (or new file):
142
+
143
+ ```ts
144
+ const cheapOr = await resolveXynthesisModel("cheap", {
145
+ source: "bundled",
146
+ preferOpenRouter: true,
147
+ openrouterApiKeyPresent: true,
148
+ });
149
+ assert.equal(cheapOr?.routing, "openrouter");
150
+ assert.ok(cheapOr?.wireModelId.includes("/"));
151
+ assert.ok(!cheapOr!.wireModelId.startsWith("openrouter/"));
152
+ assert.equal(
153
+ cheapOr?.wireModelId,
154
+ cheapOr?.profile.invocation?.openrouter?.modelId
155
+ );
156
+ ```
157
+
158
+ Optional integration test: mock Funcx `client.ask` and assert `model` is bare slug.
159
+
160
+ ---
161
+
162
+ ## ai-tasks alignment (diagnostics only)
163
+
164
+ `@exellix/ai-tasks` **`resolveProfileInvocationRouting`** mirrored the same double-prefix for **`phaseKind: "xynthesis"`** in observability logs (`LLM_PROVIDER_INVOCATION`). That affects **diagnostics only** — the HTTP payload is built inside xynthesis. ai-tasks should align xynthesis-phase diagnostics with the fixed wire shape once xynthesis ships.
165
+
166
+ Skill phase (`phaseKind: "skill"`) should **keep** `openrouter/` gateway prefix for ai-skills MAIN.
167
+
168
+ ---
169
+
170
+ ## Paste-ready GitHub issue (xynthesis repo)
171
+
172
+ **Title:** `resolveXynthesisModel`: Funcx OpenRouter wire id must use bare API slug, not `openrouter/` prefix
173
+
174
+ **Labels:** `bug`, `openrouter`, `ai-profiles`
175
+
176
+ **Body:** Use the Summary, Reproduction, and Recommended fix sections above.
177
+
178
+ **Acceptance criteria:**
179
+
180
+ - [ ] `resolveXynthesisModel("cheap", { preferOpenRouter: true })` → `wireModelId` equals ai-profiles `invocation.openrouter.modelId` (no `openrouter/` prefix).
181
+ - [ ] Live OpenRouter call with alias `cheap` succeeds (200, non-empty completion).
182
+ - [ ] Vendor-direct routing (`preferOpenRouter: false`) unchanged.
183
+ - [ ] Changelog entry; semver patch or minor per release policy.
184
+
185
+ ---
186
+
187
+ ## Related
188
+
189
+ - [`.docs/prefer-openrouter-routing-policy.md`](../../.docs/prefer-openrouter-routing-policy.md)
190
+ - [`funcx-openrouter-model-id-pass-through.md`](./funcx-openrouter-model-id-pass-through.md) — funcx is pass-through; no fix required there
191
+ - [`xynthesis-upstream-fixes-checklist.md`](../xynthesis-upstream-fixes-checklist.md)