1919
2020import { defineObjectShape , hasExactShape , isRecord } from './record-schema.js' ;
2121
22- export const RUN_COMPOSITION_SCHEMA_VERSION = 1 as const ;
22+ export const RUN_COMPOSITION_SCHEMA_VERSION = 2 as const ;
23+ export const REQUEST_COMPOSITION_SCHEMA_VERSION = 1 as const ;
2324
2425export interface RunCompositionSourceRevision {
2526 readonly id : string ;
@@ -30,30 +31,60 @@ export interface RunCompositionSnapshot {
3031 readonly schemaVersion : typeof RUN_COMPOSITION_SCHEMA_VERSION ;
3132 readonly composerId : string ;
3233 readonly composerRevision : string ;
34+ readonly baseProviderOptionsHash : `sha256:${string } `;
35+ readonly contextWindow : number | null ;
36+ }
37+
38+ export interface RequestCompositionToolSchema {
39+ readonly name : string ;
40+ readonly description : string ;
41+ readonly inputSchema : Record < string , unknown > ;
42+ readonly providerTool ?: Record < string , unknown > ;
43+ }
44+
45+ /**
46+ * One model-visible request surface, frozen at a logical model-step boundary.
47+ * A later step appends a new snapshot only when one of these effective fields
48+ * changes; physical retries of the same step keep referring to this snapshot.
49+ */
50+ export interface RequestCompositionSnapshot {
51+ readonly schemaVersion : typeof REQUEST_COMPOSITION_SCHEMA_VERSION ;
52+ readonly compositionId : string ;
53+ readonly step : number ;
54+ readonly reason : 'initial' | 'change' ;
3355 readonly sourceRevisions : readonly RunCompositionSourceRevision [ ] ;
34- readonly baseSystemPromptHash : `sha256:${string } `;
56+ readonly systemPromptHash : `sha256:${string } `;
3557 readonly toolCatalogHash : `sha256:${string } `;
3658 readonly toolAvailabilityHash : `sha256:${string } `;
37- readonly baseProviderOptionsHash : `sha256:${string } `;
59+ readonly providerOptionsHash : `sha256:${string } `;
3860 readonly toolNames : readonly string [ ] ;
39- readonly contextWindow : number | null ;
61+ readonly toolSchemas : readonly RequestCompositionToolSchema [ ] ;
4062}
4163
4264const RUN_COMPOSITION_SHAPE = defineObjectShape < RunCompositionSnapshot > ( ) (
65+ [ 'schemaVersion' , 'composerId' , 'composerRevision' , 'baseProviderOptionsHash' , 'contextWindow' ] ,
66+ [ ] ,
67+ ) ;
68+ const REQUEST_COMPOSITION_SHAPE = defineObjectShape < RequestCompositionSnapshot > ( ) (
4369 [
4470 'schemaVersion' ,
45- 'composerId' ,
46- 'composerRevision' ,
71+ 'compositionId' ,
72+ 'step' ,
73+ 'reason' ,
4774 'sourceRevisions' ,
48- 'baseSystemPromptHash ' ,
75+ 'systemPromptHash ' ,
4976 'toolCatalogHash' ,
5077 'toolAvailabilityHash' ,
51- 'baseProviderOptionsHash ' ,
78+ 'providerOptionsHash ' ,
5279 'toolNames' ,
53- 'contextWindow ' ,
80+ 'toolSchemas ' ,
5481 ] ,
5582 [ ] ,
5683) ;
84+ const REQUEST_COMPOSITION_TOOL_SCHEMA_SHAPE = defineObjectShape < RequestCompositionToolSchema > ( ) (
85+ [ 'name' , 'description' , 'inputSchema' ] ,
86+ [ 'providerTool' ] ,
87+ ) ;
5788
5889const ID_PATTERN = / ^ [ a - z ] [ a - z 0 - 9 ] * (?: [ . - ] [ a - z 0 - 9 ] + ) * $ / ;
5990const HASH_PATTERN = / ^ s h a 2 5 6 : [ a - f 0 - 9 ] { 64 } $ / ;
@@ -70,23 +101,12 @@ export function decodeRunCompositionSnapshot(value: unknown): RunCompositionSnap
70101 value . schemaVersion === RUN_COMPOSITION_SCHEMA_VERSION &&
71102 boundedMatchingString ( value . composerId , ID_PATTERN , 128 ) &&
72103 boundedString ( value . composerRevision , 128 ) &&
73- canonicalSourceRevisions ( value . sourceRevisions ) &&
74- hash ( value . baseSystemPromptHash ) &&
75- hash ( value . toolCatalogHash ) &&
76- hash ( value . toolAvailabilityHash ) &&
77104 hash ( value . baseProviderOptionsHash ) &&
78- canonicalToolNames ( value . toolNames ) &&
79105 ( value . contextWindow === null ||
80106 ( Number . isSafeInteger ( value . contextWindow ) && ( value . contextWindow as number ) > 0 ) ) ;
81107 if ( ! valid ) throw new Error ( 'Invalid Run Composition snapshot schema' ) ;
82108 return Object . freeze ( {
83109 ...( value as unknown as RunCompositionSnapshot ) ,
84- sourceRevisions : Object . freeze (
85- ( value . sourceRevisions as RunCompositionSourceRevision [ ] ) . map ( ( source ) =>
86- Object . freeze ( { ...source } ) ,
87- ) ,
88- ) ,
89- toolNames : Object . freeze ( [ ...( value . toolNames as string [ ] ) ] ) ,
90110 } ) ;
91111}
92112
@@ -98,10 +118,63 @@ export function createRunCompositionSnapshot(
98118 return decodeRunCompositionSnapshot ( {
99119 schemaVersion : RUN_COMPOSITION_SCHEMA_VERSION ,
100120 ...input ,
121+ } ) ;
122+ }
123+
124+ export type RequestCompositionSnapshotInput = Omit <
125+ RequestCompositionSnapshot ,
126+ 'schemaVersion' | 'reason'
127+ > ;
128+
129+ export function createRequestCompositionSnapshot (
130+ input : RequestCompositionSnapshotInput ,
131+ reason : RequestCompositionSnapshot [ 'reason' ] ,
132+ ) : RequestCompositionSnapshot {
133+ return decodeRequestCompositionSnapshot ( {
134+ schemaVersion : REQUEST_COMPOSITION_SCHEMA_VERSION ,
135+ ...input ,
136+ reason,
101137 sourceRevisions : [ ...input . sourceRevisions ] . sort ( ( left , right ) =>
102138 compareExactString ( left . id , right . id ) ,
103139 ) ,
104140 toolNames : [ ...input . toolNames ] . sort ( compareExactString ) ,
141+ toolSchemas : [ ...input . toolSchemas ] . sort ( ( left , right ) =>
142+ compareExactString ( left . name , right . name ) ,
143+ ) ,
144+ } ) ;
145+ }
146+
147+ export function decodeRequestCompositionSnapshot ( value : unknown ) : RequestCompositionSnapshot {
148+ if ( ! isRecord ( value ) || ! hasExactShape ( value , REQUEST_COMPOSITION_SHAPE ) ) {
149+ throw new Error ( 'Invalid Request Composition snapshot schema' ) ;
150+ }
151+ const valid =
152+ value . schemaVersion === REQUEST_COMPOSITION_SCHEMA_VERSION &&
153+ boundedString ( value . compositionId , 128 ) &&
154+ Number . isSafeInteger ( value . step ) &&
155+ ( value . step as number ) >= 0 &&
156+ ( value . reason === 'initial' || value . reason === 'change' ) &&
157+ canonicalSourceRevisions ( value . sourceRevisions ) &&
158+ hash ( value . systemPromptHash ) &&
159+ hash ( value . toolCatalogHash ) &&
160+ hash ( value . toolAvailabilityHash ) &&
161+ hash ( value . providerOptionsHash ) &&
162+ canonicalToolNames ( value . toolNames ) &&
163+ canonicalToolSchemas ( value . toolSchemas ) ;
164+ if ( ! valid ) throw new Error ( 'Invalid Request Composition snapshot schema' ) ;
165+ return Object . freeze ( {
166+ ...( value as unknown as RequestCompositionSnapshot ) ,
167+ sourceRevisions : Object . freeze (
168+ ( value . sourceRevisions as RunCompositionSourceRevision [ ] ) . map ( ( source ) =>
169+ Object . freeze ( { ...source } ) ,
170+ ) ,
171+ ) ,
172+ toolNames : Object . freeze ( [ ...( value . toolNames as string [ ] ) ] ) ,
173+ toolSchemas : Object . freeze (
174+ ( value . toolSchemas as RequestCompositionToolSchema [ ] ) . map ( ( schema ) =>
175+ Object . freeze ( structuredClone ( schema ) ) ,
176+ ) ,
177+ ) ,
105178 } ) ;
106179}
107180
@@ -137,6 +210,26 @@ function canonicalToolNames(value: unknown): value is string[] {
137210 return true ;
138211}
139212
213+ function canonicalToolSchemas ( value : unknown ) : value is RequestCompositionToolSchema [ ] {
214+ if ( ! Array . isArray ( value ) || value . length > 512 ) return false ;
215+ let previous : string | undefined ;
216+ for ( const schema of value ) {
217+ if (
218+ ! isRecord ( schema ) ||
219+ ! hasExactShape ( schema , REQUEST_COMPOSITION_TOOL_SCHEMA_SHAPE ) ||
220+ ! boundedString ( schema . name , 256 ) ||
221+ ! boundedString ( schema . description , 16_384 ) ||
222+ ! isRecord ( schema . inputSchema ) ||
223+ ( schema . providerTool !== undefined && ! isRecord ( schema . providerTool ) ) ||
224+ ( previous !== undefined && previous >= schema . name )
225+ ) {
226+ return false ;
227+ }
228+ previous = schema . name ;
229+ }
230+ return true ;
231+ }
232+
140233function hash ( value : unknown ) : value is `sha256:${string } ` {
141234 return typeof value === 'string' && HASH_PATTERN . test ( value ) ;
142235}
0 commit comments