pi-sap-aicore 0.3.6 → 0.3.7
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.
- package/CHANGELOG.md +27 -1
- package/package.json +7 -7
- package/scripts/test-auth-storage.mjs +95 -0
- package/src/sap-model-commands.ts +2 -26
- package/src/stream.ts +17 -22
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.7] - 2026-07-16
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Restored shared `/login` credentials for `sap-aicore-foundation` and
|
|
15
|
+
`/sap-models discover` on Pi 0.80.9+. Pi replaced the old synchronous
|
|
16
|
+
`AuthStorage.list()` / `get()` API; the extension now uses the public
|
|
17
|
+
`readStoredCredential()` helper and reads only the `sap-aicore` credential.
|
|
18
|
+
Previously the compatibility error was swallowed and every model request
|
|
19
|
+
incorrectly reported that no service key was configured.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Added `scripts/test-auth-storage.mjs`, an isolated regression test that checks
|
|
24
|
+
shared credential lookup, literal `$` preservation, provider isolation, and
|
|
25
|
+
credential-type validation.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Raised the Pi peer and development dependency floor to 0.80.9, where the
|
|
30
|
+
supported synchronous stored-credential helper is available.
|
|
31
|
+
|
|
10
32
|
## [0.3.6] - 2026-07-04
|
|
11
33
|
|
|
12
34
|
### Fixed
|
|
@@ -221,7 +243,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
221
243
|
`reasoning_effort` for OpenAI).
|
|
222
244
|
- MIT license and npm packaging.
|
|
223
245
|
|
|
224
|
-
[Unreleased]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.3.
|
|
246
|
+
[Unreleased]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.3.7...HEAD
|
|
247
|
+
[0.3.7]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.3.6...v0.3.7
|
|
248
|
+
[0.3.6]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.3.5...v0.3.6
|
|
249
|
+
[0.3.5]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.3.4...v0.3.5
|
|
250
|
+
[0.3.4]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.3.3...v0.3.4
|
|
225
251
|
[0.3.3]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.3.2...v0.3.3
|
|
226
252
|
[0.3.2]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.3.1...v0.3.2
|
|
227
253
|
[0.3.1]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.3.0...v0.3.1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-sap-aicore",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "SAP AI Core (orchestration + foundation) provider for the pi coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Tim Pearson (https://github.com/ttiimmaahh)",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"update-models": "node scripts/update-models.mjs",
|
|
34
|
-
"test": "node scripts/test-vertex-tool-schema.mjs && node scripts/test-bedrock-tool-results.mjs && node scripts/test-empty-content-blocks.mjs",
|
|
34
|
+
"test": "node scripts/test-auth-storage.mjs && node scripts/test-vertex-tool-schema.mjs && node scripts/test-bedrock-tool-results.mjs && node scripts/test-empty-content-blocks.mjs",
|
|
35
35
|
"validate:foundation": "node scripts/validate-foundation-executables.mjs",
|
|
36
36
|
"prepublishOnly": "tsc --noEmit && npm test"
|
|
37
37
|
},
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"@sap-ai-sdk/orchestration": "^2.12.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@earendil-works/pi-ai": "
|
|
45
|
-
"@earendil-works/pi-coding-agent": "
|
|
44
|
+
"@earendil-works/pi-ai": ">=0.80.9",
|
|
45
|
+
"@earendil-works/pi-coding-agent": ">=0.80.9"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@earendil-works/pi-ai": "^0.80.
|
|
49
|
-
"@earendil-works/pi-coding-agent": "^0.80.
|
|
50
|
-
"typescript": "^
|
|
48
|
+
"@earendil-works/pi-ai": "^0.80.9",
|
|
49
|
+
"@earendil-works/pi-coding-agent": "^0.80.9",
|
|
50
|
+
"typescript": "^7.0.2"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
53
|
"node": ">=20"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Offline regression test for sharing the orchestration provider's stored
|
|
3
|
+
// subscription credential with the foundation provider. This makes no network
|
|
4
|
+
// calls and uses an isolated temporary auth file.
|
|
5
|
+
|
|
6
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { pathToFileURL } from "node:url";
|
|
10
|
+
|
|
11
|
+
const ROOT = new URL("..", import.meta.url).pathname;
|
|
12
|
+
const { readSharedServiceKeyFromStore } = await import(
|
|
13
|
+
pathToFileURL(join(ROOT, "src/stream.ts")).href
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
let failures = 0;
|
|
17
|
+
function check(condition, message) {
|
|
18
|
+
if (condition) {
|
|
19
|
+
console.log(` ✓ ${message}`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
console.error(` ❌ ${message}`);
|
|
23
|
+
failures++;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const serviceKey = JSON.stringify({
|
|
27
|
+
clientid: "client-id",
|
|
28
|
+
clientsecret: "literal$secret",
|
|
29
|
+
url: "https://auth.example.test",
|
|
30
|
+
serviceurls: { AI_API_URL: "https://api.example.test" },
|
|
31
|
+
});
|
|
32
|
+
const tempDir = mkdtempSync(join(tmpdir(), "pi-sap-aicore-auth-"));
|
|
33
|
+
const authPath = join(tempDir, "auth.json");
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
console.log("Shared OAuth credential lookup");
|
|
37
|
+
writeFileSync(
|
|
38
|
+
authPath,
|
|
39
|
+
JSON.stringify({
|
|
40
|
+
"sap-aicore": {
|
|
41
|
+
type: "oauth",
|
|
42
|
+
serviceKey,
|
|
43
|
+
access: "",
|
|
44
|
+
refresh: "",
|
|
45
|
+
expires: Number.MAX_SAFE_INTEGER,
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
check(
|
|
50
|
+
readSharedServiceKeyFromStore(authPath) === serviceKey,
|
|
51
|
+
"reads the sap-aicore OAuth credential verbatim",
|
|
52
|
+
);
|
|
53
|
+
check(
|
|
54
|
+
readSharedServiceKeyFromStore(authPath)?.includes("literal$secret"),
|
|
55
|
+
"preserves literal dollar signs",
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
console.log("Provider isolation");
|
|
59
|
+
writeFileSync(
|
|
60
|
+
authPath,
|
|
61
|
+
JSON.stringify({
|
|
62
|
+
"another-provider": {
|
|
63
|
+
type: "oauth",
|
|
64
|
+
serviceKey,
|
|
65
|
+
access: "",
|
|
66
|
+
refresh: "",
|
|
67
|
+
expires: Number.MAX_SAFE_INTEGER,
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
check(
|
|
72
|
+
readSharedServiceKeyFromStore(authPath) === undefined,
|
|
73
|
+
"does not borrow another provider's credential",
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
console.log("Credential type validation");
|
|
77
|
+
writeFileSync(
|
|
78
|
+
authPath,
|
|
79
|
+
JSON.stringify({
|
|
80
|
+
"sap-aicore": { type: "api_key", key: serviceKey },
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
check(
|
|
84
|
+
readSharedServiceKeyFromStore(authPath) === undefined,
|
|
85
|
+
"ignores non-OAuth credentials",
|
|
86
|
+
);
|
|
87
|
+
} finally {
|
|
88
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (failures > 0) {
|
|
92
|
+
console.error(`\n${failures} check(s) failed`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
console.log("\nAll checks passed");
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AuthStorage,
|
|
3
|
-
type ExtensionAPI,
|
|
4
|
-
} from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
2
|
import { ScenarioApi } from "@sap-ai-sdk/ai-api";
|
|
6
3
|
|
|
7
4
|
import { parseAndValidateServiceKey } from "./auth.ts";
|
|
@@ -14,31 +11,10 @@ import {
|
|
|
14
11
|
} from "./model-catalog.ts";
|
|
15
12
|
import { ensureServiceKey, resolveResourceGroup } from "./stream.ts";
|
|
16
13
|
|
|
17
|
-
function sharedServiceKeyFromAuthStore(): string | undefined {
|
|
18
|
-
try {
|
|
19
|
-
const store = AuthStorage.create();
|
|
20
|
-
for (const provider of store.list()) {
|
|
21
|
-
const cred = store.get(provider);
|
|
22
|
-
if (cred?.type !== "oauth") continue;
|
|
23
|
-
const serviceKey = (cred as { serviceKey?: unknown }).serviceKey;
|
|
24
|
-
if (
|
|
25
|
-
typeof serviceKey === "string" &&
|
|
26
|
-
serviceKey.trimStart().startsWith("{")
|
|
27
|
-
) {
|
|
28
|
-
return serviceKey;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
} catch {
|
|
32
|
-
// Let callers produce the actionable no-key message.
|
|
33
|
-
}
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
14
|
function resolveCommandServiceKey(): ReturnType<
|
|
38
15
|
typeof parseAndValidateServiceKey
|
|
39
16
|
> {
|
|
40
|
-
|
|
41
|
-
return ensureServiceKey(raw);
|
|
17
|
+
return ensureServiceKey(undefined);
|
|
42
18
|
}
|
|
43
19
|
|
|
44
20
|
function formatModelList(ids: string[], max = 30): string {
|
package/src/stream.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
type SimpleStreamOptions,
|
|
15
15
|
type Usage,
|
|
16
16
|
} from "@earendil-works/pi-ai";
|
|
17
|
-
import {
|
|
17
|
+
import { readStoredCredential } from "@earendil-works/pi-coding-agent";
|
|
18
18
|
import type { ChatModel, LlmModelParams } from "@sap-ai-sdk/orchestration";
|
|
19
19
|
import type { TokenUsage } from "@sap-ai-sdk/orchestration/internal.js";
|
|
20
20
|
|
|
@@ -575,27 +575,22 @@ export function latchFinishReason(
|
|
|
575
575
|
|
|
576
576
|
let lastValidatedKey: ValidatedKey | undefined;
|
|
577
577
|
|
|
578
|
-
// pi stores
|
|
579
|
-
//
|
|
580
|
-
//
|
|
581
|
-
//
|
|
582
|
-
//
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
} catch {
|
|
595
|
-
// Auth store unreadable (missing/locked/format change) — fall through to
|
|
596
|
-
// the actionable "no key configured" error below.
|
|
597
|
-
}
|
|
598
|
-
return undefined;
|
|
578
|
+
// pi stores OAuth credentials by provider id, not by the OAuth display name.
|
|
579
|
+
// The foundation provider therefore reads the orchestration provider's stored
|
|
580
|
+
// login explicitly. `readStoredCredential` is the synchronous, public helper
|
|
581
|
+
// for stream setup; AuthStorage's old synchronous list/get API was removed in
|
|
582
|
+
// pi 0.80.9.
|
|
583
|
+
export function readSharedServiceKeyFromStore(
|
|
584
|
+
authPath?: string,
|
|
585
|
+
): string | undefined {
|
|
586
|
+
const credential = readStoredCredential("sap-aicore", authPath);
|
|
587
|
+
if (credential?.type !== "oauth") return undefined;
|
|
588
|
+
|
|
589
|
+
const serviceKey = (credential as { serviceKey?: unknown }).serviceKey;
|
|
590
|
+
return typeof serviceKey === "string" &&
|
|
591
|
+
serviceKey.trimStart().startsWith("{")
|
|
592
|
+
? serviceKey
|
|
593
|
+
: undefined;
|
|
599
594
|
}
|
|
600
595
|
|
|
601
596
|
export function ensureServiceKey(apiKey: string | undefined): ValidatedKey {
|