pi-sap-aicore

内容来源:README.md(说明文档) · 原始地址 · 查看安装指南

原始内容

pi-sap-aicore

A custom provider extension for the pi coding agent that routes inference through SAP AI Core — via the orchestration service (every model from a single deployment) and/or direct foundation deployments (per-model foundation endpoints such as Azure OpenAI or AWS Bedrock). Both register at once and share one login, so you pick the route per model. See Orchestration vs. Foundation.

Prerequisites

  • pi 0.81.0 or newer installed (npm install -g @earendil-works/pi-coding-agent)
  • Node.js 22.19.0 or newer
  • An SAP BTP account with AI Core entitlement and an orchestration deployment
  • (optional, for the foundation provider) one or more foundation-models deployments — one per model you want to route directly (azure-openai for GPT/OpenAI models, aws-bedrock for Anthropic/Claude models)
  • The service key JSON for your AI Core service binding

[!IMPORTANT] pi-sap-aicore 0.4.0 and newer require pi 0.81.0 or newer because they use pi's complete native Provider API. If you must remain on pi 0.80.x, pin the previous compatible extension release until you can upgrade:

pi install npm:pi-sap-aicore@0.3.8

Credentials

The extension looks for the SAP BTP service-key JSON in this order:

  1. Pi's auth store~/.pi/agent/auth.json, populated by /login (see below). Persisted across sessions, file-permission-locked by pi.
  2. AICORE_SERVICE_KEY environment variable — per-shell override. Useful for testing against a different tenant for one session without re-running /login.

If neither is present, inference fails with a clear "no service key configured" error.

Both providers — sap-aicore (orchestration) and sap-aicore-foundation — use the same service key, so a single /login (or one AICORE_SERVICE_KEY) covers both. pi keys stored credentials per provider, so the foundation provider reads the shared login from pi's auth store directly; you never log in twice.

Recommended: /login

From inside pi:

/login

Then:

  1. Pick Sign in with an API key.
  2. Pick SAP AI Core.
  3. Paste your BTP service-key JSON as a single line and hit enter. It's validated immediately — if anything is missing or malformed, you'll get a specific error pointing at the field, so you can re-run /login and fix it.

Pi 0.81 stores the JSON as a native provider credential in ~/.pi/agent/auth.json. The value is passed to the provider verbatim, so literal $ characters in clientsecret are preserved.

To get the JSON: BTP cockpit → your AI Core service instance → Service Keys → View. Copy the entire JSON object.

Upgrading from 0.3.x? Existing credentials created through Use a subscription → SAP AI Core continue to work without re-login. The Pi 0.81 provider retains that legacy credential handler solely for migration; new logins should use the native API-key flow above. The foundation provider still borrows the primary sap-aicore credential and does not add a second subscription entry. Pi may list SAP AI Core (Foundation) in the API-key provider picker because every complete Provider declares auth; choose the primary SAP AI Core entry when you want one credential shared by both.

Alternative: AICORE_SERVICE_KEY env var

export AICORE_SERVICE_KEY='{"clientid":"...","clientsecret":"...","url":"https://...authentication.sap.hana.ondemand.com","serviceurls":{"AI_API_URL":"https://api.ai.<region>.ml.hana.ondemand.com"}}'

The @sap-ai-sdk/orchestration SDK reads this directly for XSUAA auth, token caching, and deployment resolution — no manual token plumbing needed.

Install

From npm (recommended)

pi install npm:pi-sap-aicore

pi downloads the package under ~/.pi/agent/npm/, runs npm install to pull the SAP AI SDKs, and auto-loads the extension on every startup. Run the one command on each machine; pi update keeps it current. Pin a version with pi install npm:pi-sap-aicore@<version> (pinned specs are skipped by pi update).

Then configure credentials with /login (see Credentials) and confirm the models are visible:

pi --list-models | grep sap-aicore

Local development (this repo)

npm install
pi -e ./index.ts --list-models

You'll see the orchestration models under sap-aicore/ (Claude, GPT-5*, Gemini), plus any direct foundation models under sap-aicore-foundation/:

  • sap-aicore/anthropic--claude-4.7-opus — Claude Opus 4.7 (orchestration)
  • sap-aicore/gpt-5.5 — GPT-5.5 via orchestration
  • sap-aicore-foundation/gpt-5.5 — GPT-5.5 via its direct Azure OpenAI foundation deployment
  • sap-aicore-foundation/anthropic--claude-4.8-opus — Claude Opus 4.8 via its direct AWS Bedrock foundation deployment

Run pi -e ./index.ts to launch pi with the local extension loaded; this overrides any globally-installed version for the session, which is the fastest iteration loop while developing.

Alternative: install from git

For an unpublished fork or a branch you want to track directly:

pi install git:github.com/ttiimmaahh/pi-sap-aicore@main

pi clones to ~/.pi/agent/git/…, runs npm install, and auto-loads on startup. Note: an @main git install is not moved to newer commits by pi update (it only reconciles to the pinned ref) — prefer the npm install above for hands-off updates.

Orchestration vs. Foundation

The extension registers two providers, both backed by the same service key:

sap-aicore (orchestration) sap-aicore-foundation (direct)
SAP deployment one orchestration deployment fronts every model one foundation deployment per model
Models Claude, GPT-5*, Gemini GPT/OpenAI (azure-openai), Anthropic/Claude (aws-bedrock), and Gemini (gcp-vertexai)
Tool use yes yes — tools are translated to OpenAI, Bedrock Converse, or Vertex/Gemini function declarations by executable
Streaming subject to orchestration's per-model allow-list — new models can 400 Streaming is not supported (we fall back to non-streaming) Azure OpenAI streams natively; AWS Bedrock and Vertex AI currently use non-streaming endpoints and replay responses into pi stream events
Reasoning effort tunable (reasoning_effort / thinking) model default only for Azure; Bedrock/Anthropic and Vertex/Gemini effort controls are intentionally conservative
Content filter / grounding / templating yes no — raw model access
SDK / endpoint @sap-ai-sdk/orchestration AzureOpenAiChatClient for azure-openai; SAP /inference/deployments/{id}/converse for aws-bedrock; SAP /inference/deployments/{id}/models/{model}:generateContent for gcp-vertexai

Both routes appear in the model list simultaneously, so you choose per model. The foundation route exists mainly to access new models directly when orchestration lags behind model deployment or streaming support (for example gpt-5.5 on Azure OpenAI or a newly deployed Claude model on AWS Bedrock).

Adding a foundation model: it needs its own foundation-models deployment in SAP AI Core — one per (model, version, resource group). The extension chooses the foundation executable from the model id: gpt-*azure-openai, anthropic--*aws-bedrock, and gemini-*gcp-vertexai. Then add its id to the per-machine extension overlay at ~/.pi/agent/pi-sap-aicore/models.json:

{
  "foundation": { "enabledModelIds": ["gpt-5.5", "anthropic--claude-4.8-opus"] }
}

Definitions are reused from the shared catalog, so an id only has to be present there. An id with no matching deployment 404s at call time. Run /sap-models discover in pi (or node scripts/list-sap-models.mjs from this repo) to see what your tenant actually deploys.

Live foundation validation

To validate normal coding-agent behavior across the three direct foundation executables, run the live smoke-test matrix from this repo:

npm run validate:foundation

The script uses pi --no-extensions -e ./index.ts so it tests the local checkout, not a globally installed package. It makes real SAP model calls and validates:

  • text generation
  • real tool execution via side-effect files
  • image input smoke tests

Default models are gpt-5.5, anthropic--claude-4.8-opus, and gemini-3.5-flash. Override them with GPT_MODEL, BEDROCK_MODEL, and VERTEX_MODEL; set SKIP_IMAGE=1 to skip vision tests.

Models

The model list is composed of three sources, merged at startup:

  1. src/models-snapshot.json — packaged fallback catalog, auto-generated from models.dev's SAP AI Core catalog. Maintainers refresh it with:

    npm run update-models
    

    This re-fetches the live catalog, applies our family-specific filters (currently anthropic claude-4.x, gpt-5*, gemini-2.5*), and writes the snapshot to disk. Commit the result.

  2. ~/.pi/agent/pi-sap-aicore/models-cache.json — per-machine public catalog cache. Pi 0.81 refreshes authenticated providers in the background when /model opens and through pi update --models; checks are throttled for four hours. Force an immediate refresh inside pi with:

    /sap-models update
    

    This does not edit the installed npm package and is safe across extension updates. The existing Provider objects publish the refreshed model list immediately, without re-registration or /reload. A cache older than the bundled snapshot is ignored after extension upgrades.

  3. ~/.pi/agent/pi-sap-aicore/models.json — per-machine tenant overlay. Use it for models in your tenant that are not in the public catalog yet, model overrides, exclusions, and foundation-route enablement. Overlay models win over cache/snapshot on duplicate id.

Example overlay:

{
  "models": [
    {
      "id": "some-preview-model",
      "name": "Some Preview Model",
      "reasoning": true,
      "tool_call": true,
      "temperature": true,
      "modalities": { "input": ["text"], "output": ["text"] },
      "limit": { "context": 200000, "output": 32000 },
      "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
      "thinkingLevelMap": {
        "minimal": "low",
        "low": "low",
        "medium": "medium",
        "high": "high",
        "xhigh": "high"
      }
    }
  ],
  "overrides": {
    "gemini-2.5-pro": { "reasoning": false }
  },
  "exclude": ["gpt-5.5"],
  "foundation": {
    "enabledModelIds": ["some-preview-model"]
  }
}

Use /sap-models paths to print the exact cache and overlay paths, and /sap-models discover to compare the loaded catalog against the models your SAP tenant reports.

/sap-models commands

Run these inside pi after installing/loading the extension:

Command What it does
/sap-models update Forces the latest public SAP AI Core model metadata from models.dev into ~/.pi/agent/pi-sap-aicore/models-cache.json and updates both live Provider views.
/sap-models discover Uses your configured SAP service key to query the tenant's foundation-models scenario, then reports models that are missing from the local catalog and catalog entries absent from the tenant. Honors AICORE_RESOURCE_GROUP / service-key resourceGroup.
/sap-models list Shows how many orchestration models and foundation-enabled models are currently loaded after snapshot/cache/overlay merging.
/sap-models paths Prints the cache and overlay file paths for this machine.
/sap-models help Shows the command summary in pi.

A typical refresh workflow is:

/sap-models update
/sap-models discover
/model

If discover reports a tenant model that is missing from the catalog, add it to ~/.pi/agent/pi-sap-aicore/models.json under models. If it reports a catalog model that is absent from your tenant and selection causes SAP 400s, add the id to exclude.

Overlay reference

~/.pi/agent/pi-sap-aicore/models.json supports these top-level fields:

Field Type Purpose
models SapModel[] Adds tenant-only/pre-release models or replaces catalog models with the same id.
overrides object keyed by model id Partially overrides an existing model. Nested limit, cost, modalities, and thinkingLevelMap fields are merged. Unknown ids are ignored.
exclude string[] Removes model ids after snapshot/cache/overlay merging. Useful for public catalog entries your SAP tenant does not deploy.
foundation.enabledModelIds string[] Also exposes matching model ids through sap-aicore-foundation/*. Each id must exist in the merged catalog and have a foundation deployment in the selected resource group.

Minimal tenant-only model:

{
  "models": [
    {
      "id": "gpt-5.4-nano",
      "name": "GPT-5.4 Nano",
      "reasoning": true,
      "tool_call": true,
      "temperature": true,
      "modalities": { "input": ["text", "image"], "output": ["text"] },
      "limit": { "context": 1050000, "output": 128000 },
      "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
      "thinkingLevelMap": {
        "minimal": "low",
        "low": "low",
        "medium": "medium",
        "high": "high",
        "xhigh": "high"
      }
    }
  ]
}

Minimal foundation enablement for a model already in the catalog:

{
  "foundation": { "enabledModelIds": ["gpt-5.5"] }
}

The cost fields are vendor list prices (USD per million tokens) from models.dev. Used only for pi's in-UI cost display — your actual SAP BTP invoice is contract-based and will differ.

Thinking levels

Models with reasoning: true honor pi's thinking-level cycle (default keybind Shift+Tab): off, minimal, low, medium, high, xhigh.

  • Anthropic 4.6+ models (anthropic--claude-4.6-*, 4.7-*) use adaptive thinking — thinking: {type: "adaptive"} + output_config: {effort}. The model decides the budget; the level only nudges depth.
  • Older Anthropic models (anthropic--claude-4-*, 4.5-*) use budget-token thinking — thinking: {type: "enabled", budget_tokens: N}. Each pi level maps to a token count (1k / 4k / 8k / 16k / 32k for minimal/low/medium/high/xhigh), clamped down so max_tokens always has at least 1024 tokens of headroom for the response. SAP rejects the adaptive shape on these models ("adaptive thinking is not supported on this model"), which is why we split.

Note on reasoning visibility: SAP orchestration does NOT pass structured reasoning/thinking content through to streaming clients. The model genuinely reasons (you'll see step-by-step structure leak into the visible answer text, and the tokens are billed via completion_tokens_details.reasoning_tokens), but pi's dedicated "thinking" panel will stay empty for SAP-routed models — there's no client-side fix. If SAP exposes a server-side flag for this in the future, our pickReasoning probe is wired and ready in stream.ts.

  • OpenAI models (gpt-*) use reasoning_effort: "minimal" | "low" | "medium" | "high". xhigh is omitted — OpenAI has no equivalent tier; pi will skip it when cycling.
  • Gemini models (gemini-2.5-*) ship with reasoning: false — SAP's gemini reasoning passthrough is undocumented, so we keep Shift+Tab off the cycle for these models rather than send a request shape SAP may reject. Wire-up (likely thinking_config.thinking_budget) is a future TODO in src/stream.ts:reasoningParams.

Foundation route caveat: on sap-aicore-foundation/*, GPT/OpenAI models use the direct Azure OpenAI SDK pinned to API version 2024-10-21, which has no reasoning_effort field — so gpt-5* reason at their default effort and pi's thinking-level cycle is a no-op there. Anthropic/Claude models use SAP's AWS Bedrock /converse endpoint; Gemini models use SAP's Vertex AI generateContent endpoint with thinkingBudget: 0 by default so small pi output budgets produce visible text instead of only hidden thoughts. Tool/function calling is supported on all three direct foundation executables. Use the orchestration route if you need explicit effort control.

To override budgets per model, edit thinkingLevelMap on the relevant entry in ~/.pi/agent/pi-sap-aicore/models.json.

AI Resource Group

Resolved in this order:

  1. AICORE_RESOURCE_GROUP env var — per-shell override. Example:

    export AICORE_RESOURCE_GROUP=my-team-rg
    
  2. resourceGroup field on the service-key JSON — convenient for teams who manage multiple groups and want to bake the default into the key. Non-standard, so add it yourself before pasting into pi:

    { "clientid": "...", "clientsecret": "...", "resourceGroup": "my-team-rg", ... }
    
  3. SAP's server-side default (default) — if neither of the above is set.

The value is passed via SAP's OrchestrationClient(..., {resourceGroup}) constructor arg, which is the only supported channel — AI-Resource-Group as a request header is explicitly rejected by SAP's orchestration typings. The foundation provider applies the same resolved group when resolving and invoking direct deployments; both a model's foundation deployment and the orchestration deployment must live in the resolved group for name-based resolution to find them.

Prompt caching & cost reporting

Cache read/write tokens always report 0 on SAP-routed turns. SAP orchestration strips all detail fields from the TokenUsage response — we only get prompt_tokens, completion_tokens, and total_tokens across every route. There's no prompt_tokens_details.cached_tokens (OpenAI) and no top-level cache_read_input_tokens (Anthropic) for the client to read.

Whether the backend actually caches is invisible to pi. SAP's contract billing may give you a discount on cached tokens that this extension can't surface — check your BTP invoice if cache savings matter.

Experimental: PI_SAP_AICORE_CACHE_CONTROL=1 tags the system prompt and last user message with Anthropic's cache_control: {type:"ephemeral"}. SAP may forward it (saving SAP money on the backend, possibly passed through via your contract) or may 400 the request. Either way, you won't see cacheRead become non-zero in pi's diagnostics — that requires SAP to expose detail fields, which they currently don't.

OpenAI/Gemini routes ignore the flag — they have their own automatic caching with no breakpoint API.

Foundation route: because direct foundation endpoints bypass orchestration's usage-stripping, provider-specific cache fields may come back populated. mapUsage reads OpenAI prompt_tokens_details.cached_tokens and Anthropic-style cache-read fields when SAP exposes them, so cacheRead could be non-zero on sap-aicore-foundation/* turns where orchestration always reports 0. Treat as best-effort and provider-dependent.

Releasing (maintainers)

Releases are tag-driven and published to npm by GitHub Actions. There is no build step — pi loads the .ts sources directly via jiti — so a release is just verify + publish.

  1. Update CHANGELOG.md: move items from [Unreleased] into a new version heading.

  2. Bump the version (this commits package.json and creates a vX.Y.Z tag):

    npm version patch   # or minor / major
    git push --follow-tags
    
  3. The Publish workflow fires on the v* tag, asserts the tag matches package.json, typechecks, publishes to npm, and creates/updates the matching GitHub Release from that version's CHANGELOG.md notes.

Every push to main and every PR also runs the CI typecheck gate.

One-time setup: npm Trusted Publishing (OIDC)

Publishing is tokenless — no NPM_TOKEN secret. Authorize this repo once on npmjs.com:

  1. npmjs.com → the pi-sap-aicore package → SettingsTrusted Publisher.
  2. Choose GitHub Actions and enter (case-sensitive, exact match):
    • Organization or user: ttiimmaahh
    • Repository: pi-sap-aicore
    • Workflow filename: publish.yml
    • Allowed actions: npm publish
  3. Save. The next v* tag publishes automatically, with provenance attestations.

The first CI release must be a version newer than the last manually published one (0.1.0) — npm rejects republishing an existing version.

Repo layout

.
├── package.json              # pi-package manifest + deps + scripts
├── tsconfig.json             # editor support; pi runs the .ts directly
├── CHANGELOG.md              # Keep a Changelog; updated per release
├── LICENSE                   # MIT
├── .github/workflows/
│   ├── ci.yml                # typecheck gate on push to main + PRs
│   └── publish.yml           # tag-driven npm publish via OIDC trusted publishing
├── index.ts                  # ExtensionAPI factory + complete Provider registration
├── scripts/
│   ├── update-models.mjs                    # maintainer script: fetches models.dev, writes models-snapshot.json
│   ├── list-sap-models.mjs                  # lists models your tenant actually deploys (diff vs snapshot)
│   ├── diagnose-streaming.mjs               # probes orchestration streaming support per model
│   ├── test-native-providers.mjs            # offline Provider/auth compatibility checks
│   ├── test-model-refresh.mjs               # offline refresh/cache/failure checks
│   └── validate-foundation-executables.mjs  # live text/tool/image smoke tests for direct foundation executables
└── src/
    ├── auth.ts                  # native API-key auth + legacy credential compatibility
    ├── model-catalog.ts         # loads snapshot/cache/overlay and adapts models.dev metadata
    ├── model-catalog-controller.ts # shared synchronous state + refresh lifecycle
    ├── models-snapshot.json     # auto-generated from models.dev (committed)
    ├── providers.ts             # complete Pi 0.81 Provider objects
    ├── sap-model-commands.ts    # /sap-models update/discover/list/paths
    ├── to-pi-model.ts           # SapModel → pi-ai Model mapper
    ├── stream.ts                # orchestration stream adapter + shared usage/error helpers
    ├── translate.ts             # pi Context ↔ orchestration message shape
    ├── foundation-executables.ts         # model id → SAP foundation executable mapping
    ├── foundation-deployment.ts          # shared foundation deployment resolution helpers
    ├── foundation-params.ts              # Azure OpenAI request params (max_completion_tokens, temperature gating)
    ├── stream-foundation.ts              # foundation dispatcher
    ├── stream-foundation-azure-openai.ts # AzureOpenAiChatClient adapter with native streaming
    ├── stream-foundation-bedrock.ts      # AWS Bedrock /converse adapter for Anthropic foundation deployments
    ├── stream-foundation-vertexai.ts     # Vertex AI generateContent adapter for Gemini foundation deployments
    ├── translate-foundation.ts           # pi Context ↔ Azure OpenAI message shape
    ├── translate-foundation-bedrock.ts   # pi Context ↔ Bedrock Converse message shape
    └── translate-foundation-vertexai.ts  # pi Context ↔ Vertex AI generateContent message shape