@centerforagenticai/pi-multi-account 0.1.5 → 0.1.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/README.md +17 -3
- package/package.json +5 -5
- package/packages/pi-anthropic-oauth/package.json +2 -2
- package/packages/pi-anthropic-oauth/src/stream.ts +18 -8
- package/packages/pi-anthropic-oauth/src/transport-activity.ts +59 -0
- package/packages/pi-antigravity/package.json +2 -2
- package/packages/pi-antigravity/src/models/discovery.ts +2 -1
- package/packages/pi-antigravity/src/models/grouping.ts +12 -10
- package/packages/pi-antigravity/src/models/models.ts +11 -4
- package/src/account-group-failure.ts +149 -0
- package/src/account-group-members.ts +139 -0
- package/src/anthropic-adaptive-stream.ts +18 -9
- package/src/anthropic-alias-stream.ts +13 -68
- package/src/codex-adapter.ts +5 -51
- package/src/commands.ts +3 -3
- package/src/config.ts +46 -2
- package/src/diagnostic-store.ts +17 -15
- package/src/diagnostics.ts +97 -35
- package/src/host-final-stop-message.ts +18 -128
- package/src/index.ts +436 -88
- package/src/logical-dispatch.ts +12 -13
- package/src/logical-provider.ts +1239 -407
- package/src/models-declaration.ts +15 -8
- package/src/public-assistant-projection.ts +163 -0
- package/src/recovery-engine.ts +311 -59
- package/src/routing.ts +4 -8
- package/src/runtime-state.ts +7 -0
- package/src/upstream-anthropic.ts +12 -4
- package/src/upstream-antigravity.ts +2 -38
|
@@ -88,6 +88,16 @@ export type ManagedLogicalModelSource =
|
|
|
88
88
|
readonly model: Model<typeof GOOGLE_ANTIGRAVITY_API>;
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Closed copy-by-name tables over Pi's model types. A field Pi removes or
|
|
93
|
+
* renames is a compile error (excess property), so a lost capability is never
|
|
94
|
+
* silent. A field Pi adds is NOT required here: it is excluded by default
|
|
95
|
+
* because projection copies only listed fields, and
|
|
96
|
+
* `test/package-contract.test.ts` reports it as a warning for review. See
|
|
97
|
+
* docs/pi-upgrades.md.
|
|
98
|
+
*/
|
|
99
|
+
type PiFieldDisposition<T, D extends string> = Partial<Record<keyof T, D>>;
|
|
100
|
+
|
|
91
101
|
const MODEL_FIELD_DISPOSITION = {
|
|
92
102
|
id: "copy",
|
|
93
103
|
name: "copy",
|
|
@@ -103,8 +113,8 @@ const MODEL_FIELD_DISPOSITION = {
|
|
|
103
113
|
samplingParams: "copy",
|
|
104
114
|
headers: "exclude",
|
|
105
115
|
compat: "copy",
|
|
106
|
-
} as const satisfies
|
|
107
|
-
|
|
116
|
+
} as const satisfies PiFieldDisposition<
|
|
117
|
+
Model<Api>,
|
|
108
118
|
"copy" | "exclude" | "override"
|
|
109
119
|
>;
|
|
110
120
|
|
|
@@ -118,9 +128,8 @@ const ANTHROPIC_COMPAT_DISPOSITION = {
|
|
|
118
128
|
allowEmptySignature: "copy",
|
|
119
129
|
supportsStrictTools: "copy",
|
|
120
130
|
allowedFallbackModels: "exclude",
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
keyof AnthropicMessagesCompat,
|
|
131
|
+
} as const satisfies PiFieldDisposition<
|
|
132
|
+
AnthropicMessagesCompat,
|
|
124
133
|
"copy" | "exclude"
|
|
125
134
|
>;
|
|
126
135
|
|
|
@@ -133,7 +142,7 @@ const CODEX_COMPAT_DISPOSITION = {
|
|
|
133
142
|
supportsAdditionalTools: "copy",
|
|
134
143
|
supportsToolSearch: "copy",
|
|
135
144
|
supportsExplicitPromptCacheMode: "copy",
|
|
136
|
-
} as const satisfies
|
|
145
|
+
} as const satisfies PiFieldDisposition<OpenAIResponsesCompat, "copy">;
|
|
137
146
|
|
|
138
147
|
const THINKING_LEVEL_DISPOSITION = {
|
|
139
148
|
off: "copy",
|
|
@@ -549,8 +558,6 @@ function projectAnthropicCompat(
|
|
|
549
558
|
if (emptySignature !== undefined) projected.allowEmptySignature = emptySignature;
|
|
550
559
|
const strictTools = optionalCompatBoolean(properties, "supportsStrictTools", "anthropic");
|
|
551
560
|
if (strictTools !== undefined) projected.supportsStrictTools = strictTools;
|
|
552
|
-
const toolReferences = optionalCompatBoolean(properties, "supportsToolReferences", "anthropic");
|
|
553
|
-
if (toolReferences !== undefined) projected.supportsToolReferences = toolReferences;
|
|
554
561
|
return projected;
|
|
555
562
|
}
|
|
556
563
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { Api, Model, AssistantMessage, AssistantMessageEvent, ToolCall } from "@earendil-works/pi-ai";
|
|
2
|
+
import { PROVIDER_ERROR_CODES, TRANSPORT_FAILURE_KINDS, type ProviderErrorCode, type TransportFailureKind } from "./error-classification.js";
|
|
3
|
+
import { hostFinalStopMessage } from "./host-final-stop-message.js";
|
|
4
|
+
import { sanitizeDiagnosticText } from "./diagnostics.js";
|
|
5
|
+
|
|
6
|
+
export interface PublicAssistantIdentity {
|
|
7
|
+
readonly api: AssistantMessage["api"];
|
|
8
|
+
readonly provider: AssistantMessage["provider"];
|
|
9
|
+
readonly model: string;
|
|
10
|
+
}
|
|
11
|
+
export type PublicAssistantMessage = AssistantMessage & {
|
|
12
|
+
code?: ProviderErrorCode;
|
|
13
|
+
httpStatus?: number;
|
|
14
|
+
transportKind?: TransportFailureKind;
|
|
15
|
+
retryAfterSeconds?: number;
|
|
16
|
+
resetAtMs?: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Bounds accounting/time facts without coercing provider values. */
|
|
20
|
+
export function finiteNonNegative(value: unknown): number | undefined {
|
|
21
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= Number.MAX_SAFE_INTEGER ? value : undefined;
|
|
22
|
+
}
|
|
23
|
+
export function projectTerminalUsage(message: AssistantMessage): AssistantMessage["usage"] | undefined {
|
|
24
|
+
const usage = message.usage;
|
|
25
|
+
if (typeof usage !== "object" || usage === null || typeof usage.cost !== "object" || usage.cost === null) return undefined;
|
|
26
|
+
const input = finiteNonNegative(usage.input);
|
|
27
|
+
const output = finiteNonNegative(usage.output);
|
|
28
|
+
const cacheRead = finiteNonNegative(usage.cacheRead);
|
|
29
|
+
const cacheWrite = finiteNonNegative(usage.cacheWrite);
|
|
30
|
+
const totalTokens = finiteNonNegative(usage.totalTokens);
|
|
31
|
+
const costInput = finiteNonNegative(usage.cost.input);
|
|
32
|
+
const costOutput = finiteNonNegative(usage.cost.output);
|
|
33
|
+
const costCacheRead = finiteNonNegative(usage.cost.cacheRead);
|
|
34
|
+
const costCacheWrite = finiteNonNegative(usage.cost.cacheWrite);
|
|
35
|
+
const costTotal = finiteNonNegative(usage.cost.total);
|
|
36
|
+
const cacheWrite1h = finiteNonNegative(usage.cacheWrite1h);
|
|
37
|
+
const reasoning = finiteNonNegative(usage.reasoning);
|
|
38
|
+
if ((usage.cacheWrite1h !== undefined && cacheWrite1h === undefined) || (usage.reasoning !== undefined && reasoning === undefined)) return undefined;
|
|
39
|
+
// Construct only validated named accounting fields.
|
|
40
|
+
if (input === undefined || output === undefined || cacheRead === undefined || cacheWrite === undefined || totalTokens === undefined || costInput === undefined || costOutput === undefined || costCacheRead === undefined || costCacheWrite === undefined || costTotal === undefined) return undefined;
|
|
41
|
+
return { input, output, cacheRead, cacheWrite, totalTokens,
|
|
42
|
+
...(cacheWrite1h === undefined ? {} : { cacheWrite1h }), ...(reasoning === undefined ? {} : { reasoning }),
|
|
43
|
+
cost: { input: costInput, output: costOutput, cacheRead: costCacheRead, cacheWrite: costCacheWrite, total: costTotal } };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function projectToolCall(block: ToolCall): ToolCall {
|
|
47
|
+
if (typeof block !== "object" || block === null || typeof block.id !== "string" || typeof block.name !== "string" || typeof block.arguments !== "object" || block.arguments === null || Array.isArray(block.arguments)) {
|
|
48
|
+
throw new TypeError("Malformed public tool call.");
|
|
49
|
+
}
|
|
50
|
+
return { type: "toolCall", id: block.id, name: block.name, arguments: block.arguments,
|
|
51
|
+
...(typeof block.thoughtSignature === "string" ? { thoughtSignature: block.thoughtSignature } : {}),
|
|
52
|
+
...(typeof block.namespace === "string" ? { namespace: block.namespace } : {}) };
|
|
53
|
+
}
|
|
54
|
+
/** Content is the response, not diagnostic metadata. Keep protocol signatures and tool IDs. */
|
|
55
|
+
export function projectAssistantContent(content: AssistantMessage["content"]): AssistantMessage["content"] {
|
|
56
|
+
if (!Array.isArray(content)) return [];
|
|
57
|
+
return content.flatMap((block): AssistantMessage["content"] => {
|
|
58
|
+
if (typeof block !== "object" || block === null) return [];
|
|
59
|
+
switch (block.type) {
|
|
60
|
+
case "text": if (typeof block.text !== "string") return [];
|
|
61
|
+
return [{ type: "text", text: block.text,
|
|
62
|
+
...(typeof block.textSignature === "string" ? { textSignature: block.textSignature } : {}) }];
|
|
63
|
+
case "thinking": if (typeof block.thinking !== "string") return [];
|
|
64
|
+
return [{ type: "thinking", thinking: block.thinking,
|
|
65
|
+
...(typeof block.thinkingSignature === "string" ? { thinkingSignature: block.thinkingSignature } : {}),
|
|
66
|
+
...(typeof block.redacted === "boolean" ? { redacted: block.redacted } : {}) }];
|
|
67
|
+
case "toolCall":
|
|
68
|
+
if (typeof block.id !== "string" || typeof block.name !== "string" || typeof block.arguments !== "object" || block.arguments === null || Array.isArray(block.arguments)) return [];
|
|
69
|
+
return [projectToolCall(block)];
|
|
70
|
+
default: return [];
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Only the managed transport diagnostic's named facts are public; never its Error/details DTO. */
|
|
76
|
+
function projectTransportDiagnostics(message: AssistantMessage): AssistantMessage["diagnostics"] {
|
|
77
|
+
if (!Array.isArray(message.diagnostics)) return undefined;
|
|
78
|
+
return message.diagnostics.flatMap((entry) => {
|
|
79
|
+
if (typeof entry !== "object" || entry === null || entry.type !== "provider_transport_failure") return [];
|
|
80
|
+
const timestamp = finiteNonNegative(entry.timestamp);
|
|
81
|
+
if (timestamp === undefined) return [];
|
|
82
|
+
const details = entry.details;
|
|
83
|
+
const configured = details?.configuredTransport;
|
|
84
|
+
const fallback = details?.fallbackTransport;
|
|
85
|
+
const phase = details?.phase;
|
|
86
|
+
return [{ type: "provider_transport_failure", timestamp, details: {
|
|
87
|
+
...(typeof configured === "string" && ["auto", "sse", "websocket", "websocket-cached"].includes(configured) ? { configuredTransport: configured } : {}),
|
|
88
|
+
...(fallback === "sse" ? { fallbackTransport: "sse" } : {}),
|
|
89
|
+
...(typeof details?.eventsEmitted === "boolean" ? { eventsEmitted: details.eventsEmitted } : {}),
|
|
90
|
+
...(phase === "before_message_stream_start" || phase === "after_message_stream_start" ? { phase } : {}),
|
|
91
|
+
} }];
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Known wire reasons are public enums, not arbitrary provider prose. Managed streams
|
|
96
|
+
// emit no DeferredHandle; responseId/responseModel are output-only metadata here.
|
|
97
|
+
const PUBLIC_RAW_STOP_REASONS = [
|
|
98
|
+
"end_turn", "max_tokens", "stop_sequence", "tool_use", "pause_turn", "refusal", "sensitive",
|
|
99
|
+
"completed", "incomplete", "incomplete.max_output_tokens", "incomplete.content_filter", "content_filter", "failed", "cancelled",
|
|
100
|
+
"STOP", "MAX_TOKENS", "SAFETY", "PROHIBITED_CONTENT", "BLOCKLIST", "SPII", "RECITATION", "FINISH_REASON_UNSPECIFIED", "OTHER",
|
|
101
|
+
] as const;
|
|
102
|
+
/** Public managed response DTO: no provider-object spread, request IDs, raw bodies or unknown fields. */
|
|
103
|
+
export function projectPublicAssistantMessage(
|
|
104
|
+
message: AssistantMessage, identity: PublicAssistantIdentity, errorMessage?: string,
|
|
105
|
+
): PublicAssistantMessage {
|
|
106
|
+
const stop = hostFinalStopMessage(message);
|
|
107
|
+
const codeValue = Object.getOwnPropertyDescriptor(message, "code")?.value;
|
|
108
|
+
const code = PROVIDER_ERROR_CODES.find((value) => value === codeValue);
|
|
109
|
+
const statusValue = Object.getOwnPropertyDescriptor(message, "httpStatus")?.value ?? Object.getOwnPropertyDescriptor(message, "status")?.value;
|
|
110
|
+
const httpStatus = typeof statusValue === "number" && Number.isInteger(statusValue) && statusValue >= 100 && statusValue <= 599 ? statusValue : undefined;
|
|
111
|
+
const transportValue = Object.getOwnPropertyDescriptor(message, "transportKind")?.value;
|
|
112
|
+
const transportKind = TRANSPORT_FAILURE_KINDS.find((value) => value === transportValue);
|
|
113
|
+
const retryAfterSeconds = finiteNonNegative(Object.getOwnPropertyDescriptor(message, "retryAfterSeconds")?.value);
|
|
114
|
+
const resetAtMs = finiteNonNegative(Object.getOwnPropertyDescriptor(message, "resetAtMs")?.value);
|
|
115
|
+
const diagnostics = projectTransportDiagnostics(message);
|
|
116
|
+
const usage = projectTerminalUsage(message) ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0,
|
|
117
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } };
|
|
118
|
+
return {
|
|
119
|
+
role: "assistant", content: projectAssistantContent(message.content),
|
|
120
|
+
api: identity.api, provider: identity.provider, model: identity.model,
|
|
121
|
+
usage, timestamp: finiteNonNegative(message.timestamp) ?? 0,
|
|
122
|
+
stopReason: ["pending", "stop", "length", "toolUse", "error", "aborted", "deferred"].includes(message.stopReason) ? message.stopReason : "error",
|
|
123
|
+
...(typeof message.endTurn === "boolean" ? { endTurn: message.endTurn } : {}),
|
|
124
|
+
...(PUBLIC_RAW_STOP_REASONS.find((reason) => reason === message.rawStopReason) === undefined ? {} : { rawStopReason: message.rawStopReason }),
|
|
125
|
+
...(code === undefined ? {} : { code }), ...(httpStatus === undefined ? {} : { httpStatus }),
|
|
126
|
+
...(transportKind === undefined ? {} : { transportKind }),
|
|
127
|
+
...(retryAfterSeconds === undefined ? {} : { retryAfterSeconds }), ...(resetAtMs === undefined ? {} : { resetAtMs }),
|
|
128
|
+
...(diagnostics === undefined || diagnostics.length === 0 ? {} : { diagnostics }),
|
|
129
|
+
...(stop ?? (errorMessage === undefined ? {} : { errorMessage })),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Named event variants prevent extra fields on progress and terminal envelopes from surviving. */
|
|
134
|
+
export function projectPublicAssistantEvent(event: unknown, identity: PublicAssistantIdentity, errorMessage?: string): AssistantMessageEvent | undefined {
|
|
135
|
+
if (typeof event !== "object" || event === null) return undefined;
|
|
136
|
+
// The public stream union is validated by its producer/registration bridge.
|
|
137
|
+
const source = event as AssistantMessageEvent;
|
|
138
|
+
const contentIndex = "contentIndex" in source && typeof source.contentIndex === "number" && Number.isSafeInteger(source.contentIndex) && source.contentIndex >= 0 ? source.contentIndex : 0;
|
|
139
|
+
switch (source.type) {
|
|
140
|
+
case "done": return { type: "done", reason: ["stop", "length", "toolUse", "deferred"].includes(source.reason) ? source.reason : "stop", message: projectPublicAssistantMessage(source.message, identity, errorMessage) };
|
|
141
|
+
case "error": return { type: "error", reason: source.reason === "aborted" ? "aborted" : "error", error: projectPublicAssistantMessage(source.error, identity, errorMessage) };
|
|
142
|
+
case "start": return { type: "start", partial: projectPublicAssistantMessage(source.partial, identity) };
|
|
143
|
+
case "text_start": case "thinking_start": case "toolcall_start":
|
|
144
|
+
return { type: source.type, contentIndex, partial: projectPublicAssistantMessage(source.partial, identity) };
|
|
145
|
+
case "text_delta": case "thinking_delta": case "toolcall_delta":
|
|
146
|
+
return { type: source.type, contentIndex, delta: typeof source.delta === "string" ? source.delta : "", partial: projectPublicAssistantMessage(source.partial, identity) };
|
|
147
|
+
case "text_end": case "thinking_end":
|
|
148
|
+
return { type: source.type, contentIndex, content: typeof source.content === "string" ? source.content : "", partial: projectPublicAssistantMessage(source.partial, identity) };
|
|
149
|
+
case "toolcall_end":
|
|
150
|
+
return { type: "toolcall_end", contentIndex, toolCall: projectToolCall(source.toolCall), partial: projectPublicAssistantMessage(source.partial, identity) };
|
|
151
|
+
default: return undefined;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Alias errors keep existing bounded retry prose; structured stops always use fixed reasons. */
|
|
156
|
+
export function projectAliasAssistantEvent(event: AssistantMessageEvent, model: Model<Api>): AssistantMessageEvent {
|
|
157
|
+
const rawError = event.type === "error" ? event.error.errorMessage : undefined;
|
|
158
|
+
const projected = projectPublicAssistantEvent(event, {
|
|
159
|
+
api: model.api, provider: model.provider, model: model.id,
|
|
160
|
+
}, rawError === undefined ? undefined : sanitizeDiagnosticText(rawError));
|
|
161
|
+
if (projected === undefined) throw new TypeError("Unsupported provider stream event.");
|
|
162
|
+
return projected;
|
|
163
|
+
}
|