@oh-my-pi/pi-ai 17.2.4 → 17.2.6

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 (53) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/types/dialect/examples.d.ts +18 -2
  3. package/dist/types/dialect/inventory.d.ts +6 -9
  4. package/dist/types/dialect/rendering.d.ts +9 -0
  5. package/dist/types/dialect/types.d.ts +0 -1
  6. package/dist/types/error/aws.d.ts +5 -1
  7. package/dist/types/error/flags.d.ts +5 -0
  8. package/dist/types/providers/aws-credentials.d.ts +5 -10
  9. package/dist/types/providers/bedrock-mantle.d.ts +13 -0
  10. package/dist/types/providers/openai-shared.d.ts +17 -5
  11. package/dist/types/providers/transform-messages.d.ts +1 -1
  12. package/dist/types/registry/amazon-bedrock.d.ts +7 -1
  13. package/dist/types/registry/aws.d.ts +13 -0
  14. package/dist/types/registry/bedrock-mantle.d.ts +22 -0
  15. package/dist/types/registry/registry.d.ts +25 -1
  16. package/dist/types/registry/types.d.ts +24 -0
  17. package/dist/types/types.d.ts +11 -4
  18. package/dist/types/utils/aws-profile.d.ts +17 -0
  19. package/dist/types/utils/harmony-leak.d.ts +9 -0
  20. package/dist/types/utils/schema/typescript.d.ts +8 -2
  21. package/package.json +4 -4
  22. package/src/auth-broker/discover.ts +2 -1
  23. package/src/auth-broker/wire-schema-resource.ts +6 -1
  24. package/src/auth-storage.ts +2 -2
  25. package/src/dialect/examples.ts +50 -12
  26. package/src/dialect/gemini.ts +17 -31
  27. package/src/dialect/harmony.ts +1 -2
  28. package/src/dialect/inventory.ts +21 -64
  29. package/src/dialect/rendering.ts +54 -0
  30. package/src/dialect/types.ts +0 -1
  31. package/src/error/aws.ts +5 -1
  32. package/src/error/flags.ts +8 -0
  33. package/src/providers/amazon-bedrock.ts +5 -5
  34. package/src/providers/anthropic.ts +77 -32
  35. package/src/providers/aws-credentials.ts +262 -76
  36. package/src/providers/bedrock-mantle.ts +110 -0
  37. package/src/providers/cursor.ts +6 -3
  38. package/src/providers/ollama.ts +27 -1
  39. package/src/providers/openai-codex-responses.ts +19 -5
  40. package/src/providers/openai-shared.ts +84 -35
  41. package/src/providers/transform-messages.ts +1 -1
  42. package/src/registry/amazon-bedrock.ts +9 -14
  43. package/src/registry/aws.ts +57 -0
  44. package/src/registry/bedrock-mantle.ts +34 -0
  45. package/src/registry/google-vertex.ts +2 -2
  46. package/src/registry/registry.ts +2 -0
  47. package/src/registry/types.ts +30 -0
  48. package/src/stream.ts +46 -30
  49. package/src/types.ts +14 -8
  50. package/src/utils/aws-profile.ts +88 -0
  51. package/src/utils/harmony-leak.ts +12 -0
  52. package/src/utils/schema/typescript.ts +21 -7
  53. package/src/utils.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.2.6] - 2026-08-03
6
+
7
+ ### Added
8
+
9
+ - Added profile-aware Bedrock Mantle region selection, authenticated model discovery, bearer-token or SigV4 authentication, and credential refresh handling for OpenAI Responses models.
10
+
11
+ ### Fixed
12
+
13
+ - Fixed an issue where Ollama requests without a user-role message would fail to generate output or silently fail with a misleading error.
14
+
15
+ ## [17.2.5] - 2026-08-03
16
+
17
+ ### Changed
18
+
19
+ - Standardized tool-call examples in `renderToolExamples` and `renderToolInventory` to use Python keyword-argument syntax (`name(key="value")`) across all models, removing the model-specific dialect parameter and the `DialectRenderOptions.example` flag.
20
+ - Updated `renderToolInventory` to render the tool catalog as a unified OpenAI-Harmony-style `## functions` block using TypeScript type declarations and comments, replacing the previous per-tool Markdown sections.
21
+ - Added a `style: "harmony"` option to `jsonSchemaToTypeScript` for generating compact, comma-delimited TypeScript definitions.
22
+
23
+ ### Fixed
24
+
25
+ - Fixed a session-blocking issue where unescaped Harmony control tokens in replayed assistant responses and tool inputs caused subsequent requests to be rejected with `invalid_prompt` errors.
26
+ - Fixed an issue where Codex Responses dropped native image-generation results from assistant content and replays due to stale `generating` statuses.
27
+ - Fixed Anthropic stream truncation handling where unexpected connection closures were incorrectly treated as clean stops, causing the agent loop to halt silently mid-sentence.
28
+ - Optimized Anthropic prompt caching to prevent unnecessary cache invalidation of the entire system prefix when volatile project footer details (such as current working directory, date, or workspace tree) change.
29
+
5
30
  ## [17.2.4] - 2026-08-01
6
31
 
7
32
  ### Fixed
@@ -1,2 +1,18 @@
1
- import type { Dialect, InbandTool } from "./types.js";
2
- export declare function renderToolExamples(tool: InbandTool, dialect: Dialect, intentField?: string): string;
1
+ import type { InbandTool } from "./types.js";
2
+ /**
3
+ * Render a tool's examples as an `<examples>` block. Calls render in Python
4
+ * keyword-argument syntax (`name(key="value", n=1)`) regardless of the model's
5
+ * tool-call dialect, so example bytes stay identical across models. Multiline
6
+ * string args render as verbatim `"""…"""` blocks, and a call whose only
7
+ * argument is a string renders as the bare value — the block already names the
8
+ * tool, and payload args (commands, code, patches) read best verbatim.
9
+ */
10
+ export declare function renderToolExamples(tool: InbandTool, intentField?: string): string;
11
+ /**
12
+ * Render a tool's examples as JSDoc-style `@example` lines for comment-gutter
13
+ * contexts (the Harmony `namespace functions` inventory): `@example "caption"`
14
+ * followed by the call in the same Python kwargs syntax as the wire block. The
15
+ * tag line delimits each example, so no XML envelope is needed — which is why
16
+ * the inventory uses this instead of `//`-prefixing the `<examples>` block.
17
+ */
18
+ export declare function renderToolExamplesJsdoc(tool: InbandTool): string;
@@ -1,12 +1,9 @@
1
1
  import type { InbandTool } from "./types.js";
2
2
  /**
3
- * Human-readable per-tool inventory: each tool renders as a `# Tool: <name>`
4
- * section with its description, a simplified TypeScript-style parameter
5
- * signature (derived from the wire JSON Schema), and examples in the model's
6
- * native dialect. Shared by the verbose system-prompt inventory and
7
- * `/dump` so both render the catalog the same way.
8
- *
9
- * `model` is a model id; the native example dialect is resolved from it
10
- * (`preferredDialect`, which falls back to XML for empty/unknown ids).
3
+ * Tool catalog in the OpenAI-Harmony `namespace functions { }` shape: each
4
+ * tool renders as its full description (and Python-syntax examples) as `//`
5
+ * comment lines above a flat `type <name> = (_: {…});` declaration. Shared by
6
+ * the verbose system-prompt inventory and `/dump` so both render the catalog
7
+ * the same way.
11
8
  */
12
- export declare function renderToolInventory(tools: readonly InbandTool[], model: string): string;
9
+ export declare function renderToolInventory(tools: readonly InbandTool[]): string;
@@ -4,6 +4,15 @@ export declare function renderToolResponseResults(results: readonly DialectToolR
4
4
  export declare function kimiCallId(name: string, id: string, index: number): string;
5
5
  export declare function harmonyRecipient(name: string): string;
6
6
  export declare function stringifyJson(value: unknown): string;
7
+ /**
8
+ * Render `name(key=value, …)` with Python-literal argument values. Top-level
9
+ * multiline strings render as verbatim `"""…"""` blocks so payload-carrying
10
+ * args (file content, scripts, patches) keep real newlines instead of `\n`
11
+ * escape soup; nested values always use escaped single-line literals.
12
+ */
13
+ export declare function pyCall(name: string, args: Record<string, unknown>): string;
14
+ /** Render a JSON-ish value as a Python literal (`True`/`False`/`None`, escaped strings, lists, dicts). */
15
+ export declare function pyValue(value: unknown): string;
7
16
  export declare function escapeXmlAttr(value: string): string;
8
17
  export declare function escapeXmlText(value: string): string;
9
18
  export type AssistantTranscriptParts = {
@@ -42,7 +42,6 @@ export interface DialectToolResult {
42
42
  }
43
43
  export interface DialectRenderOptions {
44
44
  readonly tools?: readonly InbandTool[];
45
- readonly example?: boolean;
46
45
  }
47
46
  export interface DialectDefinition {
48
47
  readonly dialect: CatalogDialect;
@@ -9,7 +9,11 @@ export type AwsCredentialsErrorKind =
9
9
  /** SSO `GetRoleCredentials` call failed or returned no role. */
10
10
  | "sso-role"
11
11
  /** External `credential_process` failed, timed out, or emitted bad output. */
12
- | "credential-process";
12
+ | "credential-process"
13
+ /** STS web-identity exchange failed or returned malformed credentials. */
14
+ | "web-identity"
15
+ /** ECS/container credential endpoint failed or returned malformed credentials. */
16
+ | "container";
13
17
  /** A failure resolving AWS credentials for the Bedrock provider. */
14
18
  export declare class AwsCredentialsError extends Error {
15
19
  readonly kind: AwsCredentialsErrorKind;
@@ -40,6 +40,11 @@ export declare function retriable(id: number | undefined, opts?: {
40
40
  }): boolean;
41
41
  export declare function status(error: unknown): number | undefined;
42
42
  export declare function isStreamReadErrorText(text: string): boolean;
43
+ /** Persisted-text form of {@link isStreamEnvelopeError}: recognizes the
44
+ * prefix-tagged envelope diagnostic on an aborted turn's `errorMessage` /
45
+ * `stopDetails.explanation` so loop-level salvage can classify it after the
46
+ * original `Error` instance is gone. */
47
+ export declare function isStreamEnvelopeErrorText(text: string): boolean;
43
48
  export declare function classify(error: unknown, api?: Api): number;
44
49
  /**
45
50
  * Whether an error (or message string) classifies as an account usage/quota
@@ -4,16 +4,11 @@
4
4
  * Chain (first hit wins):
5
5
  * 1. Static credentials from the environment
6
6
  * (`AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` [+ `AWS_SESSION_TOKEN`]).
7
- * 2. Profile in `~/.aws/credentials` (and `~/.aws/config` for SSO):
8
- * - static `aws_access_key_id` / `aws_secret_access_key` / `aws_session_token`
9
- * - SSO profile referencing a cached token in `~/.aws/sso/cache/*.json`,
10
- * which we exchange for short-lived role credentials via
11
- * `https://portal.sso.{region}.amazonaws.com/federation/credentials`.
12
- * - `credential_process` — an external command emitting the AWS SDK
13
- * `Version: 1` JSON envelope on stdout. Used by `aws-vault`, `granted`,
14
- * in-house brokers, etc.
15
- * 3. EC2 IMDSv2 (only when `AWS_EC2_METADATA_DISABLED` is unset / falsey and
16
- * `169.254.169.254` is reachable within a 1 s timeout).
7
+ * 2. Web identity (`AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN`).
8
+ * 3. Profile in `~/.aws/credentials` (and `~/.aws/config` for SSO):
9
+ * - static keys, SSO, or `credential_process`.
10
+ * 4. ECS/container credentials from `AWS_CONTAINER_CREDENTIALS_*`.
11
+ * 5. EC2 IMDSv2 when metadata is enabled.
17
12
  *
18
13
  * Resolved credentials are cached process-wide per profile and refreshed
19
14
  * 60 s before `Expiration` to absorb clock skew.
@@ -0,0 +1,13 @@
1
+ import { type AwsBedrockProviderOptions } from "../registry/aws.js";
2
+ import type { FetchImpl, Model } from "../types.js";
3
+ import type { OpenAIResponsesOptions } from "./openai-responses.js";
4
+ export type BedrockMantleProviderOptions = AwsBedrockProviderOptions;
5
+ export interface BedrockMantleOptions extends OpenAIResponsesOptions {
6
+ providerOptions?: BedrockMantleProviderOptions;
7
+ }
8
+ export declare function createBedrockMantleAuthenticatedFetch(options?: BedrockMantleOptions): FetchImpl;
9
+ export interface PreparedBedrockMantleRequest {
10
+ model: Model<"openai-responses">;
11
+ options: OpenAIResponsesOptions;
12
+ }
13
+ export declare function prepareBedrockMantleRequest(model: Model<"openai-responses">, options: BedrockMantleOptions): PreparedBedrockMantleRequest;
@@ -421,17 +421,27 @@ export interface BuildResponsesInputOptions<TApi extends Api> {
421
421
  preserveAssistantMessageIds?: boolean;
422
422
  }
423
423
  /**
424
- * Escape reserved Harmony control tokens in the client-boundary text of
425
- * replayed Responses input items user/developer/system message text and
426
- * tool-result output. Model-owned items (assistant output, reasoning, tool-call
427
- * arguments) carry no client data and are returned untouched.
424
+ * Escape reserved Harmony control tokens in the free-text fields of replayed
425
+ * Responses input items: user/developer/system text, tool-result output,
426
+ * assistant message text, and tool-call payloads.
427
+ *
428
+ * Tool-call items are covered deliberately. The original #6913 fix skipped
429
+ * model-owned items on the theory that they carry no client data — but a model
430
+ * legitimately writing *about* Harmony samples `<|channel|>` etc. into its own
431
+ * `function_call.arguments`, and a full-transcript replay (stale or blocked
432
+ * previous_response_id, provider fallback) feeds those bytes back as input,
433
+ * which gpt-5.x reject with invalid_prompt / "Request blocked", permanently
434
+ * poisoning the session. `arguments` is a JSON document, so it uses
435
+ * {@link escapeHarmonyControlTokensInJson} to stay parseable. Reasoning items
436
+ * are left untouched: `encrypted_content` is opaque and plaintext summaries
437
+ * are never rendered back into the prompt.
428
438
  *
429
439
  * Native history replay pushes stored `providerPayload` items straight onto the
430
440
  * wire, bypassing {@link convertResponsesInputContent}; without this a stored
431
441
  * `input_text` carrying `<|channel|>analysis` still reaches gpt-5.x raw (#6913).
432
442
  * Callers gate on {@link isHarmonyDialectModel}. Items are copied, not mutated.
433
443
  */
434
- export declare function escapeReplayedClientText(items: ResponseInput): ResponseInput;
444
+ export declare function escapeReplayedControlTokens(items: ResponseInput): ResponseInput;
435
445
  export declare function buildResponsesInput<TApi extends Api>(options: BuildResponsesInputOptions<TApi>): ResponseInput;
436
446
  export declare function convertResponsesAssistantMessage<TApi extends Api>(assistantMsg: AssistantMessage, model: Model<TApi>, msgIndex: number, knownCallIds: Set<string>, includeThinkingSignatures?: boolean, customCallIds?: Set<string>, preserveMessageIds?: boolean, supportsCustomToolCalls?: boolean, customToolWireNameMap?: ReadonlyMap<string, string>, computerCallIds?: Set<string>): ResponseInput;
437
447
  /** Appends one tool result while keeping consecutive outputs ahead of its synthetic image messages. */
@@ -516,6 +526,8 @@ export interface ProcessResponsesStreamOptions {
516
526
  requestServiceTier?: ServiceTier;
517
527
  }
518
528
  export declare function computerCallMetadata(item: ResponseComputerToolCall): ComputerToolCallMetadata;
529
+ /** Append a native Responses image result and emit its completion event. */
530
+ export declare function appendResponsesImageResult(output: AssistantMessage, stream: AssistantMessageEventStream, result: string): void;
519
531
  export declare function processResponsesStream<TApi extends Api>(openaiStream: AsyncIterable<ResponseStreamEvent>, output: AssistantMessage, stream: AssistantMessageEventStream, model: Model<TApi>, options?: ProcessResponsesStreamOptions): Promise<void>;
520
532
  export declare function mapOpenAIResponsesStopReason(status: ResponseStatus | undefined): StopReason;
521
533
  export declare function hasExecutableIncompleteResponsesToolCalls(output: AssistantMessage): boolean;
@@ -13,7 +13,7 @@ import type { Api, AssistantMessage, Message, Model } from "../types.js";
13
13
  * credential redaction is enabled. Exported so hosts can route the same shapes
14
14
  * through reversible obfuscation (keyed placeholders restored before local tool
15
15
  * execution) instead of the irreversible `[*_token_redacted]` rewrite below —
16
- * an irreversible placeholder echoed back in edit-tool `old_text` can never
16
+ * an irreversible placeholder echoed back in edit-tool `old_string` can never
17
17
  * match the real bytes on disk.
18
18
  */
19
19
  export declare const SENSITIVE_TOKEN_RE: RegExp;
@@ -1,5 +1,11 @@
1
+ import { resolveAwsRegistryApiKey } from "./aws.js";
1
2
  export declare const amazonBedrockProvider: {
2
3
  readonly id: "amazon-bedrock";
3
4
  readonly name: "Amazon Bedrock";
4
- readonly envKeys: () => "<authenticated>" | undefined;
5
+ readonly envKeys: typeof resolveAwsRegistryApiKey;
6
+ readonly mapSimpleOptions: (options: import("../index.js").SimpleStreamOptions) => {
7
+ region: string | undefined;
8
+ profile: string | undefined;
9
+ bearerToken: string | undefined;
10
+ };
5
11
  };
@@ -0,0 +1,13 @@
1
+ export interface AwsBedrockProviderOptions extends Readonly<Record<string, unknown>> {
2
+ /** AWS region used in the service endpoint and SigV4 credential scope. */
3
+ region?: string;
4
+ /** Named AWS shared-credentials/config profile. */
5
+ profile?: string;
6
+ /** Amazon Bedrock API key sent as a bearer token, ahead of SigV4 credential resolution. */
7
+ bearerToken?: string;
8
+ }
9
+ export declare function hasAwsCredentialSource(): boolean;
10
+ /** Registry key marker for AWS transports that resolve their own bearer/IAM credentials. */
11
+ export declare function resolveAwsRegistryApiKey(): string | undefined;
12
+ /** Resolve a real AWS bearer token while filtering the registry's auth marker. */
13
+ export declare function resolveAwsBearerToken(apiKey?: string, bearerToken?: string): string | undefined;
@@ -0,0 +1,22 @@
1
+ import type { Model } from "../types.js";
2
+ import { resolveAwsRegistryApiKey } from "./aws.js";
3
+ export declare const bedrockMantleProvider: {
4
+ readonly id: "bedrock-mantle";
5
+ readonly name: "Amazon Bedrock Mantle";
6
+ readonly envKeys: typeof resolveAwsRegistryApiKey;
7
+ readonly allowsMissingApiKey: true;
8
+ readonly prepareRequest: (model: Model<import("@oh-my-pi/pi-catalog").Api>, options: import("../index.js").StreamOptions) => import("../providers/bedrock-mantle.js").PreparedBedrockMantleRequest;
9
+ readonly mapSimpleOptions: (options: import("../index.js").SimpleStreamOptions) => {
10
+ providerOptions: Readonly<Record<string, unknown>> | undefined;
11
+ };
12
+ readonly prepareModelDiscovery: (config: import("./types.js").ProviderModelDiscoveryConfig) => {
13
+ baseUrl?: string;
14
+ fetch?: import("@oh-my-pi/pi-utils").FetchImpl;
15
+ apiKey: undefined;
16
+ authenticated: false;
17
+ } | {
18
+ authenticated: true;
19
+ baseUrl: string;
20
+ fetch: import("@oh-my-pi/pi-utils").FetchImpl;
21
+ };
22
+ };
@@ -25,7 +25,12 @@ declare const ALL: ({
25
25
  } | {
26
26
  readonly id: "amazon-bedrock";
27
27
  readonly name: "Amazon Bedrock";
28
- readonly envKeys: () => "<authenticated>" | undefined;
28
+ readonly envKeys: typeof import("./aws.js").resolveAwsRegistryApiKey;
29
+ readonly mapSimpleOptions: (options: import("../index.js").SimpleStreamOptions) => {
30
+ region: string | undefined;
31
+ profile: string | undefined;
32
+ bearerToken: string | undefined;
33
+ };
29
34
  } | {
30
35
  readonly id: "anthropic";
31
36
  readonly name: "Anthropic (Claude Pro/Max)";
@@ -41,6 +46,25 @@ declare const ALL: ({
41
46
  readonly id: "baseten";
42
47
  readonly name: "Baseten";
43
48
  readonly login: (cb: import("./oauth/index.js").OAuthLoginCallbacks) => Promise<string>;
49
+ } | {
50
+ readonly id: "bedrock-mantle";
51
+ readonly name: "Amazon Bedrock Mantle";
52
+ readonly envKeys: typeof import("./aws.js").resolveAwsRegistryApiKey;
53
+ readonly allowsMissingApiKey: true;
54
+ readonly prepareRequest: (model: import("@oh-my-pi/pi-catalog").Model<import("@oh-my-pi/pi-catalog").Api>, options: import("../index.js").StreamOptions) => import("../providers/bedrock-mantle.js").PreparedBedrockMantleRequest;
55
+ readonly mapSimpleOptions: (options: import("../index.js").SimpleStreamOptions) => {
56
+ providerOptions: Readonly<Record<string, unknown>> | undefined;
57
+ };
58
+ readonly prepareModelDiscovery: (config: import("./types.js").ProviderModelDiscoveryConfig) => {
59
+ baseUrl?: string;
60
+ fetch?: import("@oh-my-pi/pi-utils").FetchImpl;
61
+ apiKey: undefined;
62
+ authenticated: false;
63
+ } | {
64
+ authenticated: true;
65
+ baseUrl: string;
66
+ fetch: import("@oh-my-pi/pi-utils").FetchImpl;
67
+ };
44
68
  } | {
45
69
  readonly id: "cerebras";
46
70
  readonly name: "Cerebras";
@@ -9,6 +9,7 @@
9
9
  * (default model, model-manager factory, catalog discovery) lives in
10
10
  * `@oh-my-pi/pi-catalog`'s descriptor table.
11
11
  */
12
+ import type { Api, FetchImpl, Model, SimpleStreamOptions, StreamOptions } from "../types.js";
12
13
  import type { OAuthCredentials, OAuthLoginCallbacks } from "./oauth/types.js";
13
14
  /**
14
15
  * API-key environment fallback: either a single env var name (e.g.
@@ -16,6 +17,21 @@ import type { OAuthCredentials, OAuthLoginCallbacks } from "./oauth/types.js";
16
17
  * the host (Vertex ADC, Bedrock credential chains, …).
17
18
  */
18
19
  export type KeyResolver = string | (() => string | undefined);
20
+ /** Credentials are resolved by the provider transport rather than used as a bearer string. */
21
+ export declare const AUTHENTICATED_SENTINEL = "<authenticated>";
22
+ export interface PreparedProviderRequest {
23
+ readonly model: Model<Api>;
24
+ readonly options: StreamOptions;
25
+ }
26
+ export type ProviderRequestPreparer = (model: Model<Api>, options: StreamOptions) => PreparedProviderRequest;
27
+ export type ProviderSimpleOptionsMapper = (options: SimpleStreamOptions) => Readonly<Record<string, unknown>>;
28
+ export interface ProviderModelDiscoveryConfig {
29
+ readonly apiKey?: string;
30
+ readonly baseUrl?: string;
31
+ readonly fetch?: FetchImpl;
32
+ readonly authenticated?: boolean;
33
+ }
34
+ export type ProviderModelDiscoveryPreparer = (config: ProviderModelDiscoveryConfig) => ProviderModelDiscoveryConfig;
19
35
  /**
20
36
  * Declarative description of a single provider's auth/login wiring. All
21
37
  * fields are optional except `id`/`name`; presence of a field opts the
@@ -39,6 +55,14 @@ export interface ProviderDefinition {
39
55
  /** Whether to surface in the interactive login list. Defaults to true when `login` is present. */
40
56
  readonly showInLoginList?: boolean;
41
57
  readonly envKeys?: KeyResolver;
58
+ /** Provider transport can authenticate without a resolved API-key string. */
59
+ readonly allowsMissingApiKey?: boolean;
60
+ /** Provider-owned request shaping applied before generic API dispatch. */
61
+ readonly prepareRequest?: ProviderRequestPreparer;
62
+ /** Provider-owned projection from the generic simple-stream option bag. */
63
+ readonly mapSimpleOptions?: ProviderSimpleOptionsMapper;
64
+ /** Provider-owned authentication and endpoint setup for model discovery. */
65
+ readonly prepareModelDiscovery?: ProviderModelDiscoveryPreparer;
42
66
  readonly login?: (callbacks: OAuthLoginCallbacks) => Promise<OAuthCredentials | string>;
43
67
  readonly refreshToken?: (credentials: OAuthCredentials) => Promise<OAuthCredentials>;
44
68
  readonly getApiKey?: (credentials: OAuthCredentials) => string;
@@ -124,10 +124,10 @@ export declare function serviceTierFamily(model: ServiceTierModel): ServiceTierF
124
124
  export declare function resolveModelServiceTier(tiers: ServiceTierByFamily | null | undefined, model: Pick<Model, "provider" | "api" | "id">): ServiceTier | undefined;
125
125
  /**
126
126
  * True when the tier should be sent on the wire as the provider's service-tier
127
- * request field. OpenAI / OpenAI-Codex accept `flex`/`scale`/`priority`; Google
128
- * (Gemini API + Vertex) and OpenRouter accept `flex`/`priority`; Fireworks
129
- * Serverless realizes only its Priority serving path. Anthropic is absent — it
130
- * realizes `priority` via `speed: "fast"`, not a service-tier field.
127
+ * request field. OpenAI / OpenAI-Codex accept every {@link ServiceTier};
128
+ * Google (Gemini API + Vertex) and OpenRouter accept `flex`/`priority`;
129
+ * Fireworks Serverless realizes only its Priority serving path. Anthropic is
130
+ * absent because it realizes `priority` via `speed: "fast"`.
131
131
  */
132
132
  export declare function shouldSendServiceTier(serviceTier: ServiceTier | null | undefined, target: Provider | ServiceTierModel | undefined): boolean;
133
133
  /**
@@ -249,6 +249,11 @@ export interface StreamOptions {
249
249
  * For example, Anthropic uses `user_id` for abuse tracking and rate limiting.
250
250
  */
251
251
  metadata?: Record<string, unknown>;
252
+ /**
253
+ * Provider-owned request configuration. Provider hooks interpret this bag;
254
+ * generic API transports do not forward its fields onto the wire.
255
+ */
256
+ providerOptions?: Readonly<Record<string, unknown>>;
252
257
  /** OpenAI Responses/Codex response fields to include verbatim. */
253
258
  include?: OpenAIResponseInclude[];
254
259
  /**
@@ -660,6 +665,8 @@ export interface AssistantRetryRecovery {
660
665
  export interface ContextSnapshot {
661
666
  promptTokens: number;
662
667
  nonMessageTokens: number;
668
+ /** Estimated prompt tokens removed by local history rewrites after this provider snapshot was recorded. */
669
+ historyRewriteTokensRemoved?: number;
663
670
  lastMessageTimestamp?: number;
664
671
  }
665
672
  export interface AssistantMessage {
@@ -0,0 +1,17 @@
1
+ /** INI sections with `profile ` / `sso-session ` prefixes normalized. */
2
+ export type AwsIniFile = Record<string, Record<string, string>>;
3
+ export declare function parseAwsIni(text: string): AwsIniFile;
4
+ /** Resolve the selected shared-credentials profile. */
5
+ export declare function resolveAwsProfile(profile?: string): string;
6
+ /**
7
+ * Whether the shared config file participates in profile/region resolution.
8
+ * Explicit profile selection enables it; the implicit default profile follows
9
+ * the AWS SDK's `AWS_SDK_LOAD_CONFIG` opt-in.
10
+ */
11
+ export declare function shouldLoadAwsSharedConfig(profile?: string): boolean;
12
+ export declare function resolveAwsProfileRegion(profile?: string): string | undefined;
13
+ /** Region selected by the environment or active shared-config profile. */
14
+ export declare function resolveAwsAmbientRegion(profile?: string): string | undefined;
15
+ /** Resolve the region precedence shared by AWS transports and credential exchanges. */
16
+ export declare function resolveAwsRegion(explicitRegion?: string, profile?: string): string;
17
+ export declare function hasConfiguredAwsProfile(profile?: string): boolean;
@@ -8,6 +8,15 @@ import type { AssistantMessage, Model, ToolCall } from "../types.js";
8
8
  * the persisted transcript keeps the byte-for-byte original.
9
9
  */
10
10
  export declare function escapeHarmonyControlTokens(text: string): string;
11
+ /**
12
+ * Escape reserved Harmony control tokens inside a JSON document string (e.g.
13
+ * `function_call.arguments`). Doubles the backslash so the document remains
14
+ * valid JSON whose *decoded* strings carry the inert `<\|token\|>` spelling.
15
+ * `<|` cannot occur outside a string literal in valid JSON, so the blanket
16
+ * replace never corrupts structure; malformed documents are escaped
17
+ * best-effort.
18
+ */
19
+ export declare function escapeHarmonyControlTokensInJson(text: string): string;
11
20
  /**
12
21
  * Whether requests to `model` are served by a Harmony-dialect backend
13
22
  * (gpt-5.x / gpt-oss), which rejects reserved control-token spellings appearing
@@ -9,10 +9,16 @@
9
9
  * literal enums/consts, and descriptions survive.
10
10
  */
11
11
  export interface JsonSchemaToTsOptions {
12
- /** Indentation unit for nested object bodies. Default two spaces. */
12
+ /** Indentation unit for nested object bodies. Default two spaces (none in `harmony` style). */
13
13
  readonly indent?: string;
14
- /** Emit `description` keywords as JSDoc comments on object properties. Default true. */
14
+ /** Emit `description` keywords as comments on object properties. Default true. */
15
15
  readonly comments?: boolean;
16
+ /**
17
+ * Output flavor. `default` renders JSDoc comments, `;` delimiters, and
18
+ * indented bodies; `harmony` renders the flat OpenAI-Harmony convention —
19
+ * `//` line comments, `,` delimiters, no indentation.
20
+ */
21
+ readonly style?: "default" | "harmony";
16
22
  }
17
23
  /** Convert a JSON Schema object into a simplified TypeScript type string. */
18
24
  export declare function jsonSchemaToTypeScript(schema: unknown, options?: JsonSchemaToTsOptions): string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "17.2.4",
4
+ "version": "17.2.6",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -38,9 +38,9 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.1",
41
- "@oh-my-pi/pi-catalog": "17.2.4",
42
- "@oh-my-pi/pi-utils": "17.2.4",
43
- "@oh-my-pi/pi-wire": "17.2.4",
41
+ "@oh-my-pi/pi-catalog": "17.2.6",
42
+ "@oh-my-pi/pi-utils": "17.2.6",
43
+ "@oh-my-pi/pi-wire": "17.2.6",
44
44
  "arktype": "2.2.3",
45
45
  "zod": "^4"
46
46
  },
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import * as path from "node:path";
8
8
  import {
9
+ $envExact,
9
10
  getAgentDbPath,
10
11
  getAgentDir,
11
12
  getAuthBrokerSnapshotCachePath,
@@ -55,7 +56,7 @@ export function getAuthBrokerTokenFilePath(): string {
55
56
  */
56
57
  async function defaultResolveConfigValue(config: string): Promise<string | undefined> {
57
58
  if (config.startsWith("!")) return undefined;
58
- const envValue = process.env[config];
59
+ const envValue = $envExact(config);
59
60
  return envValue || config;
60
61
  }
61
62
 
@@ -12,7 +12,7 @@
12
12
  * exception (standard type keeps extra keys): it preserves provider-specific extension fields so
13
13
  * they round-trip through the broker instead of being dropped (see below).
14
14
  */
15
- import { type Type, type } from "arktype";
15
+ import { scope, type Type } from "arktype";
16
16
  import {
17
17
  type ApiKeyCredential,
18
18
  type AuthCredential,
@@ -84,6 +84,11 @@ export interface AuthBrokerWireSchemas {
84
84
  }
85
85
 
86
86
  function buildAuthBrokerWireSchemas(): AuthBrokerWireSchemas {
87
+ // Wire schemas validate only a handful of times per process, so ArkType's
88
+ // definition-time JIT codegen is startup tax. A local jitless scope skips
89
+ // that codegen and uses interpreted traversal; correctness is unchanged.
90
+ const { type } = scope({}, { jitless: true });
91
+
87
92
  // ─── Credential payloads ───────────────────────────────────────────────────
88
93
 
89
94
  /** Real OAuth credential (broker-side) — refresh token is the actual upstream value. */
@@ -12,7 +12,7 @@ import { createHash } from "node:crypto";
12
12
  import * as fs from "node:fs/promises";
13
13
  import * as path from "node:path";
14
14
  import { parseAlibabaTokenPlanCredential } from "@oh-my-pi/pi-catalog/wire/alibaba-token-plan";
15
- import { $env, getAgentDbPath, getDbBusyTimeoutMs, logger } from "@oh-my-pi/pi-utils";
15
+ import { $env, $envExact, getAgentDbPath, getDbBusyTimeoutMs, logger } from "@oh-my-pi/pi-utils";
16
16
  import type { ApiKeyResolver } from "./auth-retry";
17
17
  import * as AIError from "./error";
18
18
  import { isUsageLimitOutcome } from "./error/rate-limit";
@@ -643,7 +643,7 @@ export type AuthStorageOptions = {
643
643
  * Does NOT support "!command" syntax (that requires pi-natives).
644
644
  */
645
645
  async function defaultConfigValueResolver(config: string): Promise<string | undefined> {
646
- const envValue = process.env[config];
646
+ const envValue = $envExact(config);
647
647
  return envValue || config;
648
648
  }
649
649
 
@@ -1,25 +1,32 @@
1
- import type { ToolCall } from "../types";
2
- import { getDialectDefinition } from "./factory";
3
- import type { Dialect, InbandTool } from "./types";
1
+ import { pyCall } from "./rendering";
2
+ import type { InbandTool } from "./types";
4
3
 
5
4
  const INTENT_PLACEHOLDER = "…";
6
5
 
7
- export function renderToolExamples(tool: InbandTool, dialect: Dialect, intentField?: string): string {
6
+ /**
7
+ * Render a tool's examples as an `<examples>` block. Calls render in Python
8
+ * keyword-argument syntax (`name(key="value", n=1)`) regardless of the model's
9
+ * tool-call dialect, so example bytes stay identical across models. Multiline
10
+ * string args render as verbatim `"""…"""` blocks, and a call whose only
11
+ * argument is a string renders as the bare value — the block already names the
12
+ * tool, and payload args (commands, code, patches) read best verbatim.
13
+ */
14
+ export function renderToolExamples(tool: InbandTool, intentField?: string): string {
8
15
  const examples = tool.examples;
9
16
  if (!examples?.length) return "";
10
- const definition = getDialectDefinition(dialect);
11
17
  const renderCall = (args: Record<string, unknown>): string => {
18
+ const bare = bareStringArg(args);
19
+ if (bare !== undefined) {
20
+ // Bare payload. The intent placeholder still rides on the envelope so
21
+ // intent-traced schemas (where `i` is required) keep teaching it.
22
+ const intentAttr = intentField ? ` ${intentField}="${INTENT_PLACEHOLDER}"` : "";
23
+ return `<example${intentAttr}>\n${bare}\n</example>`;
24
+ }
12
25
  // When intent tracing injects `i` into the schema, examples must show a
13
26
  // placeholder so the model learns to emit it. Keep it first, matching the
14
27
  // schema injection order.
15
28
  const finalArgs = intentField ? { [intentField]: INTENT_PLACEHOLDER, ...args } : args;
16
- const call: ToolCall = {
17
- type: "toolCall",
18
- id: "example",
19
- name: tool.name,
20
- arguments: finalArgs,
21
- };
22
- return `<example>\n${definition.renderToolCall(call, { tools: [tool], example: true }).trim()}\n</example>`;
29
+ return `<example>\n${pyCall(tool.name, finalArgs)}\n</example>`;
23
30
  };
24
31
  const parts = examples.map(ex => {
25
32
  const head = ex.caption ? `# ${ex.caption}\n` : "";
@@ -31,3 +38,34 @@ export function renderToolExamples(tool: InbandTool, dialect: Dialect, intentFie
31
38
  });
32
39
  return `<examples>\n${parts.join("\n")}\n</examples>`;
33
40
  }
41
+
42
+ /**
43
+ * Render a tool's examples as JSDoc-style `@example` lines for comment-gutter
44
+ * contexts (the Harmony `namespace functions` inventory): `@example "caption"`
45
+ * followed by the call in the same Python kwargs syntax as the wire block. The
46
+ * tag line delimits each example, so no XML envelope is needed — which is why
47
+ * the inventory uses this instead of `//`-prefixing the `<examples>` block.
48
+ */
49
+ export function renderToolExamplesJsdoc(tool: InbandTool): string {
50
+ const examples = tool.examples;
51
+ if (!examples?.length) return "";
52
+ const renderCall = (args: Record<string, unknown>): string => bareStringArg(args) ?? pyCall(tool.name, args);
53
+ const parts = examples.map(ex => {
54
+ const head = ex.caption ? `@example ${JSON.stringify(ex.caption)}` : "@example";
55
+ if ("call" in ex) return `${head}\n${renderCall(ex.call)}`;
56
+ if ("good" in ex) return `${head}\nWRONG:\n${renderCall(ex.bad)}\nRIGHT:\n${renderCall(ex.good)}`;
57
+ return ex.note ? `${head}\n${ex.note}` : head;
58
+ });
59
+ return parts.join("\n");
60
+ }
61
+
62
+ /** Sole-argument string payload, if the call has exactly one string argument. */
63
+ function bareStringArg(args: Record<string, unknown>): string | undefined {
64
+ let sole: unknown;
65
+ let count = 0;
66
+ for (const key in args) {
67
+ count++;
68
+ sole = args[key];
69
+ }
70
+ return count === 1 && typeof sole === "string" ? sole : undefined;
71
+ }