@myagentroam/agent 0.10.7 → 0.10.9
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/dist/agent-core/extensions/compaction.d.ts +1 -0
- package/dist/agent-core/extensions/model-request.js +1 -1
- package/dist/agent-core/extensions/session-messages.d.ts +1 -1
- package/dist/agent-core/host/contracts.d.ts +2 -0
- package/dist/agent-core/host/local-agent-host.d.ts +2 -0
- package/dist/agent-core/host/local-agent-host.js +2 -0
- package/dist/agent-core/model/anthropic-messages.js +15 -23
- package/dist/agent-core/model/catalog-file.d.ts +14 -0
- package/dist/agent-core/model/configuration.d.ts +7 -0
- package/dist/agent-core/model/configuration.js +10 -0
- package/dist/agent-core/model/contracts.d.ts +8 -0
- package/dist/agent-core/model/openai-responses.d.ts +1 -1
- package/dist/agent-core/model/openai-responses.js +62 -29
- package/dist/agent-core/model/runtime-credential.d.ts +2 -0
- package/dist/agent-core/model/runtime-credential.js +15 -0
- package/dist/agent-core/model/sse.d.ts +1 -1
- package/dist/agent-core/model/sse.js +22 -4
- package/dist/agent-core/model/tool-input.d.ts +3 -0
- package/dist/agent-core/model/tool-input.js +18 -0
- package/dist/agent-core/model/usage-observer.d.ts +31 -0
- package/dist/agent-core/model/usage-observer.js +82 -0
- package/dist/agent-core/runtime/events.d.ts +2 -0
- package/dist/agent-core/sdk/agent.js +9 -1
- package/dist/agent-core/sdk/extension-models.d.ts +1 -1
- package/dist/agent-core/sdk/extension-models.js +9 -1
- package/dist/agent-core/sdk/session.js +31 -3
- package/dist/agent-core/session/catalog.d.ts +1 -0
- package/dist/agent-core/session/catalog.js +1 -0
- package/dist/agent-core/session/jsonl-store.d.ts +9 -0
- package/dist/agent-core/session/jsonl-store.js +24 -0
- package/dist/agent-core/session/tool-result-resources.d.ts +26 -0
- package/dist/agent-core/session/tool-result-resources.js +285 -0
- package/dist/agent-core/session/transaction-recovery.js +1 -1
- package/dist/agent-core/tools/code-mode/dispatch.js +5 -0
- package/dist/agent-core/tools/code-mode/index.js +8 -2
- package/dist/agent-core/tools/code-mode/observations.d.ts +1 -0
- package/dist/agent-core/tools/contracts.d.ts +2 -0
- package/dist/agent-core/tools/execution.js +11 -2
- package/dist/agent-core/tools/file-change-diff.d.ts +10 -0
- package/dist/agent-core/tools/file-change-diff.js +139 -0
- package/dist/agent-core/tools/local/apply-patch.js +19 -8
- package/dist/agent-core/tools/result-extensions.d.ts +46 -0
- package/dist/agent-core/tools/result-extensions.js +134 -0
- package/dist/agent-core/tools/runtime-context.d.ts +1 -0
- package/dist/agent-core/tools/runtime.js +6 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -0
- package/dist/shared/auto-title.d.ts +2 -0
- package/dist/shared/auto-title.js +6 -0
- package/dist/shared/model-text.d.ts +2 -0
- package/dist/shared/model-text.js +6 -0
- package/package.json +1 -1
|
@@ -19,6 +19,7 @@ export interface CoreCompactMessage {
|
|
|
19
19
|
readonly arguments?: unknown;
|
|
20
20
|
}
|
|
21
21
|
export interface CoreCompactContext {
|
|
22
|
+
readonly reportModelUsage?: ((usage: import('../model/usage-observer.js').ModelRequestUsage) => Promise<void> | void) | undefined;
|
|
22
23
|
readonly operationId: string;
|
|
23
24
|
readonly reason: 'manual' | 'automatic';
|
|
24
25
|
readonly messages: readonly CoreCompactMessage[];
|
|
@@ -9,7 +9,7 @@ export class ExtensionRequestSnapshots {
|
|
|
9
9
|
#current;
|
|
10
10
|
#completed;
|
|
11
11
|
record(modelId, request) {
|
|
12
|
-
const { onAttemptDiagnostic: _diagnostic, ...serializable } = request;
|
|
12
|
+
const { onAttemptDiagnostic: _diagnostic, onModelUsage: _usage, onModelUsageFailure: _usageFailure, usageRequestId: _usageRequestId, ...serializable } = request;
|
|
13
13
|
const retained = structuredClone(serializable);
|
|
14
14
|
let view;
|
|
15
15
|
// Preserve dispatch-time isolation without projecting unused public views.
|
|
@@ -29,7 +29,7 @@ export interface CoreSessionMessages {
|
|
|
29
29
|
readonly nextCursor?: string;
|
|
30
30
|
}
|
|
31
31
|
export declare function normalizeSessionMessagesInput(input: CoreSessionMessagesInput): Readonly<{
|
|
32
|
-
view: "model" | "
|
|
32
|
+
view: "model" | "conversation" | "request";
|
|
33
33
|
agentId: string | undefined;
|
|
34
34
|
includeImages: boolean;
|
|
35
35
|
cursor: number;
|
|
@@ -53,6 +53,8 @@ export interface HostSessionResourceSnapshot {
|
|
|
53
53
|
}
|
|
54
54
|
/** The Host owns workspace/tool effects; the SDK owns JSONL storage beneath its supplied HOME. */
|
|
55
55
|
export interface MarAgentHost {
|
|
56
|
+
/** Independent model consumption facts, including child and auxiliary calls. */
|
|
57
|
+
modelUsage?: import('../model/usage-observer.js').ModelUsageObserver | undefined;
|
|
56
58
|
readonly extensionPorts?: CoreHostExtensionPorts;
|
|
57
59
|
describe(): Promise<HostDescription>;
|
|
58
60
|
executeTool(call: ToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
|
|
@@ -23,6 +23,7 @@ export interface LocalAgentExecutionExtensions extends HostExecutionExtensions {
|
|
|
23
23
|
}
|
|
24
24
|
export declare class LocalAgentHost implements MarAgentHost {
|
|
25
25
|
#private;
|
|
26
|
+
readonly modelUsage: import('../model/usage-observer.js').ModelUsageObserver | undefined;
|
|
26
27
|
readonly extensionPorts: import('./contracts.js').CoreHostExtensionPorts;
|
|
27
28
|
readonly events: MarAgentEvent[];
|
|
28
29
|
constructor(input: {
|
|
@@ -31,6 +32,7 @@ export declare class LocalAgentHost implements MarAgentHost {
|
|
|
31
32
|
beforeOperationToolExecute?: (input: ToolCall, call: CoreOperationContext) => Promise<void> | void;
|
|
32
33
|
callMcpOperation?: (input: ToolCall, call: CoreOperationContext, expected: NonNullable<McpToolCallContext['expectedTool']>) => Promise<ToolExecutionResult>;
|
|
33
34
|
network?: CoreNetworkBackend | false;
|
|
35
|
+
modelUsage?: import('../model/usage-observer.js').ModelUsageObserver;
|
|
34
36
|
homeDirectory: string;
|
|
35
37
|
workspace: string;
|
|
36
38
|
mode?: 'normal' | 'node';
|
|
@@ -12,6 +12,7 @@ import { directLocalProcessExecutor } from './local-process-executor.js';
|
|
|
12
12
|
import { BUILTIN_TOOL_DEFINITIONS } from '../tools/registry.js';
|
|
13
13
|
import { assertMcpToolCatalog } from '../tools/mcp/catalog.js';
|
|
14
14
|
export class LocalAgentHost {
|
|
15
|
+
modelUsage;
|
|
15
16
|
extensionPorts;
|
|
16
17
|
#home;
|
|
17
18
|
#workspace;
|
|
@@ -37,6 +38,7 @@ export class LocalAgentHost {
|
|
|
37
38
|
#shell;
|
|
38
39
|
#resourceIdleTtlMs;
|
|
39
40
|
constructor(input) {
|
|
41
|
+
this.modelUsage = input.modelUsage;
|
|
40
42
|
this.#home = resolve(input.homeDirectory);
|
|
41
43
|
this.#workspace = resolve(input.workspace);
|
|
42
44
|
this.#environment = input.environment ?? process.env;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { observeModelRequestUsage } from './usage-observer.js';
|
|
2
|
+
import { jsonToolInput } from './tool-input.js';
|
|
1
3
|
import { observationModelParts } from '../tools/code-mode/observations.js';
|
|
2
4
|
import { stableToolSchema } from '../tools/definition-projection.js';
|
|
3
5
|
import { randomUUID } from 'node:crypto';
|
|
@@ -38,7 +40,7 @@ export class AnthropicMessagesAdapter {
|
|
|
38
40
|
let firstSemanticEventType;
|
|
39
41
|
await reportModelAttempt(request, { ...attemptDiagnostic, state: 'STARTED' });
|
|
40
42
|
try {
|
|
41
|
-
for await (const event of this.startAttempt(request, signal)) {
|
|
43
|
+
for await (const event of observeModelRequestUsage(() => this.startAttempt(request, signal), request, this.configuration, signal)) {
|
|
42
44
|
if (event.type.startsWith('output.')) {
|
|
43
45
|
resetOutputTiming = true;
|
|
44
46
|
yield event;
|
|
@@ -143,14 +145,18 @@ export class AnthropicMessagesAdapter {
|
|
|
143
145
|
let terminal;
|
|
144
146
|
let latestInputUsage;
|
|
145
147
|
let thinkingBytes = 0;
|
|
146
|
-
for await (const raw of parseAnthropicSse(response, signal)) {
|
|
148
|
+
for await (const raw of parseAnthropicSse(response, signal, apiKey.reveal())) {
|
|
147
149
|
const event = record(raw);
|
|
148
150
|
const type = text(event.type) ?? 'unknown';
|
|
149
151
|
const index = number(event.index) ?? -1;
|
|
150
152
|
if (type === 'message_start') {
|
|
151
153
|
const message = record(event.message);
|
|
152
154
|
responseId = text(message.id) ?? responseId;
|
|
153
|
-
yield observeModelStreamEvent({
|
|
155
|
+
yield observeModelStreamEvent({
|
|
156
|
+
type: 'response.started',
|
|
157
|
+
responseId,
|
|
158
|
+
...(text(message.model) ? { modelId: text(message.model) } : {})
|
|
159
|
+
}, modelStreamEventObservedAt(raw));
|
|
154
160
|
const usage = record(message.usage);
|
|
155
161
|
const inputTokens = inputTokenCount(usage.input_tokens);
|
|
156
162
|
const cacheReadTokens = inputTokenCount(usage.cache_read_input_tokens);
|
|
@@ -419,9 +425,9 @@ export class AnthropicMessagesAdapter {
|
|
|
419
425
|
});
|
|
420
426
|
}
|
|
421
427
|
}
|
|
422
|
-
async function* parseAnthropicSse(response, signal) {
|
|
428
|
+
async function* parseAnthropicSse(response, signal, apiKey) {
|
|
423
429
|
try {
|
|
424
|
-
yield* parseSse(response, signal);
|
|
430
|
+
yield* parseSse(response, signal, undefined, undefined, [apiKey]);
|
|
425
431
|
}
|
|
426
432
|
catch (cause) {
|
|
427
433
|
if (response.ok &&
|
|
@@ -491,6 +497,9 @@ function inputUsage(inputTokens, cacheReadTokens, cacheWriteTokens) {
|
|
|
491
497
|
return {
|
|
492
498
|
inputTokens,
|
|
493
499
|
contextInputTokens: tokenSum(inputTokens, cacheReadTokens, cacheWriteTokens),
|
|
500
|
+
...(inputTokens === undefined
|
|
501
|
+
? {}
|
|
502
|
+
: { totalInputTokens: tokenSum(inputTokens, cacheReadTokens, cacheWriteTokens) }),
|
|
494
503
|
cacheReadTokens,
|
|
495
504
|
cacheWriteTokens
|
|
496
505
|
};
|
|
@@ -543,7 +552,7 @@ function anthropicMessages(messages, images, tools = [], modelId, automaticCache
|
|
|
543
552
|
type: 'tool_use',
|
|
544
553
|
id: message.callId,
|
|
545
554
|
name: message.name,
|
|
546
|
-
input:
|
|
555
|
+
input: jsonToolInput(message, toolDefinitions.get(message.name ?? ''))
|
|
547
556
|
};
|
|
548
557
|
const previous = result.at(-1);
|
|
549
558
|
if (previous?.role === 'assistant' && Array.isArray(previous.content))
|
|
@@ -665,23 +674,6 @@ function uniqueModelImages(...groups) {
|
|
|
665
674
|
return [image];
|
|
666
675
|
}));
|
|
667
676
|
}
|
|
668
|
-
function anthropicToolInput(message, tool) {
|
|
669
|
-
if (isObject(message.arguments))
|
|
670
|
-
return message.arguments;
|
|
671
|
-
if (message.inputMode !== 'freeform')
|
|
672
|
-
return {};
|
|
673
|
-
const property = freeformInputProperty(tool?.inputSchema);
|
|
674
|
-
return { [property ?? 'input']: String(message.arguments ?? '') };
|
|
675
|
-
}
|
|
676
|
-
function freeformInputProperty(schema) {
|
|
677
|
-
if (schema?.type !== 'object' || !isObject(schema.properties) || !Array.isArray(schema.required))
|
|
678
|
-
return undefined;
|
|
679
|
-
const required = schema.required.filter((value) => typeof value === 'string');
|
|
680
|
-
if (required.length !== 1)
|
|
681
|
-
return undefined;
|
|
682
|
-
const property = schema.properties[required[0]];
|
|
683
|
-
return isObject(property) && property.type === 'string' ? required[0] : undefined;
|
|
684
|
-
}
|
|
685
677
|
function isObject(value) {
|
|
686
678
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
687
679
|
}
|
|
@@ -32,6 +32,13 @@ export declare function serializeModel(model: CliModelConfiguration): {
|
|
|
32
32
|
serviceTier?: "default" | "auto" | "flex" | "priority" | "fast" | "ultrafast" | undefined;
|
|
33
33
|
hostedWebSearch: boolean;
|
|
34
34
|
responsesToolNamespace?: string | undefined;
|
|
35
|
+
responsesOptionalParameters?: {
|
|
36
|
+
reasoningEffort: boolean;
|
|
37
|
+
reasoningSummary: boolean;
|
|
38
|
+
textVerbosity: boolean;
|
|
39
|
+
promptCacheKey: boolean;
|
|
40
|
+
nativeTools?: boolean | undefined;
|
|
41
|
+
} | undefined;
|
|
35
42
|
enabled: boolean;
|
|
36
43
|
} | {
|
|
37
44
|
credential: {
|
|
@@ -58,5 +65,12 @@ export declare function serializeModel(model: CliModelConfiguration): {
|
|
|
58
65
|
serviceTier?: "default" | "auto" | "flex" | "priority" | "fast" | "ultrafast" | undefined;
|
|
59
66
|
hostedWebSearch: boolean;
|
|
60
67
|
responsesToolNamespace?: string | undefined;
|
|
68
|
+
responsesOptionalParameters?: {
|
|
69
|
+
reasoningEffort: boolean;
|
|
70
|
+
reasoningSummary: boolean;
|
|
71
|
+
textVerbosity: boolean;
|
|
72
|
+
promptCacheKey: boolean;
|
|
73
|
+
nativeTools?: boolean | undefined;
|
|
74
|
+
} | undefined;
|
|
61
75
|
enabled: boolean;
|
|
62
76
|
};
|
|
@@ -90,6 +90,13 @@ declare const rawModelSchema: z.ZodObject<{
|
|
|
90
90
|
}, z.core.$strict>], "transport">>;
|
|
91
91
|
responsesToolNamespace: z.ZodOptional<z.ZodString>;
|
|
92
92
|
responsesPreviousResponseId: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
93
|
+
responsesOptionalParameters: z.ZodOptional<z.ZodObject<{
|
|
94
|
+
reasoningEffort: z.ZodBoolean;
|
|
95
|
+
reasoningSummary: z.ZodBoolean;
|
|
96
|
+
textVerbosity: z.ZodBoolean;
|
|
97
|
+
promptCacheKey: z.ZodBoolean;
|
|
98
|
+
nativeTools: z.ZodOptional<z.ZodBoolean>;
|
|
99
|
+
}, z.core.$strict>>;
|
|
93
100
|
enabled: z.ZodBoolean;
|
|
94
101
|
}, z.core.$strict>;
|
|
95
102
|
export interface MarAgentModelConfiguration extends Omit<z.infer<typeof rawModelSchema>, 'apiKey' | 'responsesPreviousResponseId' | 'responsesTransport'> {
|
|
@@ -58,6 +58,16 @@ const rawModelSchema = z
|
|
|
58
58
|
.regex(/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/u)
|
|
59
59
|
.optional(),
|
|
60
60
|
responsesPreviousResponseId: z.boolean().optional().default(false),
|
|
61
|
+
responsesOptionalParameters: z
|
|
62
|
+
.object({
|
|
63
|
+
reasoningEffort: z.boolean(),
|
|
64
|
+
reasoningSummary: z.boolean(),
|
|
65
|
+
textVerbosity: z.boolean(),
|
|
66
|
+
promptCacheKey: z.boolean(),
|
|
67
|
+
nativeTools: z.boolean().optional()
|
|
68
|
+
})
|
|
69
|
+
.strict()
|
|
70
|
+
.optional(),
|
|
61
71
|
enabled: z.boolean()
|
|
62
72
|
})
|
|
63
73
|
.strict()
|
|
@@ -82,6 +82,10 @@ export interface ModelRetryState {
|
|
|
82
82
|
retryDelayMs?: number;
|
|
83
83
|
}
|
|
84
84
|
export interface ModelRequest {
|
|
85
|
+
/** Metering identity and sink are local only, never serialized upstream. */
|
|
86
|
+
usageRequestId?: string;
|
|
87
|
+
onModelUsage?: (usage: import('./usage-observer.js').ModelRequestUsage) => Promise<void> | void;
|
|
88
|
+
onModelUsageFailure?: () => void;
|
|
85
89
|
/** Shared local attempt accounting across Adapter retries and Runtime continuation. Never sent upstream. */
|
|
86
90
|
retryState?: ModelRetryState;
|
|
87
91
|
system: string;
|
|
@@ -106,6 +110,9 @@ export interface ModelRequest {
|
|
|
106
110
|
onAttemptDiagnostic?: (diagnostic: ModelAttemptDiagnostic) => Promise<void> | void;
|
|
107
111
|
}
|
|
108
112
|
export interface ModelUsage {
|
|
113
|
+
/** Normalized total input including cache; distinct from protocol-native inputTokens. */
|
|
114
|
+
totalInputTokens?: number | undefined;
|
|
115
|
+
reasoningTokens?: number | undefined;
|
|
109
116
|
inputTokens?: number | undefined;
|
|
110
117
|
contextInputTokens?: number | undefined;
|
|
111
118
|
outputTokens?: number | undefined;
|
|
@@ -119,6 +126,7 @@ export type ModelStopReason = 'completed' | 'tool_use' | 'pause' | 'max_output'
|
|
|
119
126
|
export type ModelEvent = {
|
|
120
127
|
type: 'response.started';
|
|
121
128
|
responseId: string;
|
|
129
|
+
modelId?: string;
|
|
122
130
|
} | {
|
|
123
131
|
type: 'output.started';
|
|
124
132
|
} | {
|
|
@@ -68,5 +68,5 @@ declare class OpenAiResponsesTurnSession implements ModelTurnSession {
|
|
|
68
68
|
private connectWebSocket;
|
|
69
69
|
}
|
|
70
70
|
export declare function isOpenAiResponsesRecoverableStreamError(error: unknown): error is MarAgentError;
|
|
71
|
-
export declare function openAiInput(messages: ModelRequest['messages'], images: ModelRequest['images'] | undefined, modelId: string, requireEncryptedReasoning: boolean, configurationUpdates?: boolean): Record<string, unknown>[];
|
|
71
|
+
export declare function openAiInput(messages: ModelRequest['messages'], images: ModelRequest['images'] | undefined, modelId: string, requireEncryptedReasoning: boolean, configurationUpdates?: boolean, nativeTools?: boolean, tools?: ModelRequest['tools']): Record<string, unknown>[];
|
|
72
72
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { observeModelRequestUsage } from './usage-observer.js';
|
|
1
2
|
import { observationModelParts } from '../tools/code-mode/observations.js';
|
|
2
3
|
import { stableToolSchema } from '../tools/definition-projection.js';
|
|
3
4
|
import { isDeepStrictEqual } from 'node:util';
|
|
@@ -7,8 +8,9 @@ import { marAgentReasoningEffortSchema, resolveModelDefaultReasoningEffort, uses
|
|
|
7
8
|
import WebSocket from 'ws';
|
|
8
9
|
import { isRetryableUpstreamOverloadCode, number, parseSse, record, text, observeModelStreamEvent, modelStreamEventObservedAt } from './sse.js';
|
|
9
10
|
import { connectSocks5Target, fetchModelResponse, isRetryableModelHttpStatus, isRetryableModelTransportError, modelRetryMaxAttempts, isRetryableModelRequestError, modelRetryDelay, waitForModelRetry } from './http.js';
|
|
10
|
-
import { acquireModelCredential, appendModelCredentialHeaders } from './runtime-credential.js';
|
|
11
|
+
import { acquireModelCredential, appendModelCredentialHeaders, modelCredentialSensitiveValues } from './runtime-credential.js';
|
|
11
12
|
import { resolveModelTextBlocks } from './text-blocks.js';
|
|
13
|
+
import { jsonToolInput } from './tool-input.js';
|
|
12
14
|
import { reportModelAttempt } from './attempt-diagnostics.js';
|
|
13
15
|
const WEBSOCKET_IDLE_TIMEOUT_MS = 300_000;
|
|
14
16
|
const WEBSOCKET_SEND_FRAME_BYTES = 1024 * 1024;
|
|
@@ -114,7 +116,7 @@ export class OpenAiResponsesAdapter {
|
|
|
114
116
|
...requestPrefixDiagnostic
|
|
115
117
|
});
|
|
116
118
|
try {
|
|
117
|
-
for await (const event of this.startAttempt(request, signal, requestBody, useContinuation, credentialReason, turn)) {
|
|
119
|
+
for await (const event of observeModelRequestUsage(() => this.startAttempt(request, signal, requestBody, useContinuation, credentialReason, turn), request, this.configuration, signal)) {
|
|
118
120
|
if (event.type.startsWith('output.')) {
|
|
119
121
|
resetOutputTiming = true;
|
|
120
122
|
yield event;
|
|
@@ -252,11 +254,13 @@ export class OpenAiResponsesAdapter {
|
|
|
252
254
|
}
|
|
253
255
|
async *startAttempt(request, signal, body, continuation, credentialReason, turn) {
|
|
254
256
|
let latestCredentialRevision;
|
|
257
|
+
let diagnosticSecrets = [];
|
|
255
258
|
const fetchResponse = async (requestBody) => {
|
|
256
259
|
const serializedBody = JSON.stringify(requestBody);
|
|
257
260
|
return await fetchModelResponse(responsesEndpoint(this.configuration.baseUrl), async () => {
|
|
258
261
|
const credential = await acquireResponsesCredential(this.configuration, credentialReason, signal);
|
|
259
262
|
latestCredentialRevision = credential.revision;
|
|
263
|
+
diagnosticSecrets = modelCredentialSensitiveValues(credential);
|
|
260
264
|
return {
|
|
261
265
|
request: {
|
|
262
266
|
method: 'POST',
|
|
@@ -290,7 +294,7 @@ export class OpenAiResponsesAdapter {
|
|
|
290
294
|
else {
|
|
291
295
|
response = await fetchResponse(body);
|
|
292
296
|
turn.captureTurnState(response.headers.get(X_CODEX_TURN_STATE));
|
|
293
|
-
rawEvents = parseOpenAiResponsesSse(response, signal, continuation);
|
|
297
|
+
rawEvents = parseOpenAiResponsesSse(response, signal, continuation, diagnosticSecrets);
|
|
294
298
|
}
|
|
295
299
|
}
|
|
296
300
|
catch (error) {
|
|
@@ -383,7 +387,10 @@ export class OpenAiResponsesAdapter {
|
|
|
383
387
|
else if (type === 'response.created')
|
|
384
388
|
yield observeModelStreamEvent({
|
|
385
389
|
type: 'response.started',
|
|
386
|
-
responseId: responseEventId(event) ?? 'unknown'
|
|
390
|
+
responseId: responseEventId(event) ?? 'unknown',
|
|
391
|
+
...(text(record(event.response).model)
|
|
392
|
+
? { modelId: text(record(event.response).model) }
|
|
393
|
+
: {})
|
|
387
394
|
}, modelStreamEventObservedAt(raw));
|
|
388
395
|
else if (type === 'response.output_text.delta')
|
|
389
396
|
yield observeModelStreamEvent({
|
|
@@ -564,6 +571,8 @@ export class OpenAiResponsesAdapter {
|
|
|
564
571
|
final: true,
|
|
565
572
|
usage: {
|
|
566
573
|
inputTokens: number(usage.input_tokens),
|
|
574
|
+
totalInputTokens: number(usage.input_tokens),
|
|
575
|
+
reasoningTokens: number(record(usage.output_tokens_details).reasoning_tokens),
|
|
567
576
|
contextInputTokens: number(usage.input_tokens),
|
|
568
577
|
outputTokens: number(usage.output_tokens),
|
|
569
578
|
cacheReadTokens: number(inputDetails.cached_tokens),
|
|
@@ -891,14 +900,15 @@ function responsesRequestBody(configuration, request, messages) {
|
|
|
891
900
|
throw new MarAgentError('MAR_AGENT_MODEL_CAPABILITY_UNAVAILABLE', 'This model protocol does not support explicit text-block cache markers.');
|
|
892
901
|
const tools = request.allowTools === false
|
|
893
902
|
? []
|
|
894
|
-
: request.tools.map((tool) => tool.nativeType === 'tool_search'
|
|
903
|
+
: request.tools.map((tool) => tool.nativeType === 'tool_search' &&
|
|
904
|
+
configuration.responsesOptionalParameters?.nativeTools !== false
|
|
895
905
|
? {
|
|
896
906
|
type: 'tool_search',
|
|
897
907
|
execution: 'client',
|
|
898
908
|
description: tool.description,
|
|
899
909
|
parameters: stableToolSchema(strictFunctionSchema(stableToolSchema(tool.inputSchema)))
|
|
900
910
|
}
|
|
901
|
-
: responsesFunctionTool(tool));
|
|
911
|
+
: responsesFunctionTool(tool, configuration.responsesOptionalParameters?.nativeTools !== false));
|
|
902
912
|
if (configuration.responsesToolNamespace && tools.length > 0) {
|
|
903
913
|
const native = tools.filter((tool) => record(tool).type === 'tool_search');
|
|
904
914
|
const nested = tools.filter((tool) => record(tool).type !== 'tool_search');
|
|
@@ -914,7 +924,7 @@ function responsesRequestBody(configuration, request, messages) {
|
|
|
914
924
|
if (request.allowTools !== false && configuration.hostedWebSearch)
|
|
915
925
|
tools.push({ type: 'web_search' });
|
|
916
926
|
const toolChoice = request.toolChoice ?? 'auto';
|
|
917
|
-
const input = openAiInput(messages, request.images, configuration.id, true, usesResponsesConfigurationUpdates(configuration));
|
|
927
|
+
const input = openAiInput(messages, request.images, configuration.id, true, usesResponsesConfigurationUpdates(configuration), configuration.responsesOptionalParameters?.nativeTools !== false, request.tools);
|
|
918
928
|
if (request.systemBlocks !== undefined && (request.systemBlocks.length || request.system.length))
|
|
919
929
|
input.unshift({
|
|
920
930
|
role: 'developer',
|
|
@@ -937,25 +947,40 @@ function responsesRequestBody(configuration, request, messages) {
|
|
|
937
947
|
: {}),
|
|
938
948
|
instructions: request.systemBlocks === undefined ? request.system : '',
|
|
939
949
|
tools,
|
|
940
|
-
tool_choice: toolChoice,
|
|
950
|
+
...(tools.length > 0 ? { tool_choice: toolChoice, parallel_tool_calls: true } : {}),
|
|
941
951
|
input,
|
|
942
952
|
stream: true,
|
|
943
|
-
parallel_tool_calls: true,
|
|
944
953
|
store: false,
|
|
945
|
-
|
|
954
|
+
...(configuration.responsesOptionalParameters?.textVerbosity === false
|
|
955
|
+
? {}
|
|
956
|
+
: { text: { verbosity: 'low' } }),
|
|
946
957
|
include: [
|
|
947
958
|
'reasoning.encrypted_content',
|
|
948
959
|
...(configuration.hostedWebSearch && request.allowTools !== false && toolChoice !== 'none'
|
|
949
960
|
? ['web_search_call.action.sources']
|
|
950
961
|
: [])
|
|
951
962
|
],
|
|
952
|
-
...(request.promptCacheKey
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
963
|
+
...(request.promptCacheKey &&
|
|
964
|
+
configuration.responsesOptionalParameters?.promptCacheKey !== false
|
|
965
|
+
? { prompt_cache_key: request.promptCacheKey }
|
|
966
|
+
: {}),
|
|
967
|
+
...(configuration.responsesOptionalParameters?.reasoningEffort === false &&
|
|
968
|
+
configuration.responsesOptionalParameters?.reasoningSummary === false
|
|
969
|
+
? {}
|
|
970
|
+
: {
|
|
971
|
+
reasoning: {
|
|
972
|
+
...(configuration.responsesOptionalParameters?.reasoningEffort === false
|
|
973
|
+
? {}
|
|
974
|
+
: {
|
|
975
|
+
effort: request.requestReasoningEffort?.trim() ||
|
|
976
|
+
request.reasoningEffort?.trim() ||
|
|
977
|
+
resolveModelDefaultReasoningEffort(configuration)
|
|
978
|
+
}),
|
|
979
|
+
...(configuration.responsesOptionalParameters?.reasoningSummary === false
|
|
980
|
+
? {}
|
|
981
|
+
: { summary: 'auto' })
|
|
982
|
+
}
|
|
983
|
+
})
|
|
959
984
|
};
|
|
960
985
|
}
|
|
961
986
|
function incrementalResponsesInput(state, current) {
|
|
@@ -1025,9 +1050,9 @@ function turnStateFromEvent(event) {
|
|
|
1025
1050
|
}
|
|
1026
1051
|
return undefined;
|
|
1027
1052
|
}
|
|
1028
|
-
async function* parseOpenAiResponsesSse(response, signal, continuation) {
|
|
1053
|
+
async function* parseOpenAiResponsesSse(response, signal, continuation, sensitiveValues) {
|
|
1029
1054
|
try {
|
|
1030
|
-
yield* parseSse(response, signal);
|
|
1055
|
+
yield* parseSse(response, signal, undefined, undefined, sensitiveValues);
|
|
1031
1056
|
}
|
|
1032
1057
|
catch (cause) {
|
|
1033
1058
|
if (response.ok && !signal.aborted && isRetryableOpenAiStreamReadError(cause))
|
|
@@ -1765,11 +1790,13 @@ function responseCitations(response) {
|
|
|
1765
1790
|
function responseEventId(event) {
|
|
1766
1791
|
return text(record(event.response).id);
|
|
1767
1792
|
}
|
|
1768
|
-
export function openAiInput(messages, images, modelId, requireEncryptedReasoning, configurationUpdates = true) {
|
|
1793
|
+
export function openAiInput(messages, images, modelId, requireEncryptedReasoning, configurationUpdates = true, nativeTools = true, tools = []) {
|
|
1769
1794
|
const compatibleMessages = messages.filter((message) => message.role !== 'provider' ||
|
|
1770
1795
|
(message.provider === 'OPENAI_RESPONSES' &&
|
|
1771
1796
|
(message.providerModelId === undefined || message.providerModelId === modelId) &&
|
|
1772
1797
|
(configurationUpdates || message.item?.type !== 'configuration_update') &&
|
|
1798
|
+
(nativeTools ||
|
|
1799
|
+
!['custom_tool_call', 'tool_search_call', 'tool_search_output'].includes(String(message.item?.type))) &&
|
|
1773
1800
|
(!requireEncryptedReasoning ||
|
|
1774
1801
|
message.item?.type !== 'reasoning' ||
|
|
1775
1802
|
typeof message.item.encrypted_content === 'string')));
|
|
@@ -1804,7 +1831,10 @@ export function openAiInput(messages, images, modelId, requireEncryptedReasoning
|
|
|
1804
1831
|
}
|
|
1805
1832
|
}
|
|
1806
1833
|
for (const message of compatibleMessages)
|
|
1807
|
-
if (
|
|
1834
|
+
if (nativeTools &&
|
|
1835
|
+
message.role === 'tool_call' &&
|
|
1836
|
+
message.inputMode === 'freeform' &&
|
|
1837
|
+
message.callId)
|
|
1808
1838
|
customCallIds.add(message.callId);
|
|
1809
1839
|
const orderedMessages = [];
|
|
1810
1840
|
for (const message of compatibleMessages) {
|
|
@@ -1822,9 +1852,9 @@ export function openAiInput(messages, images, modelId, requireEncryptedReasoning
|
|
|
1822
1852
|
return orderedMessages
|
|
1823
1853
|
.filter((message) => !(message.role === 'tool_call' && message.callId && providerCallIds.has(message.callId)) &&
|
|
1824
1854
|
!(message.role === 'assistant' && providerMessageTexts.has(message.content)))
|
|
1825
|
-
.map((message) => openAiInputItem(message, message === lastUser ? images : undefined, customCallIds, functionCallIds));
|
|
1855
|
+
.map((message) => openAiInputItem(message, message === lastUser ? images : undefined, customCallIds, functionCallIds, nativeTools, tools));
|
|
1826
1856
|
}
|
|
1827
|
-
function openAiInputItem(message, images, customCallIds = new Set(), functionCallIds = new Set()) {
|
|
1857
|
+
function openAiInputItem(message, images, customCallIds = new Set(), functionCallIds = new Set(), nativeTools = true, tools = []) {
|
|
1828
1858
|
const userImages = message.role === 'user' ? uniqueModelImages(message.images, images) : [];
|
|
1829
1859
|
if (message.role === 'provider') {
|
|
1830
1860
|
if (message.provider !== 'OPENAI_RESPONSES' || !message.item)
|
|
@@ -1839,14 +1869,14 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
|
|
|
1839
1869
|
throw new MarAgentError('MAR_AGENT_MODEL_PROTOCOL_ERROR', 'Provider context item type is unsupported.');
|
|
1840
1870
|
return item;
|
|
1841
1871
|
}
|
|
1842
|
-
if (message.role === 'tool_call' && message.inputMode === 'freeform')
|
|
1872
|
+
if (nativeTools && message.role === 'tool_call' && message.inputMode === 'freeform')
|
|
1843
1873
|
return {
|
|
1844
1874
|
type: 'custom_tool_call',
|
|
1845
1875
|
call_id: message.callId,
|
|
1846
1876
|
name: message.name,
|
|
1847
1877
|
input: String(message.arguments ?? '')
|
|
1848
1878
|
};
|
|
1849
|
-
if (message.role === 'tool_call' && message.name === 'tool_search')
|
|
1879
|
+
if (nativeTools && message.role === 'tool_call' && message.name === 'tool_search')
|
|
1850
1880
|
return {
|
|
1851
1881
|
type: 'tool_search_call',
|
|
1852
1882
|
call_id: message.callId,
|
|
@@ -1859,9 +1889,12 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
|
|
|
1859
1889
|
type: 'function_call',
|
|
1860
1890
|
call_id: message.callId,
|
|
1861
1891
|
name: message.name,
|
|
1862
|
-
arguments: JSON.stringify(message.
|
|
1892
|
+
arguments: JSON.stringify(message.inputMode === 'freeform'
|
|
1893
|
+
? jsonToolInput(message, tools.find((tool) => tool.name === message.name))
|
|
1894
|
+
: (message.arguments ?? {}))
|
|
1863
1895
|
};
|
|
1864
1896
|
if (message.role === 'tool' &&
|
|
1897
|
+
nativeTools &&
|
|
1865
1898
|
message.toolSearchTools &&
|
|
1866
1899
|
!functionCallIds.has(message.callId ?? ''))
|
|
1867
1900
|
return {
|
|
@@ -1869,7 +1902,7 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
|
|
|
1869
1902
|
call_id: message.callId,
|
|
1870
1903
|
status: 'completed',
|
|
1871
1904
|
execution: 'client',
|
|
1872
|
-
tools: message.toolSearchTools.map(responsesFunctionTool)
|
|
1905
|
+
tools: message.toolSearchTools.map((tool) => responsesFunctionTool(tool))
|
|
1873
1906
|
};
|
|
1874
1907
|
if (message.role === 'tool')
|
|
1875
1908
|
return {
|
|
@@ -1928,9 +1961,9 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
|
|
|
1928
1961
|
: message.content
|
|
1929
1962
|
};
|
|
1930
1963
|
}
|
|
1931
|
-
function responsesFunctionTool(tool) {
|
|
1964
|
+
function responsesFunctionTool(tool, nativeTools = true) {
|
|
1932
1965
|
const strict = !hasOpenFunctionObject(tool.inputSchema);
|
|
1933
|
-
return tool.inputMode === 'freeform'
|
|
1966
|
+
return tool.inputMode === 'freeform' && nativeTools
|
|
1934
1967
|
? { type: 'custom', name: tool.name, description: tool.description }
|
|
1935
1968
|
: {
|
|
1936
1969
|
type: 'function',
|
|
@@ -4,3 +4,5 @@ export declare function acquireModelCredential(configuration: Pick<MarAgentModel
|
|
|
4
4
|
readonly credentialProvider?: ModelCredentialProvider;
|
|
5
5
|
}, reason: ModelCredentialAcquireReason, missingCredentialMessage: string, signal?: AbortSignal): Promise<ModelCredential>;
|
|
6
6
|
export declare function appendModelCredentialHeaders(headers: Headers, credential: ModelCredential, reservedHeaders: ReadonlySet<string>): Headers;
|
|
7
|
+
/** Values known to be credentials, used only when producing public HTTP error diagnostics. */
|
|
8
|
+
export declare function modelCredentialSensitiveValues(credential: ModelCredential): readonly string[];
|
|
@@ -55,3 +55,18 @@ export function appendModelCredentialHeaders(headers, credential, reservedHeader
|
|
|
55
55
|
}
|
|
56
56
|
return headers;
|
|
57
57
|
}
|
|
58
|
+
/** Values known to be credentials, used only when producing public HTTP error diagnostics. */
|
|
59
|
+
export function modelCredentialSensitiveValues(credential) {
|
|
60
|
+
const values = [
|
|
61
|
+
credential.bearer.reveal(),
|
|
62
|
+
...Object.values(credential.headers)
|
|
63
|
+
.filter((value) => value instanceof SecretValue)
|
|
64
|
+
.map((value) => value.reveal())
|
|
65
|
+
];
|
|
66
|
+
if (credential.socks5Url !== undefined) {
|
|
67
|
+
const proxy = credential.socks5Url.reveal();
|
|
68
|
+
const url = new URL(proxy);
|
|
69
|
+
values.push(proxy, decodeURIComponent(url.username), decodeURIComponent(url.password));
|
|
70
|
+
}
|
|
71
|
+
return values;
|
|
72
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare function observeModelStreamEvent<const T>(value: T, observedAt: number | undefined): T;
|
|
2
2
|
export declare function modelStreamEventObservedAt(value: unknown): number | undefined;
|
|
3
|
-
export declare function parseSse(response: Response, signal: AbortSignal, maxBytes?: number, idleTimeoutMs?: number): AsyncIterable<unknown>;
|
|
3
|
+
export declare function parseSse(response: Response, signal: AbortSignal, maxBytes?: number, idleTimeoutMs?: number, sensitiveValues?: readonly string[]): AsyncIterable<unknown>;
|
|
4
4
|
export declare function isRetryableUpstreamOverloadCode(code: string | undefined): boolean;
|
|
5
5
|
export declare function record(value: unknown): Record<string, unknown>;
|
|
6
6
|
export declare function text(value: unknown): string | undefined;
|
|
@@ -10,9 +10,14 @@ export function observeModelStreamEvent(value, observedAt) {
|
|
|
10
10
|
export function modelStreamEventObservedAt(value) {
|
|
11
11
|
return typeof value === 'object' && value !== null ? streamEventObservedAt.get(value) : undefined;
|
|
12
12
|
}
|
|
13
|
-
export async function* parseSse(response, signal, maxBytes = 16 * 1024 * 1024, idleTimeoutMs = DEFAULT_MODEL_STREAM_IDLE_TIMEOUT_MS) {
|
|
13
|
+
export async function* parseSse(response, signal, maxBytes = 16 * 1024 * 1024, idleTimeoutMs = DEFAULT_MODEL_STREAM_IDLE_TIMEOUT_MS, sensitiveValues = []) {
|
|
14
14
|
if (!response.ok) {
|
|
15
|
-
const
|
|
15
|
+
const redact = responseRedactor(sensitiveValues);
|
|
16
|
+
const body = redact(await readResponseText(response, signal, maxBytes, idleTimeoutMs));
|
|
17
|
+
const header = (name, limit) => {
|
|
18
|
+
const value = response.headers.get(name);
|
|
19
|
+
return value === null ? null : redact(value).slice(0, limit);
|
|
20
|
+
};
|
|
16
21
|
const upstream = upstreamError(body);
|
|
17
22
|
const code = upstream?.code ??
|
|
18
23
|
(response.status === 401 || response.status === 403
|
|
@@ -25,8 +30,11 @@ export async function* parseSse(response, signal, maxBytes = 16 * 1024 * 1024, i
|
|
|
25
30
|
throw new MarAgentError(code, upstream?.message ?? `Model endpoint returned HTTP ${response.status}.`, {
|
|
26
31
|
details: {
|
|
27
32
|
status: response.status,
|
|
28
|
-
body: body.slice(0,
|
|
29
|
-
|
|
33
|
+
body: body.slice(0, 4096),
|
|
34
|
+
bodyTruncated: body.length > 4096,
|
|
35
|
+
contentType: header('content-type', 256),
|
|
36
|
+
requestId: header('x-request-id', 256) ?? header('request-id', 256),
|
|
37
|
+
retryAfter: header('retry-after', 128)
|
|
30
38
|
},
|
|
31
39
|
retryable: isRetryableModelHttpStatus(response.status)
|
|
32
40
|
});
|
|
@@ -208,6 +216,12 @@ async function readStreamChunk(reader, signal, idleTimeoutMs) {
|
|
|
208
216
|
});
|
|
209
217
|
});
|
|
210
218
|
}
|
|
219
|
+
function responseRedactor(sensitiveValues) {
|
|
220
|
+
const secrets = [
|
|
221
|
+
...new Set(sensitiveValues.flatMap((value) => value ? [value, JSON.stringify(value).slice(1, -1), encodeURIComponent(value)] : []))
|
|
222
|
+
].sort((left, right) => right.length - left.length);
|
|
223
|
+
return (value) => secrets.reduce((text, secret) => text.replaceAll(secret, '[REDACTED]'), value);
|
|
224
|
+
}
|
|
211
225
|
function upstreamError(body) {
|
|
212
226
|
let parsed;
|
|
213
227
|
try {
|
|
@@ -217,6 +231,10 @@ function upstreamError(body) {
|
|
|
217
231
|
return undefined;
|
|
218
232
|
}
|
|
219
233
|
const root = record(parsed);
|
|
234
|
+
if (typeof root.error === 'string') {
|
|
235
|
+
const message = bounded(root.error, 1_000);
|
|
236
|
+
return message === undefined ? undefined : { message };
|
|
237
|
+
}
|
|
220
238
|
const error = record(root.error);
|
|
221
239
|
// K3-compatible gateways can put the error discriminator in `type` and omit `code`.
|
|
222
240
|
const code = bounded(text(error.code) ?? text(error.type), 80);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ModelRequest } from './contracts.js';
|
|
2
|
+
/** Preserve a freeform call when replaying it through a JSON-only tool protocol. */
|
|
3
|
+
export declare function jsonToolInput(message: ModelRequest['messages'][number], tool: ModelRequest['tools'][number] | undefined): Record<string, unknown>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Preserve a freeform call when replaying it through a JSON-only tool protocol. */
|
|
2
|
+
export function jsonToolInput(message, tool) {
|
|
3
|
+
if (isObject(message.arguments))
|
|
4
|
+
return message.arguments;
|
|
5
|
+
if (message.inputMode !== 'freeform')
|
|
6
|
+
return {};
|
|
7
|
+
const schema = tool?.inputSchema;
|
|
8
|
+
const required = schema?.type === 'object' && Array.isArray(schema.required)
|
|
9
|
+
? schema.required.filter((value) => typeof value === 'string')
|
|
10
|
+
: [];
|
|
11
|
+
const name = required.length === 1 && typeof required[0] === 'string' ? required[0] : undefined;
|
|
12
|
+
const property = name && isObject(schema?.properties) ? schema.properties[name] : undefined;
|
|
13
|
+
const key = name && isObject(property) && property.type === 'string' ? name : 'input';
|
|
14
|
+
return { [key]: String(message.arguments ?? '') };
|
|
15
|
+
}
|
|
16
|
+
function isObject(value) {
|
|
17
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
18
|
+
}
|