@ggui-ai/ui-gen 0.8.0 → 0.9.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 (52) hide show
  1. package/dist/adapters/generation-dispatch.d.ts +10 -0
  2. package/dist/adapters/generation-dispatch.js +172 -1392
  3. package/dist/adapters/generation-dispatch.js.map +1 -1
  4. package/dist/advanced/index.d.ts +1 -1
  5. package/dist/advanced/index.js +191 -1403
  6. package/dist/advanced/index.js.map +1 -1
  7. package/dist/boilerplate.js +3 -2
  8. package/dist/boilerplate.js.map +1 -1
  9. package/dist/compose.js +5 -5
  10. package/dist/compose.js.map +1 -1
  11. package/dist/{create-ui-generator-GX7tvbyH.d.ts → create-ui-generator-B9kV4U9h.d.ts} +13 -0
  12. package/dist/evaluation/axis-checks/index.js +32 -1
  13. package/dist/evaluation/axis-checks/index.js.map +1 -1
  14. package/dist/evaluation/axis-checks/registry.js +32 -1
  15. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  16. package/dist/evaluation/index.js +57 -12
  17. package/dist/evaluation/index.js.map +1 -1
  18. package/dist/fragments/index.js +5 -5
  19. package/dist/fragments/index.js.map +1 -1
  20. package/dist/harness/index.d.ts +4 -125
  21. package/dist/harness/index.js +34 -19
  22. package/dist/harness/index.js.map +1 -1
  23. package/dist/harness/runtime.d.ts +9 -0
  24. package/dist/harness/runtime.js +3 -2
  25. package/dist/harness/runtime.js.map +1 -1
  26. package/dist/harness/types-public.js +8 -7
  27. package/dist/harness/types-public.js.map +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js +191 -1403
  30. package/dist/index.js.map +1 -1
  31. package/dist/llm-router-1TyI0_TV.d.ts +156 -0
  32. package/dist/providers/index.d.ts +35 -93
  33. package/dist/providers/index.js +13 -8
  34. package/dist/providers/index.js.map +1 -1
  35. package/dist/validation/index.js +2 -2
  36. package/dist/validation/index.js.map +1 -1
  37. package/package.json +18 -18
  38. package/src/adapters/generation-dispatch.ts +19 -2
  39. package/src/boilerplate/system-prompt.ts +3 -2
  40. package/src/create-ui-generator.ts +44 -11
  41. package/src/evaluation/axis-checks/checks/state-merge.ts +1 -1
  42. package/src/evaluation/axis-checks/checks/universal.ts +39 -0
  43. package/src/evaluation/llm-evaluator.ts +13 -3
  44. package/src/evaluation/visual-evaluator.ts +5 -1
  45. package/src/fragments/realtime.ts +3 -3
  46. package/src/fragments/state.ts +2 -2
  47. package/src/harness/coding/init-session.ts +4 -1
  48. package/src/harness/coding/run-eval-round.ts +6 -0
  49. package/src/harness/llm-router.ts +59 -14
  50. package/src/harness/runtime.ts +8 -0
  51. package/src/providers/bedrock.ts +84 -22
  52. package/src/validation/ui-compiler.ts +2 -2
@@ -0,0 +1,156 @@
1
+ import { JsonObject } from '@ggui-ai/protocol';
2
+ import { LLMToolDef } from './llm.js';
3
+
4
+ interface AgentConfig {
5
+ provider: 'anthropic' | 'openai' | 'google' | 'openrouter';
6
+ model: string;
7
+ /**
8
+ * Sampling temperature. When defined it is threaded into the provider
9
+ * request; when undefined the provider default is used (unchanged
10
+ * behavior for all existing callers).
11
+ */
12
+ temperature?: number;
13
+ /**
14
+ * Explicit per-call credentials/routing, bypassing `process.env`
15
+ * entirely when supplied. Absent (default) preserves today's
16
+ * behavior — each agent falls back to reading its provider's env
17
+ * var(s) directly, so existing callers (including self-hosters who
18
+ * set env vars once at process start) are unaffected. Use this to
19
+ * run concurrent generations safely in one process: two simultaneous
20
+ * calls with different `routeOverride`s never race on shared
21
+ * `process.env` state.
22
+ */
23
+ routeOverride?: {
24
+ readonly apiKey?: string;
25
+ /** Anthropic-only: force the Bedrock-IAM client instead of the direct API client. */
26
+ readonly useBedrock?: boolean;
27
+ };
28
+ }
29
+ interface LLMResponse {
30
+ text: string;
31
+ inputTokens: number;
32
+ outputTokens: number;
33
+ }
34
+ interface LLMTool {
35
+ name: string;
36
+ description: string;
37
+ parameters: JsonObject;
38
+ handler: (args: JsonObject) => Promise<{
39
+ content: Array<{
40
+ text: string;
41
+ }>;
42
+ isError?: boolean;
43
+ }>;
44
+ }
45
+ interface LLMWithToolsResponse {
46
+ text: string;
47
+ inputTokens: number;
48
+ outputTokens: number;
49
+ turnsUsed: number;
50
+ }
51
+ interface LLMToolCall {
52
+ /** Provider-specific call ID (for sendToolResult) */
53
+ id?: string;
54
+ name: string;
55
+ input: JsonObject;
56
+ }
57
+ interface LLMToolCallResponse {
58
+ toolCalls: LLMToolCall[];
59
+ inputTokens: number;
60
+ outputTokens: number;
61
+ /**
62
+ * Tokens read from / written to the prompt cache, when the provider
63
+ * reports them. Optional because not every provider exposes
64
+ * prompt-cache accounting — absent means "unreported", never zero.
65
+ */
66
+ cacheReadTokens?: number;
67
+ cacheCreationTokens?: number;
68
+ }
69
+ /** Result of executing a tool — passed to sendToolResult to close the API contract. */
70
+ interface LLMToolResult {
71
+ /** Tool call ID from the response (for providers that need it) */
72
+ callId?: string;
73
+ /** Tool name */
74
+ name: string;
75
+ /** Text result of executing the tool */
76
+ result: string;
77
+ /** Whether the tool execution failed */
78
+ isError?: boolean;
79
+ }
80
+ declare abstract class LLMAgent {
81
+ abstract readonly provider: AgentConfig['provider'];
82
+ private client;
83
+ protected lastSessionId: string | undefined;
84
+ /**
85
+ * Explicit per-call routing override — see `AgentConfig.routeOverride`.
86
+ * Absent when constructed via the bare `new XAgent()` form (every
87
+ * existing call site), so `createClient()`/`resolveModel()` fall
88
+ * back to `process.env` exactly as before.
89
+ */
90
+ protected readonly routeOverride: AgentConfig['routeOverride'];
91
+ constructor(routeOverride?: AgentConfig['routeOverride']);
92
+ protected abstract resolveModel(model: string): string;
93
+ protected abstract createClient(): Promise<unknown>;
94
+ protected getClient<T>(): Promise<T>;
95
+ /** Text-only call — no tools */
96
+ abstract callText(model: string, systemPrompt: string, userPrompt: string, maxTokens?: number, temperature?: number): Promise<LLMResponse>;
97
+ /**
98
+ * Single-turn function calling — returns tool calls without executing them.
99
+ * Each provider uses its native function/tool calling:
100
+ * - Anthropic: tool_use blocks
101
+ * - OpenAI: function_call output items
102
+ * - Google: functionCall parts
103
+ * SDK handles JSON escaping — safe for code, diffs, and other content.
104
+ */
105
+ abstract callTools(model: string, systemPrompt: string, userPrompt: string, tools: LLMToolDef[], toolChoice?: 'required' | 'auto',
106
+ /**
107
+ * Optional scoped fallback tools. If the primary tools fail with a
108
+ * transport-class error (e.g. `malformed_tool_call` on Gemini after
109
+ * retry exhaustion), the provider may retry once with these narrower
110
+ * tools before throwing. Universal signal — not provider-gated.
111
+ */
112
+ scopedTools?: LLMToolDef[]): Promise<LLMToolCallResponse>;
113
+ /** Multi-turn agentic loop — executes tools internally */
114
+ abstract callWithTools(model: string, systemPrompt: string, userPrompt: string, tools: LLMTool[], maxTurns: number): Promise<LLMWithToolsResponse>;
115
+ /**
116
+ * Pre-warm cache for repeated callTools() calls with the same system prompt + tools.
117
+ * Override in providers that support server-side context caching (e.g., Google).
118
+ * No-op by default (Anthropic/OpenAI handle caching automatically per-request).
119
+ */
120
+ warmCache(_model: string, _systemPrompt: string, _tools: LLMToolDef[], _toolChoice?: 'required' | 'auto'): Promise<void>;
121
+ /** Cleanup any cached resources. Call after generation completes. No-op by default. */
122
+ cleanup(): Promise<void>;
123
+ /** Reset session state between independent generation runs. */
124
+ resetSession(): void;
125
+ /**
126
+ * Send tool execution results back to the provider to close the API contract.
127
+ * Call this after executing tools from callTools() and before the next callTools().
128
+ *
129
+ * For providers with server-side state (Google, OpenAI), this sends the
130
+ * function results so the next callTools() can chain properly.
131
+ * For stateless providers (Anthropic), this is a no-op.
132
+ *
133
+ * Override in providers that need it.
134
+ */
135
+ sendToolResult(_results: LLMToolResult[]): Promise<void>;
136
+ /**
137
+ * Execute an API call. No retry — if it fails, it fails.
138
+ * Logs the error with provider context and re-throws.
139
+ */
140
+ protected apiCall<T>(fn: () => Promise<T>): Promise<T>;
141
+ }
142
+ /**
143
+ * Create a fresh agent instance for the given provider.
144
+ * Each call returns a new instance — no shared state between callers.
145
+ *
146
+ * Accepts either a bare provider string (every pre-existing call
147
+ * site — the agent falls back to reading its provider's env var(s)
148
+ * directly) or a full {@link AgentConfig}, whose `routeOverride`
149
+ * (when present) is threaded into the constructed agent so it never
150
+ * touches `process.env`.
151
+ */
152
+ declare function createAgent(provider: AgentConfig['provider']): LLMAgent;
153
+ declare function createAgent(config: AgentConfig): LLMAgent;
154
+ declare function callLLM(config: AgentConfig, systemPrompt: string, userPrompt: string, maxTokens?: number): Promise<LLMResponse>;
155
+
156
+ export { type AgentConfig as A, type LLMResponse as L, createAgent as a, callLLM as c };
@@ -1,5 +1,4 @@
1
1
  import { ProviderAdapter } from '../provider-adapter.js';
2
- import { AnthropicBedrock } from '@anthropic-ai/bedrock-sdk';
3
2
  import { LlmProvider } from '@ggui-ai/mcp-server-core';
4
3
  import '@ggui-ai/protocol';
5
4
 
@@ -72,95 +71,36 @@ interface OpenRouterAdapterOptions {
72
71
  declare function createOpenRouterAdapter(options?: OpenRouterAdapterOptions): ProviderAdapter;
73
72
 
74
73
  /**
75
- * Concrete AWS Bedrock `ProviderAdapter` — invokes Anthropic Claude
76
- * models on Bedrock via the official `@anthropic-ai/bedrock-sdk`
77
- * package. IAM-based auth (no API key in flight); the AWS credential
78
- * chain (IRSA pod token / `~/.aws/credentials` / env vars) supplies
79
- * SigV4 signatures automatically.
80
- *
81
- * ## Why this adapter exists
82
- *
83
- * Anyone running the generator on an AWS-credentialed host (EC2, ECS,
84
- * Lambda, EKS) can target Bedrock without managing API keys: IAM is
85
- * the auth boundary, AWS rotates host credentials automatically, and
86
- * a misconfigured IAM role surfaces as a clear `AccessDeniedException`
87
- * the SDK funnels through `mapError`. The alternative — parking a
88
- * provider API key in a secrets store and lazily fetching it on first
89
- * render — works but adds operational surface (a seed ceremony,
90
- * key-rotation discipline, a misconfig mode where the secret is
91
- * empty); Bedrock removes all of it.
92
- *
93
- * ## Wire shape
94
- *
95
- * The Bedrock SDK's `client.messages.create(...)` mirrors the direct
96
- * Anthropic API surface 1:1 — same request body fields (`model`,
97
- * `max_tokens`, `system`, `messages`), same response shape (`content[]`
98
- * with `{type:'text', text}` blocks, `stop_reason`, `usage`). Both
99
- * adapters therefore share ONE success-envelope parser —
100
- * {@link parseAnthropicMessagesResponse} in `./anthropic-wire.ts`.
101
- * Streaming is supported by the SDK
102
- * (`client.messages.stream(...)` returns an async iterable) but this
103
- * adapter uses the non-streaming `create(...)` call to match the
104
- * single-completion {@link ProviderAdapter} contract; higher layers
105
- * (`UiGenerator`) compose multi-turn loops above the seam.
106
- *
107
- * ## Auth — no API key in `ProviderRequest`
108
- *
109
- * The {@link ProviderAdapter} contract types `ProviderRequest.apiKey`
110
- * as a required string because direct API providers need it on the
111
- * wire. Bedrock doesn't — the SDK signs requests with AWS credentials
112
- * resolved at process boot. Two compatible options were considered:
113
- *
114
- * 1. Add an `auth: 'apiKey' | 'iam'` discriminator to
115
- * `ProviderAdapter` + thread it through every adapter.
116
- * 2. Override `validateConfig` so this adapter accepts (and ignores)
117
- * whatever the caller puts in `apiKey` — `'iam'` / sentinel /
118
- * empty string all pass.
119
- *
120
- * Option 2 wins on cost: ZERO callers, contract, or tests change.
121
- * Callers pass a sentinel (e.g. `'bedrock-iam'`) so the model-id
122
- * check still gates on a non-empty value. Future work could add the
123
- * discriminator if a third auth mode (e.g. cross-account assume-role)
124
- * lands.
125
- *
126
- * ## Model IDs — pass-through
127
- *
128
- * Bedrock and the direct Anthropic API use OVERLAPPING but DISTINCT
129
- * model id namespaces:
130
- *
131
- * - Direct API: `claude-haiku-4-5`, `claude-opus-4-7`, etc.
132
- * - Bedrock foundation models: `anthropic.claude-sonnet-4-6`
133
- * - Bedrock cross-region inference profiles: `us.anthropic.claude-haiku-4-5-20251001-v1:0`
134
- *
135
- * The adapter passes whatever `request.model` contains straight to
136
- * Bedrock — translation lives in the caller (model picker / deployment
137
- * config). Supply a Bedrock foundation-model or inference-profile id,
138
- * not a direct-API model name.
139
- *
140
- * ## Failure mapping
141
- *
142
- * The bedrock-sdk throws `APIError` subclasses (`AuthenticationError`,
143
- * `PermissionDeniedError`, `RateLimitError`, `InternalServerError`,
144
- * etc.) that all carry a numeric `.status` property. We classify them
145
- * by structural duck-typing (`typeof err.status === 'number'`) rather
146
- * than `instanceof Anthropic.APIError` because pnpm hoisting often
147
- * resolves multiple `@anthropic-ai/sdk` versions across the workspace
148
- * — the bedrock-sdk's nested SDK and ui-gen's direct SDK can diverge,
149
- * and `instanceof` then silently returns false for valid errors.
150
- * Duck-typing on `.status` is robust to that drift and matches the
151
- * SDK's documented API contract (every subclass exposes `.status`).
152
- *
153
- * Transport-shaped errors (`APIConnectionError`, `APIUserAbortError`)
154
- * carry NO status — they fall through to {@link classifyFetchError}
155
- * for the standard `network` / `aborted` mapping.
156
- *
157
- * AWS-specific failure modes (`AccessDeniedException` — wrong IAM
158
- * grants; `ThrottlingException` — Bedrock rate limit;
159
- * `ValidationException` — bad model id) all surface as `APIError`
160
- * subclasses in the SDK, distinguished by `.status` (403 / 429 / 400
161
- * respectively).
74
+ * Which Bedrock endpoint a model id resolves to. The namespaces are
75
+ * disjoint (see the docstring above), so the shape of the id fully
76
+ * determines the endpoint — no configuration knob needed.
162
77
  */
163
-
78
+ type BedrockEndpoint = 'runtime' | 'mantle';
79
+ /**
80
+ * Structural slice of the two SDK clients the adapter actually drives —
81
+ * the seam that lets tests inject a stub without standing up a real
82
+ * AWS client. Both `AnthropicBedrock` and `AnthropicBedrockMantle`
83
+ * satisfy it (the adapter's single-completion contract only ever calls
84
+ * non-streaming `messages.create`; the response is parsed from
85
+ * `unknown` by `parseAnthropicMessagesResponse`, so the SDK's
86
+ * version-volatile `Message` type is deliberately not part of the
87
+ * seam).
88
+ */
89
+ interface BedrockMessagesClient {
90
+ readonly messages: {
91
+ create(body: {
92
+ model: string;
93
+ max_tokens: number;
94
+ system?: string;
95
+ messages: Array<{
96
+ role: 'user';
97
+ content: string;
98
+ }>;
99
+ }, options?: {
100
+ signal?: AbortSignal;
101
+ }): Promise<unknown>;
102
+ };
103
+ }
164
104
  /**
165
105
  * Constructor options for the Bedrock adapter.
166
106
  *
@@ -181,11 +121,13 @@ interface BedrockAdapterOptions {
181
121
  readonly region?: string;
182
122
  /**
183
123
  * Optional client factory override — used by tests to inject a
184
- * mock or stub `AnthropicBedrock` client without actually hitting
185
- * AWS. Production callers leave this unset; the adapter constructs
186
- * the real client lazily on first `complete(...)` call.
124
+ * mock or stub client without actually hitting AWS. Called with the
125
+ * endpoint the request's model id routed to (`'runtime'` →
126
+ * `AnthropicBedrock`, `'mantle'` → `AnthropicBedrockMantle`).
127
+ * Production callers leave this unset; the adapter constructs the
128
+ * real client lazily on first `complete(...)` call per endpoint.
187
129
  */
188
- readonly clientFactory?: (region: string) => AnthropicBedrock;
130
+ readonly clientFactory?: (region: string, endpoint: BedrockEndpoint) => BedrockMessagesClient;
189
131
  }
190
132
  /**
191
133
  * Construct an AWS Bedrock provider adapter.
@@ -1,4 +1,4 @@
1
- import { AnthropicBedrock } from '@anthropic-ai/bedrock-sdk';
1
+ import { AnthropicBedrockMantle, AnthropicBedrock } from '@anthropic-ai/bedrock-sdk';
2
2
 
3
3
  // src/provider-adapter.ts
4
4
  function makeProviderError(args) {
@@ -609,14 +609,19 @@ function createOpenRouterAdapter(options = {}) {
609
609
  }
610
610
  var PROVIDER5 = "bedrock";
611
611
  var DEFAULT_MAX_TOKENS2 = 4096;
612
+ function bedrockEndpointFor(model) {
613
+ return model.startsWith("anthropic.") ? "mantle" : "runtime";
614
+ }
612
615
  function createBedrockAdapter(options = {}) {
613
616
  const region = options.region ?? process.env["AWS_REGION"] ?? "us-east-1";
614
- const clientFactory = options.clientFactory ?? ((r) => new AnthropicBedrock({ awsRegion: r }));
615
- let cachedClient = null;
616
- function getClient() {
617
- if (cachedClient) return cachedClient;
618
- cachedClient = clientFactory(region);
619
- return cachedClient;
617
+ const clientFactory = options.clientFactory ?? ((r, endpoint) => endpoint === "mantle" ? new AnthropicBedrockMantle({ awsRegion: r }) : new AnthropicBedrock({ awsRegion: r }));
618
+ const cachedClients = {};
619
+ function getClient(endpoint) {
620
+ const cached = cachedClients[endpoint];
621
+ if (cached) return cached;
622
+ const created = clientFactory(region, endpoint);
623
+ cachedClients[endpoint] = created;
624
+ return created;
620
625
  }
621
626
  function mapError(raw) {
622
627
  if (raw && typeof raw === "object" && "status" in raw) {
@@ -667,7 +672,7 @@ function createBedrockAdapter(options = {}) {
667
672
  error: classifyFetchError(null, PROVIDER5, request.signal)
668
673
  };
669
674
  }
670
- const client = getClient();
675
+ const client = getClient(bedrockEndpointFor(request.route.model));
671
676
  let raw;
672
677
  try {
673
678
  raw = await client.messages.create(