Skip to content

Commit 72ac1dc

Browse files
committed
remove unnecessary inut validation; add more comments, refactor variables
1 parent 911717e commit 72ac1dc

File tree

4 files changed

+88
-47
lines changed

4 files changed

+88
-47
lines changed

packages/agents-core/src/run.ts

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -349,21 +349,22 @@ export class Runner extends RunHooks<any, AgentOutputType<unknown>> {
349349
// Keeps track of which inputs should be written back to session memory. `sourceItems` reflects
350350
// the original objects (so we can respect resume counts) while `filteredItems`, when present,
351351
// contains the filtered/redacted clones that must be persisted for history.
352-
// The helper takes the filtered clones produced by callModelInputFilter and reconciles them
353-
// with the original objects so resume-from-state bookkeeping stays consistent.
352+
// The helper reconciles the filtered copies produced by callModelInputFilter with their original
353+
// counterparts so resume-from-state bookkeeping stays consistent and duplicate references only
354+
// consume a single persistence slot.
354355
const recordSessionItemsForPersistence = (
355356
sourceItems: (AgentInputItem | undefined)[],
356357
filteredItems?: AgentInputItem[],
357358
) => {
358-
const counts = sessionInputPendingWriteCounts;
359+
const pendingWriteCounts = sessionInputPendingWriteCounts;
359360
if (filteredItems !== undefined) {
360-
if (!counts) {
361+
if (!pendingWriteCounts) {
361362
sessionInputFilteredSnapshot = filteredItems.map((item) =>
362363
structuredClone(item),
363364
);
364365
return;
365366
}
366-
const collected: AgentInputItem[] = [];
367+
const persistableItems: AgentInputItem[] = [];
367368
const sourceOccurrenceCounts = new WeakMap<AgentInputItem, number>();
368369
// Track how many times each original object appears so duplicate references only consume one persistence slot.
369370
for (const source of sourceItems) {
@@ -374,10 +375,10 @@ export class Runner extends RunHooks<any, AgentOutputType<unknown>> {
374375
sourceOccurrenceCounts.set(source, nextCount);
375376
}
376377
// Let filtered items without a one-to-one source match claim any remaining persistence count.
377-
const allocateFallback = () => {
378-
for (const [key, remaining] of counts) {
378+
const consumeAnyPendingWriteSlot = () => {
379+
for (const [key, remaining] of pendingWriteCounts) {
379380
if (remaining > 0) {
380-
counts.set(key, remaining - 1);
381+
pendingWriteCounts.set(key, remaining - 1);
381382
return true;
382383
}
383384
}
@@ -398,44 +399,45 @@ export class Runner extends RunHooks<any, AgentOutputType<unknown>> {
398399
continue;
399400
}
400401
const sourceKey = getAgentInputItemKey(source);
401-
const remaining = counts.get(sourceKey) ?? 0;
402+
const remaining = pendingWriteCounts.get(sourceKey) ?? 0;
402403
if (remaining > 0) {
403-
counts.set(sourceKey, remaining - 1);
404-
collected.push(structuredClone(filteredItem));
404+
pendingWriteCounts.set(sourceKey, remaining - 1);
405+
persistableItems.push(structuredClone(filteredItem));
405406
allocated = true;
406407
continue;
407408
}
408409
}
409410
const filteredKey = getAgentInputItemKey(filteredItem);
410-
const filteredRemaining = counts.get(filteredKey) ?? 0;
411+
const filteredRemaining = pendingWriteCounts.get(filteredKey) ?? 0;
411412
if (filteredRemaining > 0) {
412-
counts.set(filteredKey, filteredRemaining - 1);
413-
collected.push(structuredClone(filteredItem));
413+
pendingWriteCounts.set(filteredKey, filteredRemaining - 1);
414+
persistableItems.push(structuredClone(filteredItem));
414415
allocated = true;
415416
continue;
416417
}
417-
if (!source && allocateFallback()) {
418-
collected.push(structuredClone(filteredItem));
418+
if (!source && consumeAnyPendingWriteSlot()) {
419+
persistableItems.push(structuredClone(filteredItem));
419420
allocated = true;
420421
}
421422
if (
422423
!allocated &&
423424
!source &&
424425
sessionInputFilteredSnapshot === undefined
425426
) {
426-
collected.push(structuredClone(filteredItem));
427+
// Preserve at least one copy so later persistence resolves even when no counters remain.
428+
persistableItems.push(structuredClone(filteredItem));
427429
}
428430
}
429431
if (
430-
collected.length > 0 ||
432+
persistableItems.length > 0 ||
431433
sessionInputFilteredSnapshot === undefined
432434
) {
433-
sessionInputFilteredSnapshot = collected;
435+
sessionInputFilteredSnapshot = persistableItems;
434436
}
435437
return;
436438
}
437439
const filtered: AgentInputItem[] = [];
438-
if (!counts) {
440+
if (!pendingWriteCounts) {
439441
for (const item of sourceItems) {
440442
if (!item) {
441443
continue;
@@ -448,11 +450,11 @@ export class Runner extends RunHooks<any, AgentOutputType<unknown>> {
448450
continue;
449451
}
450452
const key = getAgentInputItemKey(item);
451-
const remaining = counts.get(key) ?? 0;
453+
const remaining = pendingWriteCounts.get(key) ?? 0;
452454
if (remaining <= 0) {
453455
continue;
454456
}
455-
counts.set(key, remaining - 1);
457+
pendingWriteCounts.set(key, remaining - 1);
456458
filtered.push(structuredClone(item));
457459
}
458460
}
@@ -477,6 +479,12 @@ export class Runner extends RunHooks<any, AgentOutputType<unknown>> {
477479

478480
let preparedInput: typeof input = input;
479481
if (!(preparedInput instanceof RunState)) {
482+
if (session && Array.isArray(preparedInput) && !sessionInputCallback) {
483+
throw new UserError(
484+
'RunConfig.sessionInputCallback must be provided when using session history with list inputs.',
485+
);
486+
}
487+
480488
const prepared = await prepareInputItemsWithSession(
481489
preparedInput,
482490
session,
@@ -602,8 +610,10 @@ export class Runner extends RunHooks<any, AgentOutputType<unknown>> {
602610
agent: Agent<TContext, AgentOutputType>,
603611
): Promise<{ model: Model; explictlyModelSet: boolean }> {
604612
const explictlyModelSet =
605-
(agent.model !== undefined && agent.model !== '') ||
606-
(this.config.model !== undefined && this.config.model !== '');
613+
(agent.model !== undefined &&
614+
agent.model !== Agent.DEFAULT_MODEL_PLACEHOLDER) ||
615+
(this.config.model !== undefined &&
616+
this.config.model !== Agent.DEFAULT_MODEL_PLACEHOLDER);
607617
let resolvedModel = selectModel(agent.model, this.config.model);
608618
if (typeof resolvedModel === 'string') {
609619
resolvedModel = await this.config.modelProvider.getModel(resolvedModel);
@@ -1518,7 +1528,7 @@ export function selectModel(
15181528
runConfigModel: string | Model | undefined,
15191529
): string | Model {
15201530
// When initializing an agent without model name, the model property is set to an empty string. So,
1521-
// * agentModel === '' & runConfigModel exists, runConfigModel will be used
1531+
// * agentModel === Agent.DEFAULT_MODEL_PLACEHOLDER & runConfigModel exists, runConfigModel will be used
15221532
// * agentModel is set, the agentModel will be used over runConfigModel
15231533
if (
15241534
(typeof agentModel === 'string' &&

packages/agents-core/src/runImplementation.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,8 @@ export function extractOutputItemsFromRunItems(
17051705
.map((item) => item.rawItem as AgentInputItem);
17061706
}
17071707

1708+
// Carries metadata while recursively sanitizing nested payloads so binary blobs can share the
1709+
// appropriate media type when converted into durable data URLs.
17081710
type SessionBinaryContext = {
17091711
mediaType?: string;
17101712
};
@@ -1923,20 +1925,6 @@ export async function prepareInputItemsWithSession(
19231925
? [...input]
19241926
: toInputItemList(input);
19251927

1926-
// When callers hand us pre-expanded AgentInputItems we cannot guess how to merge them with
1927-
// previously persisted history without risking duplicate tool outputs or approvals. Align with
1928-
// the Python SDK by requiring an explicit callback in that scenario so the merge strategy stays
1929-
// intentional and predictable.
1930-
if (
1931-
Array.isArray(input) &&
1932-
!sessionInputCallback &&
1933-
includeHistoryInPreparedInput
1934-
) {
1935-
throw new UserError(
1936-
'When using session memory, list inputs require a RunConfig.sessionInputCallback to define how history and new items are merged. Provide a string input instead or disable session memory to manage items manually.',
1937-
);
1938-
}
1939-
19401928
if (!sessionInputCallback) {
19411929
return {
19421930
preparedInput: includeHistoryInPreparedInput

packages/agents-core/test/runImplementation.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,47 @@ describe('prepareInputItemsWithSession', () => {
475475
async clearSession(): Promise<void> {}
476476
}
477477

478+
it('concatenates session history with array inputs when no callback is provided', async () => {
479+
const historyItem: AgentInputItem = {
480+
type: 'message',
481+
role: 'user',
482+
content: 'history',
483+
id: 'history-1',
484+
};
485+
const newItems: AgentInputItem[] = [
486+
{
487+
type: 'message',
488+
role: 'user',
489+
content: 'fresh text',
490+
id: 'new-1',
491+
},
492+
{
493+
type: 'function_call_result',
494+
name: 'foo-func',
495+
callId: 'new-2',
496+
output: [
497+
{
498+
type: 'input_image',
499+
image: 'https://example.com/image.png',
500+
},
501+
],
502+
status: 'completed',
503+
},
504+
];
505+
const session = new StubSession([historyItem]);
506+
507+
const result = await prepareInputItemsWithSession(newItems, session);
508+
509+
expect(result.preparedInput).toEqual([historyItem, ...newItems]);
510+
const sessionItems = result.sessionItems;
511+
if (!sessionItems) {
512+
throw new Error('Expected sessionItems to be defined.');
513+
}
514+
expect(sessionItems).toEqual(newItems);
515+
expect(sessionItems[0]).toBe(newItems[0]);
516+
expect(sessionItems[1]).toBe(newItems[1]);
517+
});
518+
478519
it('only persists new inputs when callbacks prepend history duplicates', async () => {
479520
const historyItem: AgentInputItem = {
480521
type: 'message',

packages/agents-openai/src/memory/openaiConversationsSession.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export class OpenAIConversationsSession implements Session {
4848

4949
async getItems(limit?: number): Promise<AgentInputItem[]> {
5050
const conversationId = await this.getSessionId();
51+
// Convert each API item into the Agent SDK's input shape. Some API payloads expand into multiple items.
5152
const toAgentItems = (item: APIConversationItem): AgentInputItem[] => {
5253
if (item.type === 'message' && item.role === 'user') {
5354
const message = item as APIConversationMessage;
@@ -130,7 +131,7 @@ export class OpenAIConversationsSession implements Session {
130131
return [];
131132
}
132133

133-
const groups: AgentInputItem[][] = [];
134+
const itemGroups: AgentInputItem[][] = [];
134135
let total = 0;
135136
const iterator = this.#client.conversations.items.list(conversationId, {
136137
limit,
@@ -143,24 +144,25 @@ export class OpenAIConversationsSession implements Session {
143144
continue;
144145
}
145146

146-
groups.push(group);
147+
itemGroups.push(group);
147148
total += group.length;
148149

149150
if (total >= limit) {
150151
break;
151152
}
152153
}
153154

154-
const flattened: AgentInputItem[] = [];
155-
for (let index = groups.length - 1; index >= 0; index -= 1) {
156-
flattened.push(...groups[index]);
155+
// Iterate in reverse because the API returned items in descending order.
156+
const orderedItems: AgentInputItem[] = [];
157+
for (let index = itemGroups.length - 1; index >= 0; index -= 1) {
158+
orderedItems.push(...itemGroups[index]);
157159
}
158160

159-
if (flattened.length > limit) {
160-
flattened.splice(0, flattened.length - limit);
161+
if (orderedItems.length > limit) {
162+
orderedItems.splice(0, orderedItems.length - limit);
161163
}
162164

163-
return flattened;
165+
return orderedItems;
164166
}
165167

166168
async addItems(items: AgentInputItem[]): Promise<void> {

0 commit comments

Comments
 (0)