pi-sap-aicore 0.3.1 → 0.3.2

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 CHANGED
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.2] - 2026-07-01
11
+
12
+ ### Fixed
13
+
14
+ - Direct AWS Bedrock and GCP Vertex AI foundation routes now advertise pi tools
15
+ using provider-native tool/function declaration schemas, so Claude and Gemini
16
+ foundation models can perform real coding-agent tool calls instead of acting as
17
+ text-only generators.
18
+ - Vertex/Gemini foundation tool-call replay now preserves Gemini thought signatures
19
+ and normalizes provider-prefixed function names before emitting pi tool calls.
20
+
10
21
  ## [0.3.1] - 2026-07-01
11
22
 
12
23
  ### Added
@@ -135,7 +146,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
135
146
  `reasoning_effort` for OpenAI).
136
147
  - MIT license and npm packaging.
137
148
 
138
- [Unreleased]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.3.1...HEAD
149
+ [Unreleased]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.3.2...HEAD
150
+ [0.3.2]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.3.1...v0.3.2
139
151
  [0.3.1]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.3.0...v0.3.1
140
152
  [0.3.0]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.2.2...v0.3.0
141
153
  [0.2.2]: https://github.com/ttiimmaahh/pi-sap-aicore/compare/v0.2.1...v0.2.2
package/README.md CHANGED
@@ -132,8 +132,9 @@ The extension registers **two providers**, both backed by the same service key:
132
132
  |---|---|---|
133
133
  | SAP deployment | one orchestration deployment fronts **every** model | one foundation deployment **per model** |
134
134
  | Models | Claude, GPT-5*, Gemini | GPT/OpenAI (`azure-openai`), Anthropic/Claude (`aws-bedrock`), and Gemini (`gcp-vertexai`) |
135
+ | Tool use | yes | yes — tools are translated to OpenAI, Bedrock Converse, or Vertex/Gemini function declarations by executable |
135
136
  | 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 |
136
- | Reasoning effort | tunable (`reasoning_effort` / `thinking`) | model **default** only for Azure; Bedrock/Anthropic and Vertex/Gemini thinking controls are not fully wired yet |
137
+ | Reasoning effort | tunable (`reasoning_effort` / `thinking`) | model **default** only for Azure; Bedrock/Anthropic and Vertex/Gemini effort controls are intentionally conservative |
137
138
  | Content filter / grounding / templating | yes | no — raw model access |
138
139
  | 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` |
139
140
 
@@ -337,7 +338,8 @@ the direct Azure OpenAI SDK pinned to API version `2024-10-21`, which has no
337
338
  thinking-level cycle is a no-op there. Anthropic/Claude models use SAP's AWS
338
339
  Bedrock `/converse` endpoint; Gemini models use SAP's Vertex AI `generateContent`
339
340
  endpoint with `thinkingBudget: 0` by default so small pi output budgets produce
340
- visible text instead of only hidden thoughts. Use the orchestration route if you
341
+ visible text instead of only hidden thoughts. Tool/function calling is supported
342
+ on all three direct foundation executables. Use the orchestration route if you
341
343
  need explicit effort control.
342
344
 
343
345
  To override budgets per model, edit `thinkingLevelMap` on the relevant entry in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-sap-aicore",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
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)",
@@ -29,6 +29,7 @@ type VertexGenerateContentResponse = {
29
29
  role?: string;
30
30
  parts?: Array<{
31
31
  text?: string;
32
+ thoughtSignature?: string;
32
33
  functionCall?: { name?: string; args?: unknown };
33
34
  }>;
34
35
  };
@@ -192,14 +193,18 @@ function replayVertexGenerateContentResponse(
192
193
 
193
194
  if (part.functionCall) {
194
195
  const contentIndex = output.content.length;
196
+ const providerName = part.functionCall.name ?? "";
195
197
  const toolCall = {
196
198
  type: "toolCall" as const,
197
199
  id: randomUUID(),
198
- name: part.functionCall.name ?? "",
200
+ name: normalizeVertexFunctionName(providerName),
199
201
  arguments:
200
202
  part.functionCall.args && typeof part.functionCall.args === "object"
201
203
  ? (part.functionCall.args as Record<string, unknown>)
202
204
  : {},
205
+ ...(part.thoughtSignature
206
+ ? { thoughtSignature: part.thoughtSignature }
207
+ : {}),
203
208
  };
204
209
  output.content.push(toolCall);
205
210
  stream.push({ type: "toolcall_start", contentIndex, partial: output });
@@ -212,3 +217,8 @@ function replayVertexGenerateContentResponse(
212
217
  }
213
218
  }
214
219
  }
220
+
221
+ function normalizeVertexFunctionName(name: string): string {
222
+ const index = name.indexOf(":");
223
+ return index >= 0 ? name.slice(index + 1) : name;
224
+ }
@@ -3,6 +3,7 @@ import type {
3
3
  Context,
4
4
  Message,
5
5
  TextContent,
6
+ Tool,
6
7
  ToolResultMessage,
7
8
  UserMessage,
8
9
  } from "@earendil-works/pi-ai";
@@ -35,9 +36,20 @@ export type BedrockConverseMessage = {
35
36
  content: BedrockConverseContentBlock[];
36
37
  };
37
38
 
39
+ export type BedrockToolConfig = {
40
+ tools: Array<{
41
+ toolSpec: {
42
+ name: string;
43
+ description: string;
44
+ inputSchema: { json: Record<string, unknown> };
45
+ };
46
+ }>;
47
+ };
48
+
38
49
  export function piContextToBedrockConverse(context: Context): {
39
50
  system?: Array<{ text: string }>;
40
51
  messages: BedrockConverseMessage[];
52
+ toolConfig?: BedrockToolConfig;
41
53
  } {
42
54
  const messages: BedrockConverseMessage[] = [];
43
55
 
@@ -46,9 +58,11 @@ export function piContextToBedrockConverse(context: Context): {
46
58
  if (translated) messages.push(translated);
47
59
  }
48
60
 
61
+ const tools = (context.tools ?? []).map(piToolToBedrockToolSpec);
49
62
  return {
50
63
  ...(context.systemPrompt ? { system: [{ text: context.systemPrompt }] } : {}),
51
64
  messages: coalesceAdjacentMessages(messages),
65
+ ...(tools.length > 0 ? { toolConfig: { tools } } : {}),
52
66
  };
53
67
  }
54
68
 
@@ -159,6 +173,18 @@ function imageFormatFromMimeType(mimeType: string): string {
159
173
  return "png";
160
174
  }
161
175
 
176
+ function piToolToBedrockToolSpec(tool: Tool): BedrockToolConfig["tools"][number] {
177
+ return {
178
+ toolSpec: {
179
+ name: tool.name,
180
+ description: tool.description,
181
+ inputSchema: {
182
+ json: tool.parameters as unknown as Record<string, unknown>,
183
+ },
184
+ },
185
+ };
186
+ }
187
+
162
188
  function coalesceAdjacentMessages(
163
189
  messages: BedrockConverseMessage[],
164
190
  ): BedrockConverseMessage[] {
@@ -3,6 +3,7 @@ import type {
3
3
  Context,
4
4
  Message,
5
5
  TextContent,
6
+ Tool,
6
7
  ToolResultMessage,
7
8
  UserMessage,
8
9
  } from "@earendil-works/pi-ai";
@@ -10,7 +11,7 @@ import type {
10
11
  export type VertexPart =
11
12
  | { text: string }
12
13
  | { inlineData: { mimeType: string; data: string } }
13
- | { functionCall: { name: string; args: unknown } }
14
+ | { functionCall: { name: string; args: unknown }; thoughtSignature?: string }
14
15
  | { functionResponse: { name: string; response: Record<string, unknown> } };
15
16
 
16
17
  export type VertexContent = {
@@ -18,9 +19,18 @@ export type VertexContent = {
18
19
  parts: VertexPart[];
19
20
  };
20
21
 
22
+ export type VertexTool = {
23
+ functionDeclarations: Array<{
24
+ name: string;
25
+ description: string;
26
+ parameters: Record<string, unknown>;
27
+ }>;
28
+ };
29
+
21
30
  export function piContextToVertexGenerateContent(context: Context): {
22
31
  systemInstruction?: { parts: Array<{ text: string }> };
23
32
  contents: VertexContent[];
33
+ tools?: VertexTool[];
24
34
  } {
25
35
  const contents: VertexContent[] = [];
26
36
 
@@ -29,11 +39,17 @@ export function piContextToVertexGenerateContent(context: Context): {
29
39
  if (translated) contents.push(translated);
30
40
  }
31
41
 
42
+ const functionDeclarations = (context.tools ?? []).map(
43
+ piToolToVertexFunctionDeclaration,
44
+ );
32
45
  return {
33
46
  ...(context.systemPrompt
34
47
  ? { systemInstruction: { parts: [{ text: context.systemPrompt }] } }
35
48
  : {}),
36
49
  contents: coalesceAdjacentContents(contents),
50
+ ...(functionDeclarations.length > 0
51
+ ? { tools: [{ functionDeclarations }] }
52
+ : {}),
37
53
  };
38
54
  }
39
55
 
@@ -68,7 +84,13 @@ function piAssistantToVertexContent(msg: AssistantMessage): VertexContent {
68
84
  parts.push({ text: block.text });
69
85
  } else if (block.type === "toolCall") {
70
86
  parts.push({
71
- functionCall: { name: block.name, args: block.arguments },
87
+ functionCall: {
88
+ name: block.name,
89
+ args: block.arguments,
90
+ },
91
+ ...(block.thoughtSignature
92
+ ? { thoughtSignature: block.thoughtSignature }
93
+ : {}),
72
94
  });
73
95
  }
74
96
  }
@@ -111,6 +133,16 @@ function toolResultImagesAsUserParts(msg: ToolResultMessage): VertexPart[] {
111
133
  }));
112
134
  }
113
135
 
136
+ function piToolToVertexFunctionDeclaration(
137
+ tool: Tool,
138
+ ): VertexTool["functionDeclarations"][number] {
139
+ return {
140
+ name: tool.name,
141
+ description: tool.description,
142
+ parameters: tool.parameters as unknown as Record<string, unknown>,
143
+ };
144
+ }
145
+
114
146
  function coalesceAdjacentContents(contents: VertexContent[]): VertexContent[] {
115
147
  const result: VertexContent[] = [];
116
148
  for (const content of contents) {