Skip to content

Commit 020a8e6

Browse files
committed
Merge PR #7: preserve Pi trajectory metadata
# Conflicts: # packages/core/src/agy-request-metadata.test.ts # packages/core/src/agy-request-metadata.ts # packages/pi/src/convert.test.ts # packages/pi/src/convert.ts # packages/pi/src/stream.test.ts # packages/pi/src/stream.ts
2 parents 92ddb0b + d3c3bd6 commit 020a8e6

3 files changed

Lines changed: 400 additions & 284 deletions

File tree

packages/core/src/agy-request-metadata.ts

Lines changed: 60 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
1-
import { Buffer } from 'node:buffer'
2-
import { randomUUID } from 'node:crypto'
1+
import { Buffer } from "node:buffer"
2+
import { randomUUID } from "node:crypto"
33

44
const FNV1A_64_OFFSET_BASIS = 0xcbf29ce484222325n
55
const FNV1A_64_PRIME = 0x100000001b3n
66
const DEFAULT_SESSION_STATE_TTL_MS = 24 * 60 * 60 * 1000
77
const DEFAULT_MAX_SESSION_STATES = 256
88

99
const AGY_REQUEST_FIELD_ORDER = [
10-
'contents',
11-
'systemInstruction',
12-
'tools',
13-
'toolConfig',
14-
'labels',
15-
'generationConfig',
16-
'sessionId',
10+
"contents",
11+
"systemInstruction",
12+
"tools",
13+
"toolConfig",
14+
"labels",
15+
"generationConfig",
16+
"sessionId",
1717
] as const
1818

1919
const AGY_MODEL_ENUM_BY_WIRE_MODEL: Readonly<Record<string, string>> = {
20-
'gemini-3.5-flash-extra-low': 'MODEL_PLACEHOLDER_M187',
21-
'gemini-3.5-flash-low': 'MODEL_PLACEHOLDER_M20',
22-
'gemini-3-flash-agent': 'MODEL_PLACEHOLDER_M84',
23-
'gemini-3.6-flash-low': 'MODEL_PLACEHOLDER_M266',
24-
'gemini-3.6-flash-medium': 'MODEL_PLACEHOLDER_M265',
25-
'gemini-3.6-flash-high': 'MODEL_PLACEHOLDER_M264',
26-
'gemini-3.1-pro-low': 'MODEL_PLACEHOLDER_M36',
27-
'gemini-pro-agent': 'MODEL_PLACEHOLDER_M16',
28-
'claude-sonnet-4-6': 'MODEL_PLACEHOLDER_M35',
29-
'claude-opus-4-6-thinking': 'MODEL_PLACEHOLDER_M26',
30-
'gemini-3.1-flash-image': 'MODEL_PLACEHOLDER_M21',
31-
'gpt-oss-120b-medium': 'MODEL_OPENAI_GPT_OSS_120B_MEDIUM',
20+
"gemini-3.5-flash-extra-low": "MODEL_PLACEHOLDER_M187",
21+
"gemini-3.5-flash-low": "MODEL_PLACEHOLDER_M20",
22+
"gemini-3-flash-agent": "MODEL_PLACEHOLDER_M84",
23+
"gemini-3.6-flash-low": "MODEL_PLACEHOLDER_M266",
24+
"gemini-3.6-flash-medium": "MODEL_PLACEHOLDER_M265",
25+
"gemini-3.6-flash-high": "MODEL_PLACEHOLDER_M264",
26+
"gemini-3.1-pro-low": "MODEL_PLACEHOLDER_M36",
27+
"gemini-pro-agent": "MODEL_PLACEHOLDER_M16",
28+
"claude-sonnet-4-6": "MODEL_PLACEHOLDER_M35",
29+
"claude-opus-4-6-thinking": "MODEL_PLACEHOLDER_M26",
30+
"gemini-3.1-flash-image": "MODEL_PLACEHOLDER_M21",
31+
"gpt-oss-120b-medium": "MODEL_OPENAI_GPT_OSS_120B_MEDIUM",
3232
}
3333

3434
export interface AgyRequestSessionContext {
@@ -37,6 +37,7 @@ export interface AgyRequestSessionContext {
3737
numericSessionId: string
3838
usedClaude?: boolean
3939
usedNonGeminiModel?: boolean
40+
lastExecutionId?: string
4041
}
4142

4243
interface StoredAgyRequestSession {
@@ -57,12 +58,13 @@ export interface AgyRequestScope {
5758
}
5859

5960
export interface AgyRequestLabels {
61+
last_execution_id?: string
6062
last_step_index: string
6163
model_enum?: string
6264
trajectory_id: string
63-
used_claude: 'true' | 'false'
64-
used_claude_conservative: 'true' | 'false'
65-
used_non_gemini_model: 'true' | 'false'
65+
used_claude: "true" | "false"
66+
used_claude_conservative: "true" | "false"
67+
used_non_gemini_model: "true" | "false"
6668
}
6769

6870
export interface AgyAgentRequestMetadata {
@@ -72,9 +74,13 @@ export interface AgyAgentRequestMetadata {
7274
lastStepIndex: number
7375
}
7476

77+
export interface AgyAgentRequestMetadataOptions {
78+
stepCountMode?: "parts" | "contents"
79+
}
80+
7581
export function fnv1a64Signed(input: string): string {
7682
let hash = FNV1A_64_OFFSET_BASIS
77-
for (const byte of Buffer.from(input, 'utf8')) {
83+
for (const byte of Buffer.from(input, "utf8")) {
7884
hash ^= BigInt(byte)
7985
hash = BigInt.asUintN(64, hash * FNV1A_64_PRIME)
8086
}
@@ -99,10 +105,7 @@ export class AgyRequestSessionStore {
99105
private readonly maxEntries: number
100106
private readonly now: () => number
101107

102-
constructor(
103-
workspaceUri: string,
104-
options: AgyRequestSessionStoreOptions = {},
105-
) {
108+
constructor(workspaceUri: string, options: AgyRequestSessionStoreOptions = {}) {
106109
this.workspaceUri = workspaceUri
107110
this.ttlMs = options.ttlMs ?? DEFAULT_SESSION_STATE_TTL_MS
108111
this.maxEntries = options.maxEntries ?? DEFAULT_MAX_SESSION_STATES
@@ -131,14 +134,18 @@ export class AgyRequestSessionStore {
131134
beginRequest(key: string): AgyRequestScope {
132135
const session = this.getOrCreate(key)
133136
const stored = this.entries.get(key)!
134-
const timestamp = Math.max(
135-
stored.lastAccessedAt,
136-
stored.lastRequestTimestamp + 1,
137-
)
137+
const timestamp = Math.max(stored.lastAccessedAt, stored.lastRequestTimestamp + 1)
138138
stored.lastRequestTimestamp = timestamp
139139
return { session, timestamp }
140140
}
141141

142+
completeExecution(key: string): void {
143+
const stored = this.entries.get(key)
144+
if (stored) {
145+
stored.context.lastExecutionId = randomUUID()
146+
}
147+
}
148+
142149
has(key: string): boolean {
143150
return this.entries.has(key)
144151
}
@@ -147,10 +154,6 @@ export class AgyRequestSessionStore {
147154
this.entries.delete(key)
148155
}
149156

150-
clear(): void {
151-
this.entries.clear()
152-
}
153-
154157
get size(): number {
155158
return this.entries.size
156159
}
@@ -163,10 +166,7 @@ export class AgyRequestSessionStore {
163166
}
164167
}
165168

166-
while (
167-
this.entries.size >= this.maxEntries &&
168-
!this.entries.has(preservedKey)
169-
) {
169+
while (this.entries.size >= this.maxEntries && !this.entries.has(preservedKey)) {
170170
let oldestKey: string | null = null
171171
let oldestAccess = Number.POSITIVE_INFINITY
172172
for (const [key, value] of this.entries) {
@@ -187,9 +187,7 @@ export function getAgyModelEnum(model: string): string | undefined {
187187
return AGY_MODEL_ENUM_BY_WIRE_MODEL[model.toLowerCase()]
188188
}
189189

190-
export function orderAgyRequestPayloadInPlace(
191-
payload: Record<string, unknown>,
192-
): void {
190+
export function orderAgyRequestPayloadInPlace(payload: Record<string, unknown>): void {
193191
const ordered: Record<string, unknown> = {}
194192
const remaining = new Set(Object.keys(payload))
195193

@@ -209,21 +207,19 @@ export function orderAgyRequestPayloadInPlace(
209207
Object.assign(payload, ordered)
210208
}
211209

212-
export function countAgyRequestSteps(payload: Record<string, unknown>): number {
210+
export function countAgyRequestSteps(
211+
payload: Record<string, unknown>,
212+
mode: "parts" | "contents" = "parts",
213+
): number {
213214
const contents = payload.contents
214-
if (!Array.isArray(contents)) {
215-
return 1
216-
}
215+
if (!Array.isArray(contents)) return 1
216+
if (mode === "contents") return Math.max(1, contents.length)
217217

218218
let partCount = 0
219219
for (const content of contents) {
220-
if (!content || typeof content !== 'object' || Array.isArray(content)) {
221-
continue
222-
}
220+
if (!content || typeof content !== "object" || Array.isArray(content)) continue
223221
const parts = (content as Record<string, unknown>).parts
224-
if (Array.isArray(parts)) {
225-
partCount += parts.length
226-
}
222+
if (Array.isArray(parts)) partCount += parts.length
227223
}
228224
return Math.max(1, partCount)
229225
}
@@ -233,21 +229,23 @@ export function buildAgyAgentRequestMetadata(
233229
payload: Record<string, unknown>,
234230
model: string,
235231
timestamp = Date.now(),
232+
options: AgyAgentRequestMetadataOptions = {},
236233
): AgyAgentRequestMetadata {
237-
const lastStepIndex = countAgyRequestSteps(payload)
238-
const isClaude = model.toLowerCase().startsWith('claude-')
239-
const isNonGemini = isClaude || model.toLowerCase().startsWith('gpt-')
234+
const lastStepIndex = countAgyRequestSteps(payload, options.stepCountMode)
235+
+ (session.lastExecutionId ? 1 : 0)
236+
const isClaude = model.toLowerCase().startsWith("claude-")
237+
const isNonGemini = isClaude || model.toLowerCase().startsWith("gpt-")
240238
session.usedClaude = session.usedClaude === true || isClaude
241-
session.usedNonGeminiModel =
242-
session.usedNonGeminiModel === true || isNonGemini
239+
session.usedNonGeminiModel = session.usedNonGeminiModel === true || isNonGemini
243240
const modelEnum = getAgyModelEnum(model)
244241
const labels: AgyRequestLabels = {
242+
...(session.lastExecutionId ? { last_execution_id: session.lastExecutionId } : {}),
245243
last_step_index: String(lastStepIndex),
246244
...(modelEnum ? { model_enum: modelEnum } : {}),
247245
trajectory_id: session.trajectoryId,
248-
used_claude: session.usedClaude ? 'true' : 'false',
249-
used_claude_conservative: session.usedClaude ? 'true' : 'false',
250-
used_non_gemini_model: session.usedNonGeminiModel ? 'true' : 'false',
246+
used_claude: session.usedClaude ? "true" : "false",
247+
used_claude_conservative: session.usedClaude ? "true" : "false",
248+
used_non_gemini_model: session.usedNonGeminiModel ? "true" : "false",
251249
}
252250

253251
return {

0 commit comments

Comments
 (0)