mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(ai,agent,coding-agent): move client identity and provider attribution into pi-ai
pi-ai gets a ClientIdentity request option and createModels() default that choose the User-Agent (detailed with version, short, or custom) and provider attribution headers for OpenRouter, NVIDIA NIM, and OpenCode. Attribution is off by default in pi-ai and on by default in pi-agent-core and pi-durable (HarnessOptions.identity). pi-coding-agent sets its identity from the telemetry setting instead of adding headers itself.
This commit is contained in:
parent
adfc14461a
commit
adc5fa9f80
55 changed files with 958 additions and 465 deletions
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added `AgentOptions.identity` and `Agent.identity` (a pi-ai `ClientIdentity`). Agent loop requests use `DEFAULT_AGENT_IDENTITY` (`{ attribution: true }`) under it, so OpenRouter, NVIDIA NIM, and OpenCode requests are attributed to pi by default; pass `{ attribution: false }` to send no attribution headers.
|
||||
|
||||
## [1.3.0] - 2026-10-15
|
||||
|
||||
## [1.2.0] - 2026-10-12
|
||||
|
|
|
|||
|
|
@ -232,6 +232,11 @@ const agent = new Agent({
|
|||
// Session ID for provider caching
|
||||
sessionId: "session-123",
|
||||
|
||||
// How requests identify the app to providers (pi-ai ClientIdentity), merged over
|
||||
// DEFAULT_AGENT_IDENTITY ({ attribution: true }: provider attribution headers name pi).
|
||||
// Pass a name to attribute requests to your app, or false to send none.
|
||||
identity: { attribution: "my-app" },
|
||||
|
||||
// Dynamic API key resolution (for expiring OAuth tokens)
|
||||
getApiKey: async (provider) => refreshToken(),
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@
|
|||
|
||||
import {
|
||||
type AssistantMessage,
|
||||
type ClientIdentity,
|
||||
EventStream,
|
||||
getCurrentTools,
|
||||
getToolStateChanges,
|
||||
mergeClientIdentity,
|
||||
normalizeContext,
|
||||
type SystemMessage,
|
||||
type ToolResultMessage,
|
||||
|
|
@ -31,6 +33,12 @@ import type {
|
|||
|
||||
export type AgentEventSink = (event: AgentEvent) => Promise<void> | void;
|
||||
|
||||
/**
|
||||
* Request identity of agent loops: pi-ai's defaults with provider attribution to pi. `AgentLoopConfig.identity`
|
||||
* overrides it field by field.
|
||||
*/
|
||||
export const DEFAULT_AGENT_IDENTITY: ClientIdentity = { attribution: true };
|
||||
|
||||
/**
|
||||
* Start an agent loop with a new prompt message.
|
||||
* The prompt is added to the context and events are emitted for it.
|
||||
|
|
@ -403,6 +411,7 @@ async function streamAssistantResponse(
|
|||
const response = await streamFunction(config.model, llmContext, {
|
||||
...config,
|
||||
apiKey: resolvedApiKey,
|
||||
identity: mergeClientIdentity(DEFAULT_AGENT_IDENTITY, config.identity),
|
||||
signal,
|
||||
});
|
||||
// Record the requested level, whichever stream function answered.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
type ClientIdentity,
|
||||
createInitialSystemMessage,
|
||||
getCurrentSystemMessage,
|
||||
getCurrentSystemPrompt,
|
||||
|
|
@ -138,6 +139,11 @@ export interface AgentOptions {
|
|||
transport?: Transport;
|
||||
maxRetryDelayMs?: number;
|
||||
toolExecution?: ToolExecutionMode;
|
||||
/**
|
||||
* How requests identify the app to providers, merged field by field over `DEFAULT_AGENT_IDENTITY`
|
||||
* (`{ attribution: true }`). Pass `{ attribution: false }` to send no attribution headers.
|
||||
*/
|
||||
identity?: ClientIdentity;
|
||||
}
|
||||
|
||||
class PendingMessageQueue {
|
||||
|
|
@ -226,6 +232,8 @@ export class Agent {
|
|||
public maxRetryDelayMs?: number;
|
||||
/** Tool execution strategy for assistant messages that contain multiple tool calls. */
|
||||
public toolExecution: ToolExecutionMode;
|
||||
/** Request identity forwarded to the stream function, over `DEFAULT_AGENT_IDENTITY`. */
|
||||
public identity?: ClientIdentity;
|
||||
|
||||
constructor(options: AgentOptions) {
|
||||
// Older compiled consumers may omit options or streamFn even though the current API requires them.
|
||||
|
|
@ -251,6 +259,7 @@ export class Agent {
|
|||
this.transport = runtimeOptions.transport ?? "auto";
|
||||
this.maxRetryDelayMs = runtimeOptions.maxRetryDelayMs;
|
||||
this.toolExecution = runtimeOptions.toolExecution ?? "parallel";
|
||||
this.identity = runtimeOptions.identity;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -476,6 +485,7 @@ export class Agent {
|
|||
transport: this.transport,
|
||||
thinkingBudgets: this.thinkingBudgets,
|
||||
maxRetryDelayMs: this.maxRetryDelayMs,
|
||||
identity: this.identity,
|
||||
toolExecution: this.toolExecution,
|
||||
beforeToolCall: this.beforeToolCall,
|
||||
afterToolCall: this.afterToolCall,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { ClientIdentity } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type AssistantMessage,
|
||||
createAssistantMessageEventStream,
|
||||
|
|
@ -1198,4 +1199,30 @@ describe("Agent", () => {
|
|||
await agent.prompt("hello again");
|
||||
expect(receivedSessionId).toBe("session-def");
|
||||
});
|
||||
|
||||
it("attributes requests to pi by default and merges a configured identity over it", async () => {
|
||||
const received: (ClientIdentity | undefined)[] = [];
|
||||
const agent = new Agent({
|
||||
streamFn: (_model, _context, options) => {
|
||||
received.push(options?.identity);
|
||||
const stream = createAssistantMessageEventStream();
|
||||
queueMicrotask(() => {
|
||||
stream.push({ type: "done", reason: "stop", message: createAssistantMessage("ok") });
|
||||
});
|
||||
return stream;
|
||||
},
|
||||
});
|
||||
|
||||
await agent.prompt("hello");
|
||||
agent.identity = { userAgent: "short", attribution: "acme" };
|
||||
await agent.prompt("hello again");
|
||||
agent.identity = { userAgent: "short" };
|
||||
await agent.prompt("and again");
|
||||
|
||||
expect(received).toEqual([
|
||||
{ attribution: true },
|
||||
{ attribution: "acme", userAgent: "short" },
|
||||
{ attribution: true, userAgent: "short" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue