@combycode/llm-sdk 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +206 -0
  2. package/dist/agent/loop-internals.d.ts +4 -0
  3. package/dist/agent/loop.d.ts +35 -0
  4. package/dist/{llm/providers → catalog}/builtin-tools.d.ts +1 -1
  5. package/dist/{plugins/model-catalog → catalog}/catalog.d.ts +34 -0
  6. package/dist/helpers/client-pool.d.ts +1 -1
  7. package/dist/helpers/client-resolver.d.ts +1 -1
  8. package/dist/helpers/engine.d.ts +12 -1
  9. package/dist/helpers/mcp.d.ts +6 -1
  10. package/dist/helpers/models.d.ts +1 -1
  11. package/dist/helpers/one-shot.d.ts +2 -2
  12. package/dist/helpers/select-model.d.ts +1 -1
  13. package/dist/index.browser.js +1438 -891
  14. package/dist/index.d.ts +4 -4
  15. package/dist/index.js +1438 -891
  16. package/dist/llm/client-config.d.ts +1 -1
  17. package/dist/llm/client-internal.d.ts +11 -0
  18. package/dist/llm/client.d.ts +4 -0
  19. package/dist/llm/providers/_shared/sse.d.ts +19 -0
  20. package/dist/llm/providers/google/files.d.ts +15 -0
  21. package/dist/llm/providers/google/media.d.ts +22 -4
  22. package/dist/llm/providers/google/realtime.d.ts +15 -2
  23. package/dist/llm/providers/openai/media.d.ts +10 -1
  24. package/dist/llm/providers/openai/realtime.d.ts +15 -2
  25. package/dist/llm/server-state.d.ts +1 -1
  26. package/dist/llm/types/options.d.ts +2 -2
  27. package/dist/llm/types/request.d.ts +50 -1
  28. package/dist/plugins/context-measurer/counter/count-api.d.ts +1 -1
  29. package/dist/plugins/context-measurer/counter/heuristic.d.ts +1 -1
  30. package/dist/plugins/context-measurer/counter/hybrid.d.ts +1 -1
  31. package/dist/plugins/context-measurer/measurer.d.ts +1 -1
  32. package/dist/plugins/cost-collector/collector.d.ts +1 -1
  33. package/dist/plugins/cost-collector/cost-collector-internal.d.ts +1 -1
  34. package/dist/plugins/cost-collector/cost-collector-types.d.ts +1 -1
  35. package/dist/plugins/files/registry.d.ts +1 -1
  36. package/dist/plugins/files/strategy.d.ts +1 -1
  37. package/dist/plugins/internal-tools/registry.d.ts +1 -1
  38. package/dist/plugins/internal-tools/runner/types.d.ts +1 -1
  39. package/dist/plugins/mcp/sampling.d.ts +23 -1
  40. package/dist/plugins/media/output.d.ts +1 -1
  41. package/dist/plugins/telemetry/telemetry.d.ts +2 -133
  42. package/dist/plugins/telemetry/types.d.ts +139 -0
  43. package/dist/util/hash.d.ts +8 -0
  44. package/dist/{plugins/media → util}/source-image.d.ts +1 -1
  45. package/dist/wire/inherit.d.ts +47 -0
  46. package/dist/wire/interpreter.d.ts +236 -0
  47. package/dist/wire/registry.d.ts +18 -0
  48. package/dist/wire/transforms.d.ts +22 -0
  49. package/package.json +3 -3
@@ -1,7 +1,7 @@
1
1
  /** LLMClient configuration types. */
2
2
  import type { HookBus } from '../bus/hook-bus';
3
3
  import type { EngineFetch, EngineFetchStream } from '../network/types';
4
- import type { ModelCatalog } from '../plugins/model-catalog/catalog';
4
+ import type { ModelCatalog } from '../catalog/catalog';
5
5
  import type { RequestContext } from '../types/request-context';
6
6
  import type { ApiType, ProviderAdapter, ProviderName } from './types/provider';
7
7
  import type { NormalizedRequest } from './types/request';
@@ -34,6 +34,17 @@ export declare function extractSystem(messages: Message[]): {
34
34
  /** Strip leading/trailing markdown fences and JSON.parse. Exported so AgentLoop
35
35
  * + helper can share the same parsing rules. */
36
36
  export declare function parseStructured<T>(text: string): T;
37
+ /** The routing names `buildContext` needs from a client.
38
+ *
39
+ * These were read with `as unknown as { queueName: string }` casts straight into
40
+ * LLMClient's privates — which compiles, and silently returns `undefined` the
41
+ * day a field is renamed. LLMClient now exposes them deliberately as
42
+ * `client.routing`, so a rename is a type error instead. */
43
+ export interface ClientRouting {
44
+ readonly queueName: string;
45
+ readonly configName: string;
46
+ readonly cacheName: string;
47
+ }
37
48
  export declare function buildContext(client: LLMClient, options: ExecuteOptions): RequestContext;
38
49
  export declare function resolveApi(provider: ProviderName, api?: ApiType | 'auto'): ApiType;
39
50
  export declare function resolveAdapter(config: LLMClientConfig, api: ApiType): ProviderAdapter;
@@ -25,6 +25,7 @@ import type { ApiType, ProviderName } from './types/provider';
25
25
  import type { CompletionResponse, FileOutput } from './types/response';
26
26
  import type { StreamEvent } from './types/stream';
27
27
  import type { LLMClientConfig } from './client-config';
28
+ import { type ClientRouting } from './client-internal';
28
29
  export declare class LLMClient {
29
30
  readonly id: string;
30
31
  /** Trace session id (from the engine, or self-minted for a standalone client). */
@@ -44,6 +45,9 @@ export declare class LLMClient {
44
45
  private readonly queueName;
45
46
  private readonly configName;
46
47
  private readonly cacheName;
48
+ /** The routing names this client was configured with, exposed so context
49
+ * building does not have to cast into the privates above. */
50
+ readonly routing: ClientRouting;
47
51
  private readonly cacheKeyFn?;
48
52
  private readonly catalog;
49
53
  constructor(config: LLMClientConfig);
@@ -0,0 +1,19 @@
1
+ /** The one piece of SSE handling every provider genuinely shares.
2
+ *
3
+ * Report 035 proposed a shared "SSE -> StreamEvent parser" because the parsing
4
+ * looked duplicated ~5x. Measured, it is not: the six parse bodies (76-149
5
+ * lines each) share ZERO runs of three or more identical lines, because they
6
+ * decode different wire schemas — Anthropic's `content_block_*` events,
7
+ * OpenAI chat's `choices[].delta`, OpenAI Responses' typed events, and Google's
8
+ * `candidates[].content.parts`. A "shared" parser would be a switch on provider
9
+ * wearing a common signature.
10
+ *
11
+ * What IS shared is exactly this line, repeated six times. Naming it gives the
12
+ * three language ports one primitive to agree on instead of six independent
13
+ * decisions, and one place to harden if malformed frames ever need handling
14
+ * (today a bad frame throws out of the parser, which is the existing
15
+ * behaviour and deliberately unchanged here).
16
+ */
17
+ import type { SSEEvent } from '../../../network/types';
18
+ /** Decode an SSE frame's `data` payload as a JSON object. */
19
+ export declare function sseJson(event: SSEEvent): Record<string, unknown>;
@@ -3,6 +3,21 @@
3
3
  import type { EngineFetch } from '../../../network/types';
4
4
  import type { FileAttachment } from '../../../plugins/files/attachment';
5
5
  import type { FileProviderAdapter, FileUploadResult, RemoteFileInfo } from '../../../plugins/files/provider-adapter';
6
+ /** Reduce any form of Google file id to the bare name the REST path wants.
7
+ *
8
+ * Three forms reach this, and only two used to work:
9
+ *
10
+ * https://.../v1beta/files/abc the `uri` this adapter hands back from
11
+ * upload() and list() — matched on `/files/`
12
+ * abc a bare name — passed through
13
+ * files/abc Google's CANONICAL resource name, the `name`
14
+ * field its own API returns
15
+ *
16
+ * The third fell through the `/files/` test (no leading slash) and produced
17
+ * `/v1beta/files/files/abc` — a 404. It never broke this library's own
18
+ * round-trip, because upload() and list() return the `uri`; it broke the
19
+ * moment a caller passed the id Google itself gave them. */
20
+ export declare function googleFileName(remoteId: string): string;
6
21
  export interface GoogleFileAdapterConfig {
7
22
  apiKey: string;
8
23
  baseURL?: string;
@@ -1,6 +1,6 @@
1
1
  /** Google media adapter — Imagen (:predict) + Veo (:predictLongRunning).
2
2
  * All HTTP calls go through an injected EngineFetch (NetworkEngine queue). */
3
- import type { EngineFetch } from '../../../network/types';
3
+ import type { EngineFetch, HttpRequest } from '../../../network/types';
4
4
  import type { AudioGenRequest, ImageEditRequest, ImageGenRequest, MediaCapabilities, MediaProviderAdapter, RawMediaResult, VideoGenRequest, VideoStatus } from '../../../plugins/media/types';
5
5
  export interface GoogleMediaAdapterConfig {
6
6
  apiKey: string;
@@ -12,14 +12,32 @@ export declare class GoogleMediaAdapter implements MediaProviderAdapter {
12
12
  private readonly baseURL;
13
13
  constructor(config: GoogleMediaAdapterConfig);
14
14
  capabilities(): MediaCapabilities;
15
+ /** Imagen image generation: the Vertex-style `:predict` envelope. */
16
+ buildImagenRequest(req: ImageGenRequest, model?: string): HttpRequest;
17
+ /** The inline-media path shared by gemini image generation, editing and TTS. */
18
+ buildGenerateContentRequest(model: string, text: string, generationConfig: Record<string, unknown>, extraParts?: Array<Record<string, unknown>>): HttpRequest;
19
+ /** Veo video submission — a long-running operation, hence the endpoint. */
20
+ buildVideoRequest(req: VideoGenRequest, model?: string): HttpRequest;
21
+ /** `generationConfig` for the gemini image paths — generation and editing take
22
+ * the same one. */
23
+ private imageGenerationConfig;
24
+ /** The complete image request, whichever of the two Google image paths applies:
25
+ * Imagen models use `:predict`, gemini-* models generate inline via
26
+ * `:generateContent` steered by responseModalities. */
27
+ buildImageRequest(req: ImageGenRequest, model?: string): HttpRequest;
28
+ /** Gemini TTS: the same inline path with an AUDIO modality and a speechConfig. */
29
+ buildAudioRequest(req: AudioGenRequest, model?: string): HttpRequest;
30
+ /** Image-to-image edit: image generation plus the source image as a second part. */
31
+ buildEditImageRequest(req: ImageEditRequest, model?: string): HttpRequest;
15
32
  generateImage(req: ImageGenRequest, fetch: EngineFetch): Promise<RawMediaResult[]>;
16
33
  generateAudio(req: AudioGenRequest, fetch: EngineFetch): Promise<RawMediaResult>;
17
34
  /** Image-to-image edit: gemini generateContent with the source image as an
18
35
  * extra inline/file part next to the instruction. */
19
36
  editImage(req: ImageEditRequest, fetch: EngineFetch): Promise<RawMediaResult[]>;
20
- /** Shared inline-media path: POST :generateContent and collect inlineData
21
- * parts + the reported token usage (token-priced media). */
22
- private generateContentMedia;
37
+ /** Collect inlineData parts + reported usage from a `:generateContent`
38
+ * response. The request half is `buildGenerateContentRequest`; keeping the two
39
+ * apart is what lets a request be asserted without performing it. */
40
+ private parseGenerateContent;
23
41
  submitVideo(req: VideoGenRequest, fetch: EngineFetch): Promise<string>;
24
42
  getVideoStatus(operationId: string, fetch: EngineFetch): Promise<VideoStatus>;
25
43
  downloadVideo(operationId: string, fetch: EngineFetch): Promise<RawMediaResult>;
@@ -13,8 +13,8 @@
13
13
  *
14
14
  * Gemini Live models are audio-native: with responseModalities ['AUDIO'] the
15
15
  * parts carry inlineData audio, not text. */
16
- import type { EngineConnect } from '../../../network/types';
17
- import type { RealtimeProviderAdapter, RealtimeSession, RealtimeSessionConfig } from '../../realtime/types';
16
+ import type { EngineConnect, WsRequest } from '../../../network/types';
17
+ import type { RealtimeInput, RealtimeProviderAdapter, RealtimeSession, RealtimeSessionConfig } from '../../realtime/types';
18
18
  export interface GoogleRealtimeAdapterConfig {
19
19
  apiKey: string;
20
20
  baseURL?: string;
@@ -23,5 +23,18 @@ export declare class GoogleRealtimeAdapter implements RealtimeProviderAdapter {
23
23
  private readonly apiKey;
24
24
  private readonly base;
25
25
  constructor(config: GoogleRealtimeAdapterConfig);
26
+ /** The WebSocket descriptor. Separated from `connect` so it can be asserted
27
+ * without opening a socket. Gemini authenticates with a query-string key and
28
+ * does NOT name the model in the URL — that goes in the setup frame. */
29
+ buildConnectRequest(config: RealtimeSessionConfig): WsRequest;
26
30
  connect(config: RealtimeSessionConfig, connect: EngineConnect): RealtimeSession;
27
31
  }
32
+ /** The handshake frame. Pure: a function of the session config, so it can be
33
+ * asserted without opening a socket. Gemini Live names the model HERE rather
34
+ * than in the URL, which is the opposite of OpenAI. */
35
+ export declare function buildGoogleSetupFrame(config: RealtimeSessionConfig): Record<string, unknown>;
36
+ /** The frames for one turn. Gemini carries turn completion as a FIELD, where
37
+ * OpenAI signals it by sending a second frame. */
38
+ export declare function buildGoogleTurnFrames(input: RealtimeInput, opts?: {
39
+ turnComplete?: boolean;
40
+ }): Array<Record<string, unknown>>;
@@ -1,7 +1,7 @@
1
1
  /** OpenAI media adapter — image generation (/v1/images/generations) and TTS
2
2
  * (/v1/audio/speech). All HTTP calls flow through an injected EngineFetch
3
3
  * so they share the NetworkEngine queue, rate-limits, retry, and hooks. */
4
- import type { EngineFetch } from '../../../network/types';
4
+ import type { EngineFetch, HttpRequest } from '../../../network/types';
5
5
  import type { AudioGenRequest, ImageEditRequest, ImageGenRequest, MediaCapabilities, MediaProviderAdapter, RawMediaResult, VideoGenRequest, VideoStatus } from '../../../plugins/media/types';
6
6
  export interface OpenAIMediaAdapterConfig {
7
7
  apiKey: string;
@@ -14,6 +14,15 @@ export declare class OpenAIMediaAdapter implements MediaProviderAdapter {
14
14
  constructor(config: OpenAIMediaAdapterConfig);
15
15
  capabilities(): MediaCapabilities;
16
16
  private authHeaders;
17
+ /** Text-to-image. */
18
+ buildGenerateImageRequest(req: ImageGenRequest, model?: string): HttpRequest;
19
+ /** Image-to-image edit. Generation's field set minus `style`, plus the source
20
+ * image and an optional mask. */
21
+ buildEditImageRequest(req: ImageEditRequest, model?: string): HttpRequest;
22
+ /** TTS. `responseType` is arraybuffer because the response is audio bytes. */
23
+ buildAudioRequest(req: AudioGenRequest, model: string): HttpRequest;
24
+ /** Sora video submission. `seconds` goes on the wire as a string. */
25
+ buildVideoRequest(req: VideoGenRequest, model?: string): HttpRequest;
17
26
  generateImage(req: ImageGenRequest, fetch: EngineFetch): Promise<RawMediaResult[]>;
18
27
  /** Parse `/v1/images/{generations,edits}` response → RawMediaResult[], with
19
28
  * the request-level usage attached to the first item (billed once). */
@@ -10,8 +10,8 @@
10
10
  * response.output_audio.delta → { audio, base64 delta }
11
11
  * response.done → turn complete
12
12
  * error → error */
13
- import type { EngineConnect } from '../../../network/types';
14
- import type { RealtimeProviderAdapter, RealtimeSession, RealtimeSessionConfig } from '../../realtime/types';
13
+ import type { EngineConnect, WsRequest } from '../../../network/types';
14
+ import type { RealtimeInput, RealtimeProviderAdapter, RealtimeSession, RealtimeSessionConfig } from '../../realtime/types';
15
15
  export interface OpenAIRealtimeAdapterConfig {
16
16
  apiKey: string;
17
17
  baseURL?: string;
@@ -20,5 +20,18 @@ export declare class OpenAIRealtimeAdapter implements RealtimeProviderAdapter {
20
20
  private readonly apiKey;
21
21
  private readonly baseURL;
22
22
  constructor(config: OpenAIRealtimeAdapterConfig);
23
+ /** The WebSocket descriptor. Separated from `connect` so it can be asserted
24
+ * without opening a socket. Note the auth: OpenAI carries the key in a
25
+ * SUBPROTOCOL, not a header or query param, because browsers cannot set
26
+ * WebSocket headers. */
27
+ buildConnectRequest(config: RealtimeSessionConfig): WsRequest;
23
28
  connect(config: RealtimeSessionConfig, connect: EngineConnect): RealtimeSession;
24
29
  }
30
+ /** The handshake frame, sent once the socket opens. */
31
+ export declare function buildOpenAISessionUpdate(config: Pick<RealtimeSessionConfig, 'modalities' | 'instructions' | 'voice'>): Record<string, unknown>;
32
+ /** The frames for one turn. `response.create` is what asks the model to reply,
33
+ * so `turnComplete: false` withholds it and the turn stays open — where Gemini
34
+ * carries the same meaning as a field on its single frame. */
35
+ export declare function buildOpenAITurnFrames(input: RealtimeInput, opts?: {
36
+ turnComplete?: boolean;
37
+ }): Array<Record<string, unknown>>;
@@ -13,7 +13,7 @@
13
13
  * - it's within the retention TTL (catalog duration),
14
14
  * - the model matches, OR the provider is not model-bound (catalog).
15
15
  * Otherwise we fall back to resending full history (always correct). */
16
- import type { ModelCatalog } from '../plugins/model-catalog/catalog';
16
+ import type { ModelCatalog } from '../catalog/catalog';
17
17
  import type { Message } from './types/messages';
18
18
  import type { ProviderName } from './types/provider';
19
19
  export interface ServerStateDecision {
@@ -3,7 +3,7 @@ import type { ConversationHistory } from '../../agent/history';
3
3
  import type { RequestContext } from '../../types/request-context';
4
4
  import type { ModerationRequest } from '../moderation/types';
5
5
  import type { AudioOptions } from './audio';
6
- import type { CacheConfig, ThinkingConfig } from './request';
6
+ import type { CacheConfig, ProviderOptions, ThinkingConfig } from './request';
7
7
  import type { ServiceTier } from './tiers';
8
8
  import type { Tool, ToolChoice } from './tools';
9
9
  export interface ExecuteOptions {
@@ -65,7 +65,7 @@ export interface ExecuteOptions {
65
65
  * OpenAI runs it natively; other providers are emulated via OpenAI's
66
66
  * moderations endpoint. See ModerationRequest. */
67
67
  moderation?: ModerationRequest;
68
- providerOptions?: Record<string, unknown>;
68
+ providerOptions?: ProviderOptions;
69
69
  previousResponseId?: string;
70
70
  /** Server-state optimization: when the prior assistant turn carries a usable
71
71
  * server id (same provider, within TTL, model ok), send the id + only the new
@@ -4,10 +4,54 @@
4
4
  * and system are fixed at construction. The LLMClient internally builds
5
5
  * this `NormalizedRequest` from (input, options, this.model, this.system). */
6
6
  import type { ModerationRequest } from '../moderation/types';
7
+ import type { ModelWire } from '../../catalog/catalog';
7
8
  import type { AudioOptions } from './audio';
8
9
  import type { Message } from './messages';
9
10
  import type { ServiceTier } from './tiers';
10
11
  import type { Tool, ToolChoice } from './tools';
12
+ /** Provider-specific request options that have no unified equivalent.
13
+ *
14
+ * This was `Record<string, unknown>` — the one untyped hole in the request, and
15
+ * therefore the one place a typo produced silence rather than an error:
16
+ * `promtCacheOptions` type-checked and was simply never sent.
17
+ *
18
+ * Every key below is one an adapter actually reads; the list is derived from
19
+ * the read sites, not invented. The index signature stays so a caller can still
20
+ * pass something the SDK does not know about yet — a provider ships a parameter
21
+ * before we model it, and refusing it would make the escape hatch useless. What
22
+ * changed is that the keys we DO know are checked and discoverable.
23
+ *
24
+ * Keys are grouped by the provider that consumes them; sending one to a
25
+ * different provider is ignored, not an error. */
26
+ export interface ProviderOptions {
27
+ /** Forwarded as the `anthropic-user-profile-id` header: identifies the end
28
+ * user a request acts on behalf of. Needs the account-level
29
+ * `user-profiles` beta. */
30
+ userProfileId?: string;
31
+ /** Native moderation policy, sent alongside the `moderation` request field. */
32
+ moderationPolicy?: Record<string, unknown>;
33
+ /** `prompt_cache_options` — OpenAI-only prompt-cache controls. */
34
+ promptCacheOptions?: Record<string, unknown>;
35
+ /** `reasoning.mode` on the Responses API. */
36
+ reasoningMode?: 'standard' | 'pro';
37
+ /** Overrides `generationConfig.responseModalities`, e.g. for image or audio
38
+ * generation. Wins over the modality implied by `outputModalities`. */
39
+ responseModalities?: string[];
40
+ /** `generationConfig.speechConfig` — voice selection for audio output. */
41
+ speechConfig?: Record<string, unknown>;
42
+ /** `generationConfig.imageConfig` — aspect ratio / size for image output. */
43
+ imageConfig?: Record<string, unknown>;
44
+ /** `generationConfig.translationConfig`. */
45
+ translationConfig?: Record<string, unknown>;
46
+ /** Name of a cached-content handle to reuse. Forwarded only when it is a
47
+ * non-empty string. */
48
+ cachedContent?: string;
49
+ /** Routing options merged into the request body (provider order, transforms,
50
+ * and the rest of OpenRouter's routing surface). */
51
+ openrouter?: Record<string, unknown>;
52
+ /** Anything the SDK does not model yet. Adapters ignore what they do not read. */
53
+ [key: string]: unknown;
54
+ }
11
55
  export interface NormalizedRequest {
12
56
  /** From LLMClientConfig.model — fixed at construction. */
13
57
  model: string;
@@ -47,7 +91,12 @@ export interface NormalizedRequest {
47
91
  cache?: CacheConfig;
48
92
  serviceTier?: ServiceTier;
49
93
  moderation?: ModerationRequest;
50
- providerOptions?: Record<string, unknown>;
94
+ providerOptions?: ProviderOptions;
95
+ /** Wire traits for THIS model, resolved from the catalog by `LLMClient`.
96
+ * Adapters prefer this over parsing the model id. Absent when the engine runs
97
+ * without a catalog or the model is not catalogued, in which case adapters
98
+ * fall back to the id. */
99
+ wire?: ModelWire;
51
100
  audio?: AudioOptions;
52
101
  outputModalities?: Array<'text' | 'audio'>;
53
102
  previousResponseId?: string;
@@ -2,7 +2,7 @@
2
2
  import type { Message } from '../../../llm/types/messages';
3
3
  import type { TokenCountContext, TokenCounter, LearnInput } from '../../../agent/types';
4
4
  import type { FetchFn } from '../../../network/types';
5
- import type { ModelCatalog } from '../../model-catalog/catalog';
5
+ import type { ModelCatalog } from '../../../catalog/catalog';
6
6
  /** Anthropic count endpoint: POST /v1/messages/count_tokens */
7
7
  export declare class AnthropicCountApi {
8
8
  private readonly apiKey;
@@ -1,7 +1,7 @@
1
1
  /** Heuristic token counter — chars-per-token with optional calibration. */
2
2
  import type { Message } from '../../../llm/types/messages';
3
3
  import type { TokenCountContext, TokenCounter, LearnInput } from '../../../agent/types';
4
- import type { ModelCatalog } from '../../model-catalog/catalog';
4
+ import type { ModelCatalog } from '../../../catalog/catalog';
5
5
  import type { CalibrationStore } from '../types';
6
6
  /** Count chars across a message's content parts. */
7
7
  export declare function messageChars(msg: Message): number;
@@ -1,7 +1,7 @@
1
1
  /** HybridTokenCounter — selects strategy per model based on catalog config. */
2
2
  import type { Message } from '../../../llm/types/messages';
3
3
  import type { TokenCountContext, TokenCounter, LearnInput } from '../../../agent/types';
4
- import type { ModelCatalog } from '../../model-catalog/catalog';
4
+ import type { ModelCatalog } from '../../../catalog/catalog';
5
5
  import type { CalibrationStore } from '../types';
6
6
  export interface HybridCounterConfig {
7
7
  catalog?: ModelCatalog;
@@ -4,7 +4,7 @@ import type { HookBus } from '../../bus/hook-bus';
4
4
  import type { Message } from '../../llm/types/messages';
5
5
  import type { TokenCounter } from '../../agent/types';
6
6
  import type { ConversationHistory } from '../../agent/history';
7
- import type { ModelCatalog } from '../model-catalog/catalog';
7
+ import type { ModelCatalog } from '../../catalog/catalog';
8
8
  import type { Persistence } from '../persistence/types';
9
9
  import type { CalibrationStore, ContextThresholds, CalibrationConfig } from './types';
10
10
  export interface ContextMeasurerConfig {
@@ -2,7 +2,7 @@
2
2
  * pricing or provider-reported totals, emits onCostEntry/onBudgetWarning/
3
3
  * onBudgetExceeded. */
4
4
  import type { CostEntry } from '../../bus/hook-map';
5
- import type { ModelCatalog } from '../model-catalog/catalog';
5
+ import type { ModelCatalog } from '../../catalog/catalog';
6
6
  import type { Budget, CostCollectorConfig, CostFilter, CostSummary } from './cost-collector-types';
7
7
  export declare class CostCollector {
8
8
  private ledger;
@@ -1,7 +1,7 @@
1
1
  /** CostCollector internals — pure cost-math + filtering helpers, split out of
2
2
  * the class so each is independently testable. */
3
3
  import type { CostEntry } from '../../bus/hook-map';
4
- import type { ModelCatalog } from '../model-catalog/catalog';
4
+ import type { ModelCatalog } from '../../catalog/catalog';
5
5
  import type { CostFilter, CostSummary } from './cost-collector-types';
6
6
  /** Pull provider-reported cost evidence out of a raw response body. */
7
7
  export declare function extractProviderCost(provider: string, raw: unknown): Record<string, unknown>;
@@ -1,6 +1,6 @@
1
1
  /** CostCollector public types: config, budgets, filters, summaries. */
2
2
  import type { HookBus } from '../../bus/hook-bus';
3
- import type { ModelCatalog } from '../model-catalog/catalog';
3
+ import type { ModelCatalog } from '../../catalog/catalog';
4
4
  export interface CostCollectorConfig {
5
5
  hooks: HookBus;
6
6
  catalog: ModelCatalog;
@@ -3,7 +3,7 @@
3
3
  * parts (provider_ref / inline base64 / url). */
4
4
  import type { HookBus } from '../../bus/hook-bus';
5
5
  import type { EngineFetch } from '../../network/types';
6
- import type { ModelCatalog } from '../model-catalog/catalog';
6
+ import type { ModelCatalog } from '../../catalog/catalog';
7
7
  import { FileAttachment, type FileContent } from './attachment';
8
8
  import type { FileProviderAdapter, RemoteFileInfo } from './provider-adapter';
9
9
  import { type FileStrategy } from './strategy';
@@ -1,5 +1,5 @@
1
1
  /** FileStrategy — pluggable decision maker for how to attach files. */
2
- import type { ModelInfo } from '../model-catalog/catalog';
2
+ import type { ModelInfo } from '../../catalog/catalog';
3
3
  import type { FileAttachment } from './attachment';
4
4
  export interface FileStrategyContext {
5
5
  file: FileAttachment;
@@ -1,6 +1,6 @@
1
1
  /** ToolRegistry — unified access across multiple backends with caching, search, filtering. */
2
2
  import type { InternalTool, ToolBackend, ToolFilter, SearchOptions } from './types';
3
- import type { ModelCatalog } from '../model-catalog/catalog';
3
+ import type { ModelCatalog } from '../../catalog/catalog';
4
4
  export declare class ToolRegistry {
5
5
  private backends;
6
6
  private cache;
@@ -3,7 +3,7 @@ import type { HookBus } from '../../../bus/hook-bus';
3
3
  import type { LLMClientConfig } from '../../../llm/client-config';
4
4
  import type { ProviderName } from '../../../llm/types/provider';
5
5
  import type { JsonSchema } from '../../../llm/types/tools';
6
- import type { ModelCatalog } from '../../model-catalog/catalog';
6
+ import type { ModelCatalog } from '../../../catalog/catalog';
7
7
  import type { ToolRegistry } from '../registry';
8
8
  import type { CompatFile, ModelPreference } from '../types';
9
9
  import type { TokenCounter } from '../../../agent/types';
@@ -4,6 +4,7 @@
4
4
  * custom handler or a model id to auto-wire. */
5
5
  import type { EngineHandle } from '../../helpers/engine';
6
6
  import type { ProviderName } from '../../llm/types/provider';
7
+ import type { Message } from '../../llm/types/messages';
7
8
  import type { McpCreateMessageParams, McpCreateMessageResult } from './types';
8
9
  export type McpSamplingHandler = (params: McpCreateMessageParams) => Promise<McpCreateMessageResult>;
9
10
  /** Auto-wire sampling to our LLM. */
@@ -13,5 +14,26 @@ export interface McpSamplingViaLLM {
13
14
  engine?: EngineHandle;
14
15
  }
15
16
  export type McpSamplingConfig = McpSamplingHandler | McpSamplingViaLLM;
17
+ /** The one thing this module needs from the ergonomic layer: run a completion.
18
+ *
19
+ * Taken as a parameter rather than imported, because `plugins` importing
20
+ * `helpers` closed a cycle (`helpers` already imports most of `plugins`). The
21
+ * public `samplingHandler` lives in `helpers/mcp` and supplies `complete`; the
22
+ * mapping between MCP's message shape and ours stays here, where it belongs. */
23
+ export type McpCompleteFn = (args: {
24
+ model: string;
25
+ provider?: ProviderName;
26
+ engine?: EngineHandle;
27
+ system?: string;
28
+ prompt: Message[];
29
+ maxTokens?: number;
30
+ temperature?: number;
31
+ }) => Promise<{
32
+ text: string;
33
+ response: {
34
+ model: string;
35
+ finishReason: string;
36
+ };
37
+ }>;
16
38
  /** Build a sampling handler: pass-through a custom function, or auto-wire a model. */
17
- export declare function samplingHandler(config: McpSamplingConfig): McpSamplingHandler;
39
+ export declare function samplingHandlerWith(complete: McpCompleteFn, config: McpSamplingConfig): McpSamplingHandler;
@@ -7,7 +7,7 @@
7
7
  * by core LLM adapters + an onCompletion subscriber, NOT this class. */
8
8
  import type { HookBus } from '../../bus/hook-bus';
9
9
  import type { EngineFetch } from '../../network/types';
10
- import type { ModelCatalog } from '../model-catalog/catalog';
10
+ import type { ModelCatalog } from '../../catalog/catalog';
11
11
  import { type AudioGenRequest, type ImageEditRequest, type ImageGenRequest, type MediaOutputConfig, type MediaProviderAdapter, type MediaResult, type MediaStore, type VideoGenRequest } from './types';
12
12
  export interface MediaOutputInit {
13
13
  hooks: HookBus;
@@ -10,7 +10,8 @@
10
10
  * An in-memory store backs the sandbox sidebar; `toOtlpTraces()` shapes spans
11
11
  * into OTLP-compatible JSON for a real OTel exporter to forward. */
12
12
  import type { HookBus } from '../../bus/hook-bus';
13
- import type { HookName } from '../../bus/hook-map';
13
+ export type { Span, SpanKind, TelemetryAdapterOptions, TelemetryEvent, TelemetryMetrics, TelemetryResource, TraceEvent, TraceEventType, TraceFilter, TraceHandler, } from './types';
14
+ import type { Span, TelemetryAdapterOptions, TelemetryEvent, TelemetryMetrics, TelemetryResource, TraceFilter, TraceHandler } from './types';
14
15
  /** Derive a conformant hex id from one of our readable ids. `bytes` is 16 for a trace
15
16
  * id, 8 for a span id.
16
17
  *
@@ -22,81 +23,6 @@ export declare function toOtlpId(input: string, bytes: 8 | 16): string;
22
23
  * `intValue` carrying a STRING, which is how OTLP/JSON encodes 64-bit integers; send
23
24
  * them as plain strings instead and no backend can sum them. */
24
25
  export declare function toOtlpValue(value: unknown): Record<string, unknown>;
25
- export type SpanKind = 'llm' | 'http' | 'media' | 'agent' | 'tool' | 'mcp' | 'other';
26
- export interface Span {
27
- traceId: string;
28
- spanId: string;
29
- /** The span this one runs under. Without it every span is a sibling and a backend
30
- * draws a flat list instead of a tree — so a run reads as "9 things happened", not
31
- * "a turn, which called a tool, which asked a second model".
32
- *
33
- * Resolved in this order: the innermost container span still open on this trace
34
- * (`agent.run` / `tool.call`), else the app's span from a supplied `traceparent`,
35
- * else none — this span is the root. */
36
- parentSpanId?: string;
37
- name: string;
38
- kind: SpanKind;
39
- startTime: number;
40
- endTime?: number;
41
- durationMs?: number;
42
- status: 'unset' | 'ok' | 'error';
43
- attributes: Record<string, unknown>;
44
- }
45
- /** What kind of work an event describes. `message` is conversation content, which is not
46
- * a span — it is the thing you want in a debug store and NOT in your metrics backend,
47
- * which is exactly why it filters separately. */
48
- export type TraceEventType = 'agent' | 'tool' | 'llm' | 'http' | 'mcp' | 'media' | 'message' | 'other';
49
- /** One piece of work, carrying enough of the tree that a consumer can push it straight
50
- * into their own tracer without reconstructing anything. */
51
- export interface TraceEvent {
52
- type: TraceEventType;
53
- /** The app's trace when it supplied a `traceparent`, else ours. */
54
- traceId: string;
55
- spanId: string;
56
- /** Already resolved past anything this subscriber filtered out — see `survivingParent`. */
57
- parentSpanId?: string;
58
- /** The conventional name (`chat gpt-5.4-nano`, `execute_tool search`). */
59
- name: string;
60
- startTime: number;
61
- endTime?: number;
62
- durationMs?: number;
63
- status: 'unset' | 'ok' | 'error';
64
- attributes: Record<string, unknown>;
65
- }
66
- /** Declarative on purpose, rather than a predicate: knowing the types up front lets a
67
- * filtered-out event cost nothing, where a predicate would force us to build the payload
68
- * just to let the caller throw it away. */
69
- export interface TraceFilter {
70
- types?: readonly TraceEventType[];
71
- }
72
- export type TraceHandler = (event: TraceEvent) => void;
73
- export interface TelemetryEvent {
74
- seq: number;
75
- time: number;
76
- name: HookName;
77
- category: string;
78
- traceId?: string;
79
- ctx: unknown;
80
- }
81
- export interface TelemetryMetrics {
82
- requests: number;
83
- errors: number;
84
- retries: number;
85
- rateLimitHits: number;
86
- completions: number;
87
- mediaGenerated: number;
88
- costUsd: number;
89
- inputTokens: number;
90
- outputTokens: number;
91
- inFlight: number;
92
- queueDepth: number;
93
- latency: {
94
- count: number;
95
- min: number;
96
- max: number;
97
- avg: number;
98
- };
99
- }
100
26
  /** Parse a W3C `traceparent`: `00-<32 hex trace>-<16 hex span>-<flags>`.
101
27
  * Returns null for anything malformed or for the all-zero ids the spec forbids —
102
28
  * a bad header must not silently reroute telemetry into a garbage trace. */
@@ -104,63 +30,6 @@ export declare function parseTraceparent(value: string | undefined): {
104
30
  traceId: string;
105
31
  spanId: string;
106
32
  } | null;
107
- /** OpenTelemetry Resource — identifies the SERVICE producing this telemetry, so
108
- * a shared backend can separate streams from different apps and attribute cost
109
- * per service (`sum by service.name`). Stamped on every span/metric/log. */
110
- export interface TelemetryResource {
111
- /** Primary grouping key, e.g. "billing-api". OTel default: "unknown_service". */
112
- serviceName: string;
113
- /** Optional namespace/group, e.g. "prod" or a team. */
114
- serviceNamespace?: string;
115
- /** Unique instance (pod/host/process); a good default is the engine sessionId. */
116
- serviceInstanceId?: string;
117
- serviceVersion?: string;
118
- /** Arbitrary resource attributes (deployment.environment, cloud.region, …). */
119
- attributes?: Record<string, string>;
120
- }
121
- export interface TelemetryAdapterOptions {
122
- /** Cap on retained events (ring buffer). Default 2000. */
123
- maxEvents?: number;
124
- /** Service identity stamped on all exported telemetry. */
125
- resource?: TelemetryResource;
126
- /** Whether provider error TEXT may be stored in telemetry. Default `true`
127
- * (unchanged behaviour, and the same default as the OpenAI Agents SDK's
128
- * `trace_include_sensitive_data`).
129
- *
130
- * A provider's `error.message` / `error.raw` can echo request content back —
131
- * a moderation refusal quotes the prompt, a validation error names the offending
132
- * field and value. URLs and headers are always redacted regardless; this switch
133
- * governs the free-text payload. Set `false` when telemetry leaves your trust
134
- * boundary (a shared collector, a vendor APM) and the message is replaced by a
135
- * fixed `[redacted]` string while name/code/status are kept for triage. */
136
- includeSensitiveData?: boolean;
137
- /** Which event types to hand to `onTrace`. Omitted → everything.
138
- *
139
- * Filtering SPLICES the tree rather than punching holes in it: drop `http` and the
140
- * spans under it re-parent to the nearest surviving ancestor. Dropping without that
141
- * leaves orphans, and a backend draws an orphan as a second root — worse than not
142
- * filtering at all. */
143
- types?: readonly TraceEventType[];
144
- /** Whether conversation content rides along on `message` events. Default `'none'`:
145
- * prompts and completions are the debugging gold AND the PII, so sending them is a
146
- * decision to make on purpose rather than inherit. `'full'` adds the Opt-In
147
- * `gen_ai.input.messages` / `gen_ai.output.messages` attributes; `'none'` still
148
- * reports the shape (counts and sizes), which is enough to spot a runaway prompt. */
149
- content?: 'none' | 'full';
150
- /** Fraction of TRACES to emit, 0..1. Default 1.
151
- *
152
- * Per trace, never per span: sampling spans independently shreds every tree it touches
153
- * — a tool call with no run, a model call with no tool. The decision is a hash of the
154
- * trace id, so it is stable across processes and two services sharing a trace agree
155
- * without coordinating.
156
- *
157
- * This is HEAD sampling: the choice is made when the trace first appears, before we
158
- * know whether it ends in an error. Keeping all errors needs tail sampling, which
159
- * needs buffering; do that in your collector, which is built for it. */
160
- sample?: number;
161
- /** Convenience for the common case of a single sink — same as calling `onTrace`. */
162
- onTrace?: TraceHandler;
163
- }
164
33
  export declare class TelemetryAdapter {
165
34
  readonly events: TelemetryEvent[];
166
35
  readonly spans: Span[];