@fgv/ts-extras 5.1.0-56 → 5.1.0-57

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 (98) hide show
  1. package/CAPABILITIES.md +3 -2
  2. package/dist/packlets/ai-assist/chatRequestBuilders.js +2 -2
  3. package/dist/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
  4. package/dist/packlets/ai-assist/completionClient.js +74 -14
  5. package/dist/packlets/ai-assist/completionClient.js.map +1 -1
  6. package/dist/packlets/ai-assist/converters.js +54 -0
  7. package/dist/packlets/ai-assist/converters.js.map +1 -1
  8. package/dist/packlets/ai-assist/embeddingClient.js +16 -1
  9. package/dist/packlets/ai-assist/embeddingClient.js.map +1 -1
  10. package/dist/packlets/ai-assist/imageGenerationClient.js +16 -1
  11. package/dist/packlets/ai-assist/imageGenerationClient.js.map +1 -1
  12. package/dist/packlets/ai-assist/listModelsClient.js +19 -1
  13. package/dist/packlets/ai-assist/listModelsClient.js.map +1 -1
  14. package/dist/packlets/ai-assist/model.js.map +1 -1
  15. package/dist/packlets/ai-assist/streamUsageCapability.js +16 -6
  16. package/dist/packlets/ai-assist/streamUsageCapability.js.map +1 -1
  17. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +8 -4
  18. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  19. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +22 -7
  20. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -1
  21. package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  22. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +17 -9
  23. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
  24. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +12 -9
  25. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  26. package/dist/packlets/ai-assist/streamingAdapters/proxy.js +62 -4
  27. package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
  28. package/dist/packlets/ai-assist/streamingClient.js +20 -6
  29. package/dist/packlets/ai-assist/streamingClient.js.map +1 -1
  30. package/dist/packlets/ai-assist/thinkingOptionsResolver.js +5 -2
  31. package/dist/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -1
  32. package/dist/packlets/ai-assist/toolTypes.js.map +1 -1
  33. package/dist/ts-extras.d.ts +223 -24
  34. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts +2 -2
  35. package/lib/packlets/ai-assist/chatRequestBuilders.js +2 -2
  36. package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -1
  37. package/lib/packlets/ai-assist/completionClient.d.ts +13 -0
  38. package/lib/packlets/ai-assist/completionClient.d.ts.map +1 -1
  39. package/lib/packlets/ai-assist/completionClient.js +74 -14
  40. package/lib/packlets/ai-assist/completionClient.js.map +1 -1
  41. package/lib/packlets/ai-assist/converters.d.ts +46 -0
  42. package/lib/packlets/ai-assist/converters.d.ts.map +1 -1
  43. package/lib/packlets/ai-assist/converters.js +55 -1
  44. package/lib/packlets/ai-assist/converters.js.map +1 -1
  45. package/lib/packlets/ai-assist/embeddingClient.d.ts +9 -0
  46. package/lib/packlets/ai-assist/embeddingClient.d.ts.map +1 -1
  47. package/lib/packlets/ai-assist/embeddingClient.js +16 -1
  48. package/lib/packlets/ai-assist/embeddingClient.js.map +1 -1
  49. package/lib/packlets/ai-assist/imageGenerationClient.d.ts +9 -0
  50. package/lib/packlets/ai-assist/imageGenerationClient.d.ts.map +1 -1
  51. package/lib/packlets/ai-assist/imageGenerationClient.js +16 -1
  52. package/lib/packlets/ai-assist/imageGenerationClient.js.map +1 -1
  53. package/lib/packlets/ai-assist/listModelsClient.d.ts +12 -0
  54. package/lib/packlets/ai-assist/listModelsClient.d.ts.map +1 -1
  55. package/lib/packlets/ai-assist/listModelsClient.js +19 -1
  56. package/lib/packlets/ai-assist/listModelsClient.js.map +1 -1
  57. package/lib/packlets/ai-assist/model.d.ts +22 -7
  58. package/lib/packlets/ai-assist/model.d.ts.map +1 -1
  59. package/lib/packlets/ai-assist/model.js.map +1 -1
  60. package/lib/packlets/ai-assist/streamUsageCapability.d.ts +16 -6
  61. package/lib/packlets/ai-assist/streamUsageCapability.d.ts.map +1 -1
  62. package/lib/packlets/ai-assist/streamUsageCapability.js +16 -6
  63. package/lib/packlets/ai-assist/streamUsageCapability.js.map +1 -1
  64. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +2 -1
  65. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -1
  66. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +6 -2
  67. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
  68. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts +37 -2
  69. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts.map +1 -1
  70. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +21 -6
  71. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -1
  72. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +17 -3
  73. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -1
  74. package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
  75. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +2 -1
  76. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -1
  77. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +15 -7
  78. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
  79. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +2 -1
  80. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -1
  81. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +10 -7
  82. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
  83. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts +22 -0
  84. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -1
  85. package/lib/packlets/ai-assist/streamingAdapters/proxy.js +62 -4
  86. package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
  87. package/lib/packlets/ai-assist/streamingClient.d.ts +5 -0
  88. package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -1
  89. package/lib/packlets/ai-assist/streamingClient.js +19 -5
  90. package/lib/packlets/ai-assist/streamingClient.js.map +1 -1
  91. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts +11 -3
  92. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts.map +1 -1
  93. package/lib/packlets/ai-assist/thinkingOptionsResolver.js +5 -2
  94. package/lib/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -1
  95. package/lib/packlets/ai-assist/toolTypes.d.ts +61 -3
  96. package/lib/packlets/ai-assist/toolTypes.d.ts.map +1 -1
  97. package/lib/packlets/ai-assist/toolTypes.js.map +1 -1
  98. package/package.json +7 -7
@@ -644,8 +644,11 @@ declare const ANTHROPIC_STRUCTURED_OUTPUT_TOOL_NAME: string;
644
644
  *
645
645
  * Policy: low = 2048, medium = 8192, high = 24000, max = 32000. The lower three
646
646
  * align with the Anthropic-published minimum-meaningful budget, a mid-range
647
- * default, and a "deep thinking" allotment respectively. `max` targets Opus 4.6's
648
- * deepest budget and stays within typical model limits.
647
+ * default, and a "deep thinking" allotment respectively. `max` is the deepest
648
+ * allotment and stays within typical model limits.
649
+ *
650
+ * Only reached for models outside the Claude 5 family — the adaptive-thinking path
651
+ * sends no budget at all.
649
652
  *
650
653
  * @public
651
654
  */
@@ -809,6 +812,11 @@ declare function callProviderCompletion(params: IProviderCompletionParams): Prom
809
812
  * `Result.fail` before fetch is invoked. Callers should route through
810
813
  * {@link AiAssist.callProxiedCompletionStream} or surface the failure to the user.
811
814
  *
815
+ * This guard is applied **here only**. `executeClientToolTurn` is also a
816
+ * streaming entry point and does not pre-flight the flag, so a browser
817
+ * client-tool turn against a `streamingCorsRestricted` provider fails at the
818
+ * fetch with a CORS error rather than with this legible refusal.
819
+ *
812
820
  * Connection-time failures (auth, network, non-2xx) surface as the outer
813
821
  * `Result.fail`. Once iteration begins, errors mid-stream surface as a
814
822
  * terminal error event ({@link AiAssist.IAiStreamError}) followed by the iterable
@@ -865,6 +873,19 @@ declare function callProviderListModels(params: IProviderListModelsParams): Prom
865
873
  * CORS, and API key forwarding. The request body serializes the unified
866
874
  * {@link AiAssist.IChatRequest} shape (`system?` + `messages`). Enforces the same
867
875
  * non-empty / trailing-user-turn and image-input invariants as the direct path.
876
+ *
877
+ * @remarks
878
+ * Two parameters are handled before the body is composed, rather than forwarded.
879
+ * `tier` is resolved here and sent as a concrete `modelOverride`, because both
880
+ * halves of the resolution live on the descriptor the caller already holds — so a
881
+ * proxy needs no `tier` vocabulary, where a `tier` body field it did not understand
882
+ * would be ignored and silently serve a frontier request from the base model.
883
+ * `endpoint` is **refused**: it names where the prompt must go, the proxy is what
884
+ * makes that call, and the field has never been sent to one — so honoring it is
885
+ * unverifiable and ignoring it would reach the provider's default upstream instead
886
+ * of the host the caller pinned. Use `callProviderCompletion`, or point the
887
+ * proxy itself at the intended upstream.
888
+ *
868
889
  * @param proxyUrl - Base URL of the proxy server
869
890
  * @param params - Same parameters as {@link callProviderCompletion}
870
891
  * @public
@@ -882,9 +903,23 @@ declare function callProxiedCompletion(proxyUrl: string, params: IProviderComple
882
903
  * - Response: `Content-Type: text/event-stream`; body is the unified
883
904
  * {@link AiAssist.IAiStreamEvent} JSON-serialized one event per SSE `data:` line
884
905
  * (no `event:` line needed since the type discriminator is in the JSON).
906
+ * **Only `text-delta`, `tool-event`, `done` and `error` are carried** — an event
907
+ * of any other type, including the three `client-tool-*` variants, is discarded
908
+ * without error. Client-tool turns are not proxiable; `executeClientToolTurn`
909
+ * has no proxied entry point.
885
910
  * - Error response (when the proxy can't even start): JSON `{error: string}`
886
911
  * with a non-2xx status, surfaced as `proxy: ${error}`.
887
912
  *
913
+ * Two request parameters are handled before the body is composed, identically to
914
+ * `callProxiedCompletion`: `tier` is resolved here and sent as a concrete
915
+ * `modelOverride`, so a proxy needs no `tier` vocabulary; `endpoint` is refused,
916
+ * because a proxy cannot confirm it honored it and reaching the provider's default
917
+ * upstream instead would send the request somewhere the caller excluded. `cache`,
918
+ * by contrast, is forwarded as a plain `body.cache` field exactly as
919
+ * `callProxiedCompletion` forwards it — a proxy that ignores it degrades to the
920
+ * request it would have sent anyway, never a misdirected one, so there is no
921
+ * `endpoint`-style reason to refuse it.
922
+ *
888
923
  * The proxy server is responsible for opening the upstream SSE connection,
889
924
  * translating provider-native events to the unified vocabulary, and
890
925
  * forwarding events as they arrive (no buffering). The library does not
@@ -901,6 +936,15 @@ declare function callProxiedCompletionStream(proxyUrl: string, params: IProvider
901
936
  * handles descriptor lookup, model/capability resolution, and provider dispatch.
902
937
  * Error body `{ error: string }` is surfaced as `proxy: ${error}`.
903
938
  *
939
+ * @remarks
940
+ * `endpoint` is **refused** on this path rather than forwarded or dropped, on the
941
+ * same terms as `callProxiedCompletion`. It matters more here than
942
+ * anywhere else: pointing `endpoint` at `http://localhost:11434/v1` is the
943
+ * documented way to reach a local Ollama, so dropping it does not degrade the
944
+ * answer — it sends the text to OpenAI's public API instead of the machine the
945
+ * caller named. Use `callProviderEmbedding`, or point the proxy
946
+ * itself at the intended upstream.
947
+ *
904
948
  * @param proxyUrl - Base URL of the proxy server (e.g. `http://localhost:3001`).
905
949
  * @param params - Same parameters as {@link AiAssist.callProviderEmbedding}.
906
950
  * @returns The embedding result, or a failure.
@@ -916,6 +960,15 @@ declare function callProxiedEmbedding(proxyUrl: string, params: IProviderEmbeddi
916
960
  * lookup, model resolution, provider dispatch, and response normalization
917
961
  * (including repackaging `referenceImages` for the upstream wire format).
918
962
  * Error body `{error: string}` is surfaced as `proxy: ${error}`.
963
+ *
964
+ * @remarks
965
+ * `endpoint` is **refused** on this path rather than forwarded or dropped, on the
966
+ * same terms as `callProxiedCompletion`: a proxy cannot confirm it honored
967
+ * it, the field has never been sent to one, and reaching the provider's default
968
+ * upstream instead would send the prompt somewhere the caller excluded. Use
969
+ * `callProviderImageGeneration`, or point the proxy itself at the intended
970
+ * upstream.
971
+ *
919
972
  * @param proxyUrl - Base URL of the proxy server
920
973
  * @param params - Same parameters as {@link callProviderImageGeneration}
921
974
  * @public
@@ -927,6 +980,18 @@ declare function callProxiedImageGeneration(proxyUrl: string, params: IProviderI
927
980
  * `POST ${proxyUrl}/api/ai/list-models`. Capability config is not forwarded;
928
981
  * `capabilities` is serialized as a string array. Error body `{error: string}`
929
982
  * is surfaced as `proxy: ${error}`.
983
+ *
984
+ * @remarks
985
+ * `capabilityConfig` and `endpoint` are both unusable here, but they are not the
986
+ * same case and are not treated the same way. `capabilityConfig` overrides how a
987
+ * model id is classified, and the proxy returns models already classified — so a
988
+ * caller-supplied override could only ever have arrived too late. Ignoring it
989
+ * yields the default classification: a degraded answer, disclosed above, and
990
+ * that is where it stays. `endpoint` names *which server answers at all*, so
991
+ * ignoring it would list the provider's public catalog in place of the host the
992
+ * caller pinned; it is **refused**, on the same terms as
993
+ * `callProxiedCompletion`.
994
+ *
930
995
  * @public
931
996
  */
932
997
  declare function callProxiedListModels(proxyUrl: string, params: IProviderListModelsParams): Promise<Result<ReadonlyArray<IAiModelInfo>>>;
@@ -2738,6 +2803,51 @@ declare interface IAiClientToolTurnResult {
2738
2803
  * because the pinned model cannot ground and call functions in one request.
2739
2804
  */
2740
2805
  readonly toolConflicts: IAiToolConflictReport;
2806
+ /**
2807
+ * Token usage for this turn's provider call, when the provider reports it.
2808
+ *
2809
+ * @remarks
2810
+ * The same `IAiCompletionUsage` the plain completion paths carry — normalized
2811
+ * cache reads/writes, output and total input tokens, and a `reports` level
2812
+ * saying which of those the provider is able to tell you. Sourced from the
2813
+ * terminal `done` event of the underlying stream, so it is exactly what a
2814
+ * caller watching `events` would see.
2815
+ *
2816
+ * Reading it here spares you *inspecting* the event stream, not *driving* it:
2817
+ * `nextTurn` only resolves once `events` has been iterated to completion, the
2818
+ * same as every other field on this interface.
2819
+ *
2820
+ * **One call, one usage.** `executeClientToolTurn` makes a single provider
2821
+ * call per invocation; the multi-round loop is driven by the caller
2822
+ * re-invoking with `continuationMessages`. So each result's `usage` already
2823
+ * *is* one round's usage, and a host accumulating a per-turn total sums the
2824
+ * results of its own loop. There is deliberately no `perRound` array here —
2825
+ * it could only ever have one entry.
2826
+ *
2827
+ * **Optional on purpose, unlike `toolConflicts`.** The library always knows
2828
+ * what it did to your tools, so a silent `toolConflicts` would be a defect
2829
+ * and it is required. It cannot always know what a turn cost, so a required
2830
+ * `usage` could only be satisfied by inventing one — the exact failure
2831
+ * {@link AiAssist.IAiCompletionUsage.reports} exists to prevent.
2832
+ *
2833
+ * Absent means **no normalized usage is being exposed**, which covers two
2834
+ * cases, the same as on {@link AiAssist.IAiCompletionResponse.usage} and
2835
+ * {@link AiAssist.IAiStreamDone.usage}:
2836
+ *
2837
+ * - the provider reported nothing; or
2838
+ * - the provider reported something the library declines to normalize,
2839
+ * because this descriptor is outside
2840
+ * {@link AiAssist.supportsCacheUsageReporting}. Groq, Mistral, Ollama and
2841
+ * self-hosted `openai-compat` share the OpenAI-format adapter but have
2842
+ * unverified usage shapes, so a raw `usage` block from one of them is
2843
+ * dropped rather than stamped with a cache-reporting level it may not
2844
+ * have earned.
2845
+ *
2846
+ * Either way it is never a fabricated zero. If you need to tell the two
2847
+ * apart, the descriptor answers it: the second case is a property of the
2848
+ * provider you picked, not of the turn.
2849
+ */
2850
+ readonly usage?: IAiCompletionUsage;
2741
2851
  }
2742
2852
 
2743
2853
  /**
@@ -3564,11 +3674,23 @@ declare interface IAiToolEnablement {
3564
3674
  */
3565
3675
  declare interface IAiWebSearchToolConfig {
3566
3676
  readonly type: 'web_search';
3567
- /** Optional: restrict search to these domains. */
3677
+ /**
3678
+ * Optional: restrict search to these domains.
3679
+ * Reaches Anthropic and the OpenAI/xAI Responses API; ignored on Gemini,
3680
+ * whose `google_search` tool takes no configuration at all.
3681
+ */
3568
3682
  readonly allowedDomains?: ReadonlyArray<string>;
3569
- /** Optional: exclude these domains from search. */
3683
+ /**
3684
+ * Optional: exclude these domains from search.
3685
+ * Same reach as {@link AiAssist.IAiWebSearchToolConfig.allowedDomains}:
3686
+ * Anthropic and OpenAI/xAI, ignored on Gemini.
3687
+ */
3570
3688
  readonly blockedDomains?: ReadonlyArray<string>;
3571
- /** Optional: max number of searches per request. */
3689
+ /**
3690
+ * Optional: max number of searches per request.
3691
+ * **Anthropic only** — emitted as `max_uses` by the Anthropic formatter and
3692
+ * read nowhere else, so it is silently ignored on OpenAI/xAI and Gemini.
3693
+ */
3572
3694
  readonly maxUses?: number;
3573
3695
  /**
3574
3696
  * Optional: enable image understanding during web search.
@@ -3584,11 +3706,25 @@ declare interface IAiWebSearchToolConfig {
3584
3706
  */
3585
3707
  declare interface IAnthropicThinkingConfig {
3586
3708
  /**
3587
- * Anthropic effort level. The emit-site converts to `thinking.budget_tokens`
3588
- * (the integer budget the Anthropic API requires). Mapping policy: low = 2048,
3589
- * medium = 8192, high = 24000, max = 32000.
3590
- * - 'low' | 'medium' | 'high': all thinking-capable models
3591
- * - 'max': Opus 4.6 only
3709
+ * Anthropic effort level, emitted as one of **two** wire shapes chosen by
3710
+ * {@link AiAssist.isAdaptiveThinkingModel}:
3711
+ *
3712
+ * - **Claude 5 family** (`claude-sonnet-5`, `claude-opus-5`, `claude-fable-5`
3713
+ * — which includes the `base` and `advanced` tiers, so this is the default
3714
+ * path): an adaptive `thinking` block plus an `output_config` block
3715
+ * carrying `effort`. The effort string goes on the wire verbatim and
3716
+ * **no token budget is sent**.
3717
+ * - **Older thinking-capable models**: an enabled `thinking` block carrying
3718
+ * `budget_tokens`, converted by `anthropicEffortToBudgetTokens` — low =
3719
+ * 2048, medium = 8192, high = 24000, max = 32000.
3720
+ *
3721
+ * So the budget mapping is not a description of what a default Anthropic
3722
+ * call sends; size a budget from it only when you have pinned a pre-Claude-5
3723
+ * model.
3724
+ *
3725
+ * `'max'` is accepted on every thinking-capable model here — the library
3726
+ * applies no model restriction of its own, and whether a given model accepts
3727
+ * it is the provider's to enforce.
3592
3728
  */
3593
3729
  readonly effort?: 'low' | 'medium' | 'high' | 'max';
3594
3730
  }
@@ -4565,6 +4701,17 @@ declare interface IExecuteClientToolTurnParams extends IChatRequest {
4565
4701
  * pre-policy behavior: a `Result.fail` up front, before any wire call.
4566
4702
  */
4567
4703
  readonly toolConflictPolicy?: AiToolConflictPolicy;
4704
+ /**
4705
+ * Prompt-caching plan for this request. Omitted, nothing cache-related is sent — the request
4706
+ * body is byte-identical to a build predating this feature. See {@link AiAssist.IAiCacheRequest}.
4707
+ *
4708
+ * @remarks
4709
+ * `executeClientToolTurn` represents a single round of a multi-round tool loop; a caller
4710
+ * driving several rounds passes this on **each** call, and it is validated against — and
4711
+ * applied to — that round's own `system` each time, never cached across rounds. Gated per
4712
+ * provider exactly as `IProviderCompletionParams.cache` is on the non-streaming path.
4713
+ */
4714
+ readonly cache?: IAiCacheRequest;
4568
4715
  }
4569
4716
 
4570
4717
  /**
@@ -4574,14 +4721,37 @@ declare interface IExecuteClientToolTurnParams extends IChatRequest {
4574
4721
  declare interface IExecuteClientToolTurnResult {
4575
4722
  /**
4576
4723
  * The unified-event iterable. Callers iterate this to drive the streaming UI.
4577
- * The iterable forwards `text-delta`, `tool-event`, `client-tool-call-start`,
4578
- * `client-tool-call-done`, and `client-tool-result` events through.
4724
+ *
4725
+ * @remarks
4726
+ * Forwards every event the underlying adapter emits: `text-delta`,
4727
+ * `tool-event`, `done` and `error` come through from the provider stream, and
4728
+ * `client-tool-call-start`, `client-tool-call-done` and `client-tool-result`
4729
+ * are the tool-dispatch events this layer adds.
4730
+ *
4731
+ * The `done` event is the terminal one and carries
4732
+ * {@link AiAssist.IAiStreamDone.usage} when the provider reports it — this
4733
+ * iterable is not a text-only view. A caller that only wants the token counts
4734
+ * can read them off {@link AiAssist.IAiClientToolTurnResult.usage} instead of
4735
+ * matching on events — but it must still iterate this to completion, because
4736
+ * that is what drives `nextTurn` to resolve at all.
4737
+ *
4738
+ * (An earlier version of this comment listed only the five non-terminal
4739
+ * events. It was describing the tool-dispatch subset, but it read as the
4740
+ * complete set, and at least one consumer reasonably concluded from it that
4741
+ * usage never crossed the package boundary on this path. It always did.)
4579
4742
  */
4580
4743
  readonly events: AsyncIterable<IAiStreamEvent>;
4581
4744
  /**
4582
4745
  * Resolves when the stream terminates. On success, carries the
4583
4746
  * {@link AiAssist.IAiClientToolTurnResult} with the optional continuation for the
4584
4747
  * next round. On failure, carries the error message.
4748
+ *
4749
+ * @remarks
4750
+ * **You must drive `events` for this to resolve.** It is settled from inside
4751
+ * the generator backing `events`, and an async generator does not start until
4752
+ * something iterates it — so `await nextTurn` on its own waits forever, and
4753
+ * the response body is never drained. Iterate `events` to completion (ignore
4754
+ * the events if you do not want them) and then await this.
4585
4755
  */
4586
4756
  readonly nextTurn: Promise<Result<IAiClientToolTurnResult>>;
4587
4757
  }
@@ -4674,8 +4844,9 @@ declare interface IGeminiThinkingConfig {
4674
4844
  /**
4675
4845
  * Whether to include thought summaries in the response.
4676
4846
  * @remarks
4677
- * INERT in phase B. Adapters never send `includeThoughts: true`.
4678
- * Wired up by the followup stream `ai-assist-thinking-events`.
4847
+ * Reserved for forward compatibility and **currently ignored by every
4848
+ * adapter** — nothing reads this field, so setting it has no effect on the
4849
+ * request. Surfacing thinking output as stream events is not yet built.
4679
4850
  */
4680
4851
  readonly includeThoughts?: boolean;
4681
4852
  }
@@ -5623,9 +5794,15 @@ declare interface IProviderCompletionParams extends IChatRequest {
5623
5794
  }
5624
5795
 
5625
5796
  /**
5626
- * Parameters for a streaming completion request. Structurally identical to
5627
- * the non-streaming `IProviderCompletionParams`; kept as its own interface
5628
- * so callers can be explicit about which path they're invoking.
5797
+ * Parameters for a streaming completion request. The non-streaming
5798
+ * `IProviderCompletionParams` shape **minus `structuredOutput`**; kept as its
5799
+ * own interface so callers can be explicit about which path they're invoking.
5800
+ *
5801
+ * @remarks
5802
+ * The omission is deliberate, not an oversight. Native structured output is a
5803
+ * non-streaming feature on every provider the package supports, so the field
5804
+ * would have nowhere to go here. A caller that needs it must use
5805
+ * `callProviderCompletion`.
5629
5806
  *
5630
5807
  * @remarks
5631
5808
  * Carries the unified {@link AiAssist.IChatRequest} shape (`system?` + ordered
@@ -5681,6 +5858,13 @@ declare interface IProviderCompletionStreamParams extends IChatRequest {
5681
5858
  * Messages API requires the field, so it falls back to `DEFAULT_ANTHROPIC_MAX_TOKENS`.
5682
5859
  */
5683
5860
  readonly maxTokens?: number;
5861
+ /**
5862
+ * Prompt-caching plan for this request. Omitted, nothing cache-related is sent — the request
5863
+ * body is byte-identical to a build predating this feature. See {@link AiAssist.IAiCacheRequest}.
5864
+ * Validated against this request's own `system` at the point it is applied, and gated per
5865
+ * provider exactly as `IProviderCompletionParams.cache` is on the non-streaming path.
5866
+ */
5867
+ readonly cache?: IAiCacheRequest;
5684
5868
  }
5685
5869
 
5686
5870
  /**
@@ -5919,7 +6103,12 @@ declare interface IResolvedImageOptions {
5919
6103
  * @public
5920
6104
  */
5921
6105
  declare interface IResolvedThinkingConfig {
5922
- /** Anthropic: effort level; emit-site converts to `thinking.budget_tokens` via `anthropicEffortToBudgetTokens`. */
6106
+ /**
6107
+ * Anthropic: effort level. The emit-site picks one of two wire shapes — on the
6108
+ * Claude 5 family it sends the effort string verbatim in `output_config` with no
6109
+ * budget; on older models it converts via `anthropicEffortToBudgetTokens`. See
6110
+ * {@link AiAssist.IAnthropicThinkingConfig.effort}.
6111
+ */
5923
6112
  readonly anthropicEffort?: IAnthropicThinkingConfig['effort'];
5924
6113
  /** OpenAI Chat: reasoning_effort value; OpenAI Responses: reasoning.effort */
5925
6114
  readonly openAiEffort?: IOpenAiThinkingConfig['effort'];
@@ -8822,9 +9011,10 @@ declare const SUPPORTED_SCHEMES: ReadonlyArray<string>;
8822
9011
  * non-streaming alike).
8823
9012
  *
8824
9013
  * @remarks
8825
- * All four call sites (`callOpenAiCompletion`, `callOpenAiResponsesCompletion`,
8826
- * and their streaming counterparts) are shared by every `apiFormat: 'openai'`
8827
- * descriptor — xAI Grok, Groq, Mistral, Ollama, and self-hosted
9014
+ * The completion paths it gates — `callOpenAiCompletion`,
9015
+ * `callOpenAiResponsesCompletion`, the Responses streaming adapter, and
9016
+ * `executeClientToolTurn`'s direct Responses call — are shared by every
9017
+ * `apiFormat: 'openai'` descriptor — xAI Grok, Groq, Mistral, Ollama, and self-hosted
8828
9018
  * `openai-compat` all route through the same adapter as OpenAI. Only OpenAI
8829
9019
  * and xAI Grok are confirmed to report cache-relevant `usage` fields; an
8830
9020
  * ordinary `usage` block from one of the other descriptors (e.g. Groq's plain
@@ -8832,9 +9022,18 @@ declare const SUPPORTED_SCHEMES: ReadonlyArray<string>;
8832
9022
  * carries no cache information. Normalizing it anyway would stamp
8833
9023
  * `reports: 'reads'` on a provider that has no prompt-caching concept —
8834
9024
  * a false cache-reporting signal, not an absent one. So this gate is checked
8835
- * before every normalize call; when it's `false`, `usage` stays absent
8836
- * entirely rather than being guessed from whatever shape the wire happens to
8837
- * send.
9025
+ * before every normalize call it governs; when it's `false`, `usage` stays
9026
+ * absent entirely rather than being guessed from whatever shape the wire
9027
+ * happens to send.
9028
+ *
9029
+ * **Streaming Chat Completions is gated by a different predicate.** That path
9030
+ * consults {@link AiAssist.supportsStreamUsageOption} instead, because the
9031
+ * request has to opt in via `stream_options: { include_usage: true }` before
9032
+ * there is any usage to normalize — and that opt-in is `'openai'`-only, since
9033
+ * the other descriptors' tolerance for the unrecognized field is unverified.
9034
+ * The practical consequence is that xAI Grok, which *is* confirmed here, still
9035
+ * gets no `usage` on streaming Chat Completions. If you are tracing a missing
9036
+ * xAI streaming figure, that predicate is the one to read, not this one.
8838
9037
  * @public
8839
9038
  */
8840
9039
  declare function supportsCacheUsageReporting(descriptor: IAiProviderDescriptor): boolean;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Per-format chat request shape builders. Shared between the synchronous
3
- * (`apiClient.ts`) and streaming (`streamingClient.ts`) paths so the wire
4
- * shapes stay consistent.
3
+ * (`completionClient.ts`) and streaming (`streamingClient.ts`) paths so the
4
+ * wire shapes stay consistent.
5
5
  *
6
6
  * @packageDocumentation
7
7
  */
@@ -33,8 +33,8 @@ exports.buildAnthropicMessages = buildAnthropicMessages;
33
33
  exports.buildGeminiContents = buildGeminiContents;
34
34
  /**
35
35
  * Per-format chat request shape builders. Shared between the synchronous
36
- * (`apiClient.ts`) and streaming (`streamingClient.ts`) paths so the wire
37
- * shapes stay consistent.
36
+ * (`completionClient.ts`) and streaming (`streamingClient.ts`) paths so the
37
+ * wire shapes stay consistent.
38
38
  *
39
39
  * @packageDocumentation
40
40
  */
@@ -1 +1 @@
1
- {"version":3,"file":"chatRequestBuilders.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/chatRequestBuilders.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Per-format chat request shape builders. Shared between the synchronous\n * (`apiClient.ts`) and streaming (`streamingClient.ts`) paths so the wire\n * shapes stay consistent.\n *\n * @packageDocumentation\n */\n\nimport { isJsonObject, type JsonObject } from '@fgv/ts-json-base';\nimport { type Converter, Converters, fail, Result, succeed } from '@fgv/ts-utils';\n\nimport { type IAiCacheRequest, splitSystemForCache } from './cacheRequest';\nimport { AiPrompt, type IAiImageAttachment, type IChatMessage, toDataUrl } from './model';\n\n/**\n * The result of splitting an {@link AiAssist.IChatRequest} into the per-builder\n * inputs: the current turn (as an {@link AiPrompt}, carrying system + the final\n * user message + any attachments) and the preceding conversation history.\n * @internal\n */\nexport interface ISplitChatRequest {\n /** The current turn lowered to an {@link AiPrompt} (system + user + attachments). */\n readonly prompt: AiPrompt;\n /** Prior conversation history — every message before the current turn. */\n readonly head: ReadonlyArray<IChatMessage>;\n}\n\n/**\n * Splits a unified {@link AiAssist.IChatRequest} into `{ prompt, head }` for the\n * per-provider builders. The **last** message is the current `user` turn; the\n * preceding messages are history (`head`). This is the single linearization\n * shared by every turn entry point, so the completion path and the client-tool\n * turn path place history at the identical position relative to the current turn.\n *\n * Fails when `messages` is empty (no current turn) or when the last message is\n * not a `user` turn — relabelling a trailing assistant message as the user turn\n * would be a silent footgun, so it is rejected loudly instead.\n *\n * @internal\n */\nexport function splitChatRequest(\n system: string | undefined,\n messages: ReadonlyArray<IChatMessage>\n): Result<ISplitChatRequest> {\n if (messages.length === 0) {\n return fail('messages must contain at least one entry (the current user turn)');\n }\n const current = messages[messages.length - 1];\n if (current.role !== 'user') {\n return fail(`the last message must be the current user turn (role 'user'); got '${current.role}'`);\n }\n const head = messages.slice(0, messages.length - 1);\n const prompt = new AiPrompt(current.content, system ?? '', current.attachments);\n return succeed({ prompt, head });\n}\n\n/**\n * Rebuilds the ordered messages for a proxy wire body so that only the current\n * turn can carry attachments — history (non-current) messages are reduced to\n * `{ role, content }`. The direct per-provider builders already drop attachments\n * on history turns (only the current user turn's attachments are honored), so\n * normalizing here keeps the proxy wire shape consistent with the direct paths\n * and avoids transmitting attachment payloads the upstream provider would ignore.\n *\n * @internal\n */\nexport function normalizeOutboundMessages(split: ISplitChatRequest): IChatMessage[] {\n return [\n ...split.head.map((m) => ({ role: m.role, content: m.content })),\n ...split.prompt.toRequest().messages\n ];\n}\n\n/**\n * Converter for a rawTail message entry. Narrows a `JsonObject` to\n * `{ role: string; content: string | unknown[] }` at runtime using the\n * Converter pattern. Entries that fail validation are silently skipped — the\n * surrounding function is infallible, and a malformed continuation message is\n * better omitted than transmitted verbatim.\n * @internal\n */\nconst rawTailMessageConverter: Converter<{ role: 'user' | 'assistant'; content: string | unknown[] }> =\n Converters.object<{ role: 'user' | 'assistant'; content: string | unknown[] }>(\n {\n role: Converters.enumeratedValue<'user' | 'assistant'>(['user', 'assistant']),\n content: Converters.oneOf<string | unknown[]>([\n Converters.string,\n Converters.isA('array', (v): v is unknown[] => Array.isArray(v))\n ])\n },\n { strict: false }\n );\n\n/**\n * Converter for an OpenAI / xAI Responses API `rawTail` item. These are\n * provider-native input items (`function_call`, `function_call_output`) whose\n * fields differ per item type, so — unlike the Anthropic `{ role, content }`\n * projection — the whole object is preserved verbatim.\n *\n * The static input is already typed `JsonObject`, so the `isJsonObject` guard\n * is a runtime backstop, not a compile-time narrowing: continuation messages\n * originate from a prior turn's `IAiClientToolContinuation.messages` and a\n * consumer may persist and reload them through untyped JSON before passing them\n * back. The guard preserves the same \"a malformed continuation message is\n * better omitted than transmitted verbatim\" posture as the Anthropic path —\n * non-object entries fail conversion and are skipped by the caller.\n * @internal\n */\nconst openAiRawTailItemConverter: Converter<JsonObject> = Converters.isA<JsonObject>(\n 'JsonObject',\n (v): v is JsonObject => isJsonObject(v)\n);\n\n/**\n * Converter for a Gemini `rawTail` item. Gemini continuation messages are\n * `{ role, parts }` turns (a model turn with `functionCall` parts followed by a\n * user turn with `functionResponse` parts). Narrows a `JsonObject` to\n * `{ role: 'user' | 'model'; parts: Array<Record<string, unknown>> }`; entries\n * that fail validation are skipped by the caller.\n * @internal\n */\nconst geminiRawTailMessageConverter: Converter<{\n role: 'user' | 'model';\n parts: unknown[];\n}> = Converters.object<{ role: 'user' | 'model'; parts: unknown[] }>(\n {\n role: Converters.enumeratedValue<'user' | 'model'>(['user', 'model']),\n // `parts` is preserved verbatim and serialized into the request body, so the\n // element shape is not narrowed here — `Array.isArray` soundly guarantees\n // `unknown[]` (narrowing to `Record<string, unknown>[]` would be an unchecked cast).\n parts: Converters.isA('array', (v): v is unknown[] => Array.isArray(v))\n },\n { strict: false }\n);\n\n/**\n * Optional history (`head`) and raw continuation (`rawTail`) messages to weave\n * around the prompt's current user message.\n *\n * @internal\n */\nexport interface IBuildMessagesOptions {\n /**\n * Prior conversation history inserted between the system prompt and the\n * prompt's current user message (multi-turn chat / correction retries). The\n * single ordered linearization is `[system, ...head, user, ...rawTail]`.\n */\n readonly head?: ReadonlyArray<IChatMessage>;\n /**\n * Raw JSON objects appended after the prompt's user message. Used to\n * inject provider-specific continuation messages (e.g. Anthropic assistant\n * turns with thinking blocks, OpenAI Responses `function_call` /\n * `function_call_output` items, Gemini `functionCall` / `functionResponse`\n * turns) that cannot be expressed as plain {@link IChatMessage} objects.\n *\n * Each builder applies its own provider-specific shape guard:\n * - {@link buildAnthropicMessages} projects each entry to `{ role, content }`.\n * - {@link buildMessages} (OpenAI / xAI Responses) preserves each item\n * verbatim (item fields differ per `type`), guarding only that it is a\n * JSON object.\n * - {@link buildGeminiContents} projects each entry to `{ role, parts }`.\n *\n * Entries that fail their builder's shape check are silently skipped (the\n * caller is responsible for supplying well-formed continuation messages).\n * Appended after the current user message.\n */\n readonly rawTail?: ReadonlyArray<JsonObject>;\n}\n\n/**\n * Builds the messages array from prompt + optional history (`head`) and raw\n * continuation (`rawTail`) messages. The caller supplies the system content\n * (string when no cache breakpoints are requested, parts array when they are\n * — see {@link buildOpenAiChatSystemContent} / {@link buildOpenAiResponsesSystemContent})\n * and the user content (string for text-only, parts array for vision prompts),\n * since both shapes differ by format.\n *\n * `rawTail` items (OpenAI / xAI Responses `function_call` /\n * `function_call_output` continuation items) are appended verbatim after the\n * user message — their fields differ per item `type`, so they are preserved\n * rather than projected. The return type is `Array<Record<string, unknown>>`\n * to accommodate both `{ role, content }` messages and these heterogeneous\n * input items.\n *\n * @internal\n */\nexport function buildMessages(\n systemContent: string | unknown[],\n userContent: string | unknown[],\n options?: IBuildMessagesOptions\n): Array<Record<string, unknown>> {\n const messages: Array<Record<string, unknown>> = [{ role: 'system', content: systemContent }];\n if (options?.head) {\n for (const msg of options.head) {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n messages.push({ role: 'user', content: userContent });\n // OpenAI / xAI Responses continuation items (function_call /\n // function_call_output) are appended verbatim — their field set differs per\n // item type, so the whole object is preserved rather than projected.\n if (options?.rawTail) {\n for (const item of options.rawTail) {\n const converted = openAiRawTailItemConverter.convert(item);\n if (converted.isSuccess()) {\n messages.push(converted.value);\n }\n }\n }\n return messages;\n}\n\n/**\n * Splits `segments` (from {@link splitSystemForCache}) into a single string when nothing carries\n * a breakpoint, or a content-part array in `partType`'s shape (with `prompt_cache_breakpoint` on\n * the parts that end a cacheable prefix) otherwise.\n *\n * @remarks\n * A single non-breakpointed segment (the common case: `cache` omitted or empty) returns the\n * plain string, byte-identical to a request built before cache support existed. Shared by\n * {@link buildOpenAiChatSystemContent} and {@link buildOpenAiResponsesSystemContent}, whose only\n * difference is the content-part `type` OpenAI's two APIs use (`'text'` vs `'input_text'`,\n * research.md §1.2).\n * @internal\n */\nfunction toOpenAiSystemContent(\n segments: ReadonlyArray<{ readonly text: string; readonly cacheBreakpoint: boolean }>,\n partType: 'text' | 'input_text'\n): string | unknown[] {\n if (segments.length === 1 && !segments[0].cacheBreakpoint) {\n return segments[0].text;\n }\n return segments.map((seg) => ({\n type: partType,\n text: seg.text,\n ...(seg.cacheBreakpoint\n ? {\n // eslint-disable-next-line @typescript-eslint/naming-convention -- wire field name\n prompt_cache_breakpoint: { mode: 'explicit' }\n }\n : {})\n }));\n}\n\n/**\n * Builds the system content for OpenAI Chat Completions, splitting `system` at `cache`'s\n * breakpoints (design.md §6.3) into content parts each carrying\n * `prompt_cache_breakpoint: { mode: 'explicit' }` where a cacheable prefix ends.\n * `prompt_cache_options` is never touched — OpenAI keeps its own implicit breakpoint and writes\n * up to the latest three of ours on top of it. Returns the plain `system` string when `cache` is\n * `undefined` or declares no breakpoints, byte-identical to a request built before cache support\n * existed.\n * @internal\n */\nexport function buildOpenAiChatSystemContent(\n system: string,\n cache: IAiCacheRequest | undefined\n): Result<string | unknown[]> {\n return splitSystemForCache(system, cache).onSuccess((segments) =>\n succeed(toOpenAiSystemContent(segments, 'text'))\n );\n}\n\n/**\n * Builds the system content for the OpenAI / xAI Responses API. Identical to\n * {@link buildOpenAiChatSystemContent} except for the content-part type (`'input_text'` instead\n * of `'text'`) — see research.md §1.2, \"Where the breakpoint may be attached.\"\n * @internal\n */\nexport function buildOpenAiResponsesSystemContent(\n system: string,\n cache: IAiCacheRequest | undefined\n): Result<string | unknown[]> {\n return splitSystemForCache(system, cache).onSuccess((segments) =>\n succeed(toOpenAiSystemContent(segments, 'input_text'))\n );\n}\n\n/**\n * Builds the Anthropic `system` field, splitting `system` at `cache`'s breakpoints into content\n * blocks carrying `cache_control: { type: 'ephemeral' }` on the block that ends each cacheable\n * prefix (design.md §6.1). Returns the plain `system` string when `cache` is `undefined` or\n * declares no breakpoints — byte-identical to `system: prompt.system` from a request built\n * before cache support existed.\n * @internal\n */\nexport function buildAnthropicSystem(\n system: string,\n cache: IAiCacheRequest | undefined\n): Result<string | unknown[]> {\n return splitSystemForCache(system, cache).onSuccess((segments) => {\n if (segments.length === 1 && !segments[0].cacheBreakpoint) {\n return succeed<string | unknown[]>(segments[0].text);\n }\n return succeed<string | unknown[]>(\n segments.map((seg) => ({\n type: 'text',\n text: seg.text,\n ...(seg.cacheBreakpoint ? { cache_control: { type: 'ephemeral' } } : {})\n }))\n );\n });\n}\n\n/**\n * Builds the user content for OpenAI Chat Completions when attachments are\n * present. Returns a string when there are no attachments.\n *\n * @internal\n */\nexport function buildOpenAiChatUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'image_url',\n image_url: {\n url: toDataUrl(att),\n ...(att.detail !== undefined ? { detail: att.detail } : {})\n }\n }))\n ];\n}\n\n/**\n * Builds the user content for OpenAI / xAI Responses API when attachments\n * are present. Responses API uses `input_text` / `input_image` part types,\n * distinct from Chat Completions' `text` / `image_url`.\n *\n * @internal\n */\nexport function buildOpenAiResponsesUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'input_text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'input_image',\n image_url: toDataUrl(att),\n ...(att.detail !== undefined ? { detail: att.detail } : {})\n }))\n ];\n}\n\n/**\n * Builds the user-message content for Anthropic when attachments are present.\n *\n * @internal\n */\nexport function buildAnthropicUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'image',\n source: {\n type: 'base64',\n media_type: att.mimeType,\n data: att.base64\n }\n }))\n ];\n}\n\n/**\n * Builds the Gemini `parts` array for the user turn, including any image\n * attachments as `inlineData` parts.\n *\n * @internal\n */\nexport function buildGeminiUserParts(prompt: AiPrompt): Array<Record<string, unknown>> {\n const parts: Array<Record<string, unknown>> = [{ text: prompt.user }];\n for (const att of prompt.attachments) {\n parts.push({ inlineData: { mimeType: att.mimeType, data: att.base64 } });\n }\n return parts;\n}\n\n/**\n * Builds the Anthropic messages array, weaving any `head` history messages\n * between implicit system + the prompt's user message and appending `rawTail`\n * continuation messages after. System messages are filtered out (Anthropic uses\n * a top-level system field).\n *\n * @internal\n */\nexport function buildAnthropicMessages(\n prompt: AiPrompt,\n options?: IBuildMessagesOptions\n): Array<{ role: string; content: string | unknown[] }> {\n const messages: Array<{ role: string; content: string | unknown[] }> = [];\n if (options?.head) {\n for (const msg of options.head) {\n if (msg.role !== 'system') {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n }\n messages.push({ role: 'user', content: buildAnthropicUserContent(prompt) });\n if (options?.rawTail) {\n for (const msg of options.rawTail) {\n const converted = rawTailMessageConverter.convert(msg);\n if (converted.isSuccess()) {\n messages.push(converted.value);\n }\n }\n }\n return messages;\n}\n\n/**\n * Builds the Gemini `contents` array, weaving any `head` history messages before\n * the prompt's user parts and appending `rawTail` continuation messages after.\n * System messages are filtered out (Gemini uses a top-level systemInstruction\n * field) and assistant roles are mapped to Gemini's `model` role.\n *\n * @internal\n */\nexport function buildGeminiContents(\n prompt: AiPrompt,\n options?: IBuildMessagesOptions\n): Array<{ role: string; parts: unknown[] }> {\n const contents: Array<{ role: string; parts: unknown[] }> = [];\n if (options?.head) {\n for (const msg of options.head) {\n if (msg.role !== 'system') {\n contents.push({\n role: msg.role === 'assistant' ? 'model' : msg.role,\n parts: [{ text: msg.content }]\n });\n }\n }\n }\n contents.push({ role: 'user', parts: buildGeminiUserParts(prompt) });\n // Gemini continuation turns (model `functionCall` parts + user\n // `functionResponse` parts) are projected to `{ role, parts }`.\n if (options?.rawTail) {\n for (const item of options.rawTail) {\n const converted = geminiRawTailMessageConverter.convert(item);\n if (converted.isSuccess()) {\n contents.push(converted.value);\n }\n }\n }\n return contents;\n}\n"]}
1
+ {"version":3,"file":"chatRequestBuilders.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/chatRequestBuilders.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Per-format chat request shape builders. Shared between the synchronous\n * (`completionClient.ts`) and streaming (`streamingClient.ts`) paths so the\n * wire shapes stay consistent.\n *\n * @packageDocumentation\n */\n\nimport { isJsonObject, type JsonObject } from '@fgv/ts-json-base';\nimport { type Converter, Converters, fail, Result, succeed } from '@fgv/ts-utils';\n\nimport { type IAiCacheRequest, splitSystemForCache } from './cacheRequest';\nimport { AiPrompt, type IAiImageAttachment, type IChatMessage, toDataUrl } from './model';\n\n/**\n * The result of splitting an {@link AiAssist.IChatRequest} into the per-builder\n * inputs: the current turn (as an {@link AiPrompt}, carrying system + the final\n * user message + any attachments) and the preceding conversation history.\n * @internal\n */\nexport interface ISplitChatRequest {\n /** The current turn lowered to an {@link AiPrompt} (system + user + attachments). */\n readonly prompt: AiPrompt;\n /** Prior conversation history — every message before the current turn. */\n readonly head: ReadonlyArray<IChatMessage>;\n}\n\n/**\n * Splits a unified {@link AiAssist.IChatRequest} into `{ prompt, head }` for the\n * per-provider builders. The **last** message is the current `user` turn; the\n * preceding messages are history (`head`). This is the single linearization\n * shared by every turn entry point, so the completion path and the client-tool\n * turn path place history at the identical position relative to the current turn.\n *\n * Fails when `messages` is empty (no current turn) or when the last message is\n * not a `user` turn — relabelling a trailing assistant message as the user turn\n * would be a silent footgun, so it is rejected loudly instead.\n *\n * @internal\n */\nexport function splitChatRequest(\n system: string | undefined,\n messages: ReadonlyArray<IChatMessage>\n): Result<ISplitChatRequest> {\n if (messages.length === 0) {\n return fail('messages must contain at least one entry (the current user turn)');\n }\n const current = messages[messages.length - 1];\n if (current.role !== 'user') {\n return fail(`the last message must be the current user turn (role 'user'); got '${current.role}'`);\n }\n const head = messages.slice(0, messages.length - 1);\n const prompt = new AiPrompt(current.content, system ?? '', current.attachments);\n return succeed({ prompt, head });\n}\n\n/**\n * Rebuilds the ordered messages for a proxy wire body so that only the current\n * turn can carry attachments — history (non-current) messages are reduced to\n * `{ role, content }`. The direct per-provider builders already drop attachments\n * on history turns (only the current user turn's attachments are honored), so\n * normalizing here keeps the proxy wire shape consistent with the direct paths\n * and avoids transmitting attachment payloads the upstream provider would ignore.\n *\n * @internal\n */\nexport function normalizeOutboundMessages(split: ISplitChatRequest): IChatMessage[] {\n return [\n ...split.head.map((m) => ({ role: m.role, content: m.content })),\n ...split.prompt.toRequest().messages\n ];\n}\n\n/**\n * Converter for a rawTail message entry. Narrows a `JsonObject` to\n * `{ role: string; content: string | unknown[] }` at runtime using the\n * Converter pattern. Entries that fail validation are silently skipped — the\n * surrounding function is infallible, and a malformed continuation message is\n * better omitted than transmitted verbatim.\n * @internal\n */\nconst rawTailMessageConverter: Converter<{ role: 'user' | 'assistant'; content: string | unknown[] }> =\n Converters.object<{ role: 'user' | 'assistant'; content: string | unknown[] }>(\n {\n role: Converters.enumeratedValue<'user' | 'assistant'>(['user', 'assistant']),\n content: Converters.oneOf<string | unknown[]>([\n Converters.string,\n Converters.isA('array', (v): v is unknown[] => Array.isArray(v))\n ])\n },\n { strict: false }\n );\n\n/**\n * Converter for an OpenAI / xAI Responses API `rawTail` item. These are\n * provider-native input items (`function_call`, `function_call_output`) whose\n * fields differ per item type, so — unlike the Anthropic `{ role, content }`\n * projection — the whole object is preserved verbatim.\n *\n * The static input is already typed `JsonObject`, so the `isJsonObject` guard\n * is a runtime backstop, not a compile-time narrowing: continuation messages\n * originate from a prior turn's `IAiClientToolContinuation.messages` and a\n * consumer may persist and reload them through untyped JSON before passing them\n * back. The guard preserves the same \"a malformed continuation message is\n * better omitted than transmitted verbatim\" posture as the Anthropic path —\n * non-object entries fail conversion and are skipped by the caller.\n * @internal\n */\nconst openAiRawTailItemConverter: Converter<JsonObject> = Converters.isA<JsonObject>(\n 'JsonObject',\n (v): v is JsonObject => isJsonObject(v)\n);\n\n/**\n * Converter for a Gemini `rawTail` item. Gemini continuation messages are\n * `{ role, parts }` turns (a model turn with `functionCall` parts followed by a\n * user turn with `functionResponse` parts). Narrows a `JsonObject` to\n * `{ role: 'user' | 'model'; parts: Array<Record<string, unknown>> }`; entries\n * that fail validation are skipped by the caller.\n * @internal\n */\nconst geminiRawTailMessageConverter: Converter<{\n role: 'user' | 'model';\n parts: unknown[];\n}> = Converters.object<{ role: 'user' | 'model'; parts: unknown[] }>(\n {\n role: Converters.enumeratedValue<'user' | 'model'>(['user', 'model']),\n // `parts` is preserved verbatim and serialized into the request body, so the\n // element shape is not narrowed here — `Array.isArray` soundly guarantees\n // `unknown[]` (narrowing to `Record<string, unknown>[]` would be an unchecked cast).\n parts: Converters.isA('array', (v): v is unknown[] => Array.isArray(v))\n },\n { strict: false }\n);\n\n/**\n * Optional history (`head`) and raw continuation (`rawTail`) messages to weave\n * around the prompt's current user message.\n *\n * @internal\n */\nexport interface IBuildMessagesOptions {\n /**\n * Prior conversation history inserted between the system prompt and the\n * prompt's current user message (multi-turn chat / correction retries). The\n * single ordered linearization is `[system, ...head, user, ...rawTail]`.\n */\n readonly head?: ReadonlyArray<IChatMessage>;\n /**\n * Raw JSON objects appended after the prompt's user message. Used to\n * inject provider-specific continuation messages (e.g. Anthropic assistant\n * turns with thinking blocks, OpenAI Responses `function_call` /\n * `function_call_output` items, Gemini `functionCall` / `functionResponse`\n * turns) that cannot be expressed as plain {@link IChatMessage} objects.\n *\n * Each builder applies its own provider-specific shape guard:\n * - {@link buildAnthropicMessages} projects each entry to `{ role, content }`.\n * - {@link buildMessages} (OpenAI / xAI Responses) preserves each item\n * verbatim (item fields differ per `type`), guarding only that it is a\n * JSON object.\n * - {@link buildGeminiContents} projects each entry to `{ role, parts }`.\n *\n * Entries that fail their builder's shape check are silently skipped (the\n * caller is responsible for supplying well-formed continuation messages).\n * Appended after the current user message.\n */\n readonly rawTail?: ReadonlyArray<JsonObject>;\n}\n\n/**\n * Builds the messages array from prompt + optional history (`head`) and raw\n * continuation (`rawTail`) messages. The caller supplies the system content\n * (string when no cache breakpoints are requested, parts array when they are\n * — see {@link buildOpenAiChatSystemContent} / {@link buildOpenAiResponsesSystemContent})\n * and the user content (string for text-only, parts array for vision prompts),\n * since both shapes differ by format.\n *\n * `rawTail` items (OpenAI / xAI Responses `function_call` /\n * `function_call_output` continuation items) are appended verbatim after the\n * user message — their fields differ per item `type`, so they are preserved\n * rather than projected. The return type is `Array<Record<string, unknown>>`\n * to accommodate both `{ role, content }` messages and these heterogeneous\n * input items.\n *\n * @internal\n */\nexport function buildMessages(\n systemContent: string | unknown[],\n userContent: string | unknown[],\n options?: IBuildMessagesOptions\n): Array<Record<string, unknown>> {\n const messages: Array<Record<string, unknown>> = [{ role: 'system', content: systemContent }];\n if (options?.head) {\n for (const msg of options.head) {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n messages.push({ role: 'user', content: userContent });\n // OpenAI / xAI Responses continuation items (function_call /\n // function_call_output) are appended verbatim — their field set differs per\n // item type, so the whole object is preserved rather than projected.\n if (options?.rawTail) {\n for (const item of options.rawTail) {\n const converted = openAiRawTailItemConverter.convert(item);\n if (converted.isSuccess()) {\n messages.push(converted.value);\n }\n }\n }\n return messages;\n}\n\n/**\n * Splits `segments` (from {@link splitSystemForCache}) into a single string when nothing carries\n * a breakpoint, or a content-part array in `partType`'s shape (with `prompt_cache_breakpoint` on\n * the parts that end a cacheable prefix) otherwise.\n *\n * @remarks\n * A single non-breakpointed segment (the common case: `cache` omitted or empty) returns the\n * plain string, byte-identical to a request built before cache support existed. Shared by\n * {@link buildOpenAiChatSystemContent} and {@link buildOpenAiResponsesSystemContent}, whose only\n * difference is the content-part `type` OpenAI's two APIs use (`'text'` vs `'input_text'`,\n * research.md §1.2).\n * @internal\n */\nfunction toOpenAiSystemContent(\n segments: ReadonlyArray<{ readonly text: string; readonly cacheBreakpoint: boolean }>,\n partType: 'text' | 'input_text'\n): string | unknown[] {\n if (segments.length === 1 && !segments[0].cacheBreakpoint) {\n return segments[0].text;\n }\n return segments.map((seg) => ({\n type: partType,\n text: seg.text,\n ...(seg.cacheBreakpoint\n ? {\n // eslint-disable-next-line @typescript-eslint/naming-convention -- wire field name\n prompt_cache_breakpoint: { mode: 'explicit' }\n }\n : {})\n }));\n}\n\n/**\n * Builds the system content for OpenAI Chat Completions, splitting `system` at `cache`'s\n * breakpoints (design.md §6.3) into content parts each carrying\n * `prompt_cache_breakpoint: { mode: 'explicit' }` where a cacheable prefix ends.\n * `prompt_cache_options` is never touched — OpenAI keeps its own implicit breakpoint and writes\n * up to the latest three of ours on top of it. Returns the plain `system` string when `cache` is\n * `undefined` or declares no breakpoints, byte-identical to a request built before cache support\n * existed.\n * @internal\n */\nexport function buildOpenAiChatSystemContent(\n system: string,\n cache: IAiCacheRequest | undefined\n): Result<string | unknown[]> {\n return splitSystemForCache(system, cache).onSuccess((segments) =>\n succeed(toOpenAiSystemContent(segments, 'text'))\n );\n}\n\n/**\n * Builds the system content for the OpenAI / xAI Responses API. Identical to\n * {@link buildOpenAiChatSystemContent} except for the content-part type (`'input_text'` instead\n * of `'text'`) — see research.md §1.2, \"Where the breakpoint may be attached.\"\n * @internal\n */\nexport function buildOpenAiResponsesSystemContent(\n system: string,\n cache: IAiCacheRequest | undefined\n): Result<string | unknown[]> {\n return splitSystemForCache(system, cache).onSuccess((segments) =>\n succeed(toOpenAiSystemContent(segments, 'input_text'))\n );\n}\n\n/**\n * Builds the Anthropic `system` field, splitting `system` at `cache`'s breakpoints into content\n * blocks carrying `cache_control: { type: 'ephemeral' }` on the block that ends each cacheable\n * prefix (design.md §6.1). Returns the plain `system` string when `cache` is `undefined` or\n * declares no breakpoints — byte-identical to `system: prompt.system` from a request built\n * before cache support existed.\n * @internal\n */\nexport function buildAnthropicSystem(\n system: string,\n cache: IAiCacheRequest | undefined\n): Result<string | unknown[]> {\n return splitSystemForCache(system, cache).onSuccess((segments) => {\n if (segments.length === 1 && !segments[0].cacheBreakpoint) {\n return succeed<string | unknown[]>(segments[0].text);\n }\n return succeed<string | unknown[]>(\n segments.map((seg) => ({\n type: 'text',\n text: seg.text,\n ...(seg.cacheBreakpoint ? { cache_control: { type: 'ephemeral' } } : {})\n }))\n );\n });\n}\n\n/**\n * Builds the user content for OpenAI Chat Completions when attachments are\n * present. Returns a string when there are no attachments.\n *\n * @internal\n */\nexport function buildOpenAiChatUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'image_url',\n image_url: {\n url: toDataUrl(att),\n ...(att.detail !== undefined ? { detail: att.detail } : {})\n }\n }))\n ];\n}\n\n/**\n * Builds the user content for OpenAI / xAI Responses API when attachments\n * are present. Responses API uses `input_text` / `input_image` part types,\n * distinct from Chat Completions' `text` / `image_url`.\n *\n * @internal\n */\nexport function buildOpenAiResponsesUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'input_text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'input_image',\n image_url: toDataUrl(att),\n ...(att.detail !== undefined ? { detail: att.detail } : {})\n }))\n ];\n}\n\n/**\n * Builds the user-message content for Anthropic when attachments are present.\n *\n * @internal\n */\nexport function buildAnthropicUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'image',\n source: {\n type: 'base64',\n media_type: att.mimeType,\n data: att.base64\n }\n }))\n ];\n}\n\n/**\n * Builds the Gemini `parts` array for the user turn, including any image\n * attachments as `inlineData` parts.\n *\n * @internal\n */\nexport function buildGeminiUserParts(prompt: AiPrompt): Array<Record<string, unknown>> {\n const parts: Array<Record<string, unknown>> = [{ text: prompt.user }];\n for (const att of prompt.attachments) {\n parts.push({ inlineData: { mimeType: att.mimeType, data: att.base64 } });\n }\n return parts;\n}\n\n/**\n * Builds the Anthropic messages array, weaving any `head` history messages\n * between implicit system + the prompt's user message and appending `rawTail`\n * continuation messages after. System messages are filtered out (Anthropic uses\n * a top-level system field).\n *\n * @internal\n */\nexport function buildAnthropicMessages(\n prompt: AiPrompt,\n options?: IBuildMessagesOptions\n): Array<{ role: string; content: string | unknown[] }> {\n const messages: Array<{ role: string; content: string | unknown[] }> = [];\n if (options?.head) {\n for (const msg of options.head) {\n if (msg.role !== 'system') {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n }\n messages.push({ role: 'user', content: buildAnthropicUserContent(prompt) });\n if (options?.rawTail) {\n for (const msg of options.rawTail) {\n const converted = rawTailMessageConverter.convert(msg);\n if (converted.isSuccess()) {\n messages.push(converted.value);\n }\n }\n }\n return messages;\n}\n\n/**\n * Builds the Gemini `contents` array, weaving any `head` history messages before\n * the prompt's user parts and appending `rawTail` continuation messages after.\n * System messages are filtered out (Gemini uses a top-level systemInstruction\n * field) and assistant roles are mapped to Gemini's `model` role.\n *\n * @internal\n */\nexport function buildGeminiContents(\n prompt: AiPrompt,\n options?: IBuildMessagesOptions\n): Array<{ role: string; parts: unknown[] }> {\n const contents: Array<{ role: string; parts: unknown[] }> = [];\n if (options?.head) {\n for (const msg of options.head) {\n if (msg.role !== 'system') {\n contents.push({\n role: msg.role === 'assistant' ? 'model' : msg.role,\n parts: [{ text: msg.content }]\n });\n }\n }\n }\n contents.push({ role: 'user', parts: buildGeminiUserParts(prompt) });\n // Gemini continuation turns (model `functionCall` parts + user\n // `functionResponse` parts) are projected to `{ role, parts }`.\n if (options?.rawTail) {\n for (const item of options.rawTail) {\n const converted = geminiRawTailMessageConverter.convert(item);\n if (converted.isSuccess()) {\n contents.push(converted.value);\n }\n }\n }\n return contents;\n}\n"]}
@@ -85,6 +85,19 @@ export declare function callProviderCompletion(params: IProviderCompletionParams
85
85
  * CORS, and API key forwarding. The request body serializes the unified
86
86
  * {@link AiAssist.IChatRequest} shape (`system?` + `messages`). Enforces the same
87
87
  * non-empty / trailing-user-turn and image-input invariants as the direct path.
88
+ *
89
+ * @remarks
90
+ * Two parameters are handled before the body is composed, rather than forwarded.
91
+ * `tier` is resolved here and sent as a concrete `modelOverride`, because both
92
+ * halves of the resolution live on the descriptor the caller already holds — so a
93
+ * proxy needs no `tier` vocabulary, where a `tier` body field it did not understand
94
+ * would be ignored and silently serve a frontier request from the base model.
95
+ * `endpoint` is **refused**: it names where the prompt must go, the proxy is what
96
+ * makes that call, and the field has never been sent to one — so honoring it is
97
+ * unverifiable and ignoring it would reach the provider's default upstream instead
98
+ * of the host the caller pinned. Use `callProviderCompletion`, or point the
99
+ * proxy itself at the intended upstream.
100
+ *
88
101
  * @param proxyUrl - Base URL of the proxy server
89
102
  * @param params - Same parameters as {@link callProviderCompletion}
90
103
  * @public
@@ -1 +1 @@
1
- {"version":3,"file":"completionClient.d.ts","sourceRoot":"","sources":["../../../src/packlets/ai-assist/completionClient.ts"],"names":[],"mappings":"AA8BA,OAAO,EAGL,KAAK,OAAO,EACZ,MAAM,EAIP,MAAM,eAAe,CAAC;AAEvB,OAAO,EAEL,KAAK,kBAAkB,EAEvB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAE1B,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,SAAS,EAMf,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAyCtD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAYvE;;;;;GAKG;AACH,MAAM,WAAW,yBAA0B,SAAQ,YAAY;IAC7D,8BAA8B;IAC9B,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC;IAC3C,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,qGAAqG;IACrG,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IACxC,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC;IAClC,uGAAuG;IACvG,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;IACnD,kEAAkE;IAClE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC;IACpC;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IACpD;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;CAClC;AA0lBD;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAwMxC;AAMD;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAuHxC"}
1
+ {"version":3,"file":"completionClient.d.ts","sourceRoot":"","sources":["../../../src/packlets/ai-assist/completionClient.ts"],"names":[],"mappings":"AA8BA,OAAO,EAGL,KAAK,OAAO,EACZ,MAAM,EAIP,MAAM,eAAe,CAAC;AAEvB,OAAO,EAEL,KAAK,kBAAkB,EAEvB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAE1B,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,SAAS,EAMf,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AA2CtD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAYvE;;;;;GAKG;AACH,MAAM,WAAW,yBAA0B,SAAQ,YAAY;IAC7D,8BAA8B;IAC9B,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC;IAC3C,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,qGAAqG;IACrG,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IACxC,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC;IAClC,uGAAuG;IACvG,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;IACnD,kEAAkE;IAClE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC;IACpC;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IACpD;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;CAClC;AA0lBD;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAwMxC;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAwLxC"}