Fix single-agent ACP model ownership
This commit is contained in:
parent
1807744575
commit
adb93ed7ff
@ -205,6 +205,8 @@ class AppController extends ChangeNotifier {
|
||||
<String, List<GatewayChatMessage>>{};
|
||||
final Map<String, String> _aiGatewayStreamingTextBySession =
|
||||
<String, String>{};
|
||||
final Map<String, String> _singleAgentRuntimeModelBySession =
|
||||
<String, String>{};
|
||||
final Map<String, HttpClient> _aiGatewayStreamingClients =
|
||||
<String, HttpClient>{};
|
||||
final Set<String> _aiGatewayPendingSessionKeys = <String>{};
|
||||
@ -517,6 +519,19 @@ class AppController extends ChangeNotifier {
|
||||
String assistantModelForSession(String sessionKey) {
|
||||
final normalizedSessionKey = _normalizedAssistantSessionKey(sessionKey);
|
||||
final target = assistantExecutionTargetForSession(normalizedSessionKey);
|
||||
if (target == AssistantExecutionTarget.singleAgent) {
|
||||
if (singleAgentUsesAiChatFallbackForSession(normalizedSessionKey)) {
|
||||
final recordModel =
|
||||
_assistantThreadRecords[normalizedSessionKey]?.assistantModelId
|
||||
.trim() ??
|
||||
'';
|
||||
if (recordModel.isNotEmpty) {
|
||||
return recordModel;
|
||||
}
|
||||
return resolvedAiGatewayModel;
|
||||
}
|
||||
return singleAgentRuntimeModelForSession(normalizedSessionKey);
|
||||
}
|
||||
final recordModel =
|
||||
_assistantThreadRecords[normalizedSessionKey]?.assistantModelId
|
||||
.trim() ??
|
||||
@ -524,10 +539,6 @@ class AppController extends ChangeNotifier {
|
||||
if (recordModel.isNotEmpty) {
|
||||
return recordModel;
|
||||
}
|
||||
if (target == AssistantExecutionTarget.singleAgent &&
|
||||
singleAgentUsesAiChatFallbackForSession(normalizedSessionKey)) {
|
||||
return resolvedAiGatewayModel;
|
||||
}
|
||||
return _resolvedAssistantModelForTarget(target);
|
||||
}
|
||||
|
||||
@ -603,8 +614,20 @@ class AppController extends ChangeNotifier {
|
||||
bool get currentSingleAgentShouldSuggestAutoSwitch =>
|
||||
singleAgentShouldSuggestAutoSwitchForSession(currentSessionKey);
|
||||
|
||||
String singleAgentRuntimeModelForSession(String sessionKey) {
|
||||
final normalizedSessionKey = _normalizedAssistantSessionKey(sessionKey);
|
||||
return _singleAgentRuntimeModelBySession[normalizedSessionKey]?.trim() ?? '';
|
||||
}
|
||||
|
||||
String get currentSingleAgentRuntimeModel =>
|
||||
singleAgentRuntimeModelForSession(currentSessionKey);
|
||||
|
||||
String singleAgentModelDisplayLabelForSession(String sessionKey) {
|
||||
final normalizedSessionKey = _normalizedAssistantSessionKey(sessionKey);
|
||||
final runtimeModel = singleAgentRuntimeModelForSession(normalizedSessionKey);
|
||||
if (runtimeModel.isNotEmpty) {
|
||||
return runtimeModel;
|
||||
}
|
||||
final model = assistantModelForSession(normalizedSessionKey);
|
||||
if (model.isNotEmpty) {
|
||||
return model;
|
||||
@ -624,6 +647,21 @@ class AppController extends ChangeNotifier {
|
||||
String get currentSingleAgentModelDisplayLabel =>
|
||||
singleAgentModelDisplayLabelForSession(currentSessionKey);
|
||||
|
||||
bool singleAgentShouldShowModelControlForSession(String sessionKey) {
|
||||
final normalizedSessionKey = _normalizedAssistantSessionKey(sessionKey);
|
||||
if (assistantExecutionTargetForSession(normalizedSessionKey) !=
|
||||
AssistantExecutionTarget.singleAgent) {
|
||||
return true;
|
||||
}
|
||||
if (singleAgentUsesAiChatFallbackForSession(normalizedSessionKey)) {
|
||||
return true;
|
||||
}
|
||||
return singleAgentRuntimeModelForSession(normalizedSessionKey).isNotEmpty;
|
||||
}
|
||||
|
||||
bool get currentSingleAgentShouldShowModelControl =>
|
||||
singleAgentShouldShowModelControlForSession(currentSessionKey);
|
||||
|
||||
List<SingleAgentProvider> get singleAgentProviderOptions =>
|
||||
const <SingleAgentProvider>[
|
||||
SingleAgentProvider.auto,
|
||||
@ -1786,6 +1824,7 @@ class AppController extends ChangeNotifier {
|
||||
if (singleAgentProviderForSession(sessionKey) == sanitizedProvider) {
|
||||
return;
|
||||
}
|
||||
_singleAgentRuntimeModelBySession.remove(sessionKey);
|
||||
_upsertAssistantThreadRecord(
|
||||
sessionKey,
|
||||
singleAgentProvider: sanitizedProvider,
|
||||
@ -1838,6 +1877,9 @@ class AppController extends ChangeNotifier {
|
||||
}) async {
|
||||
final resolvedTarget = _sanitizeExecutionTarget(target);
|
||||
final normalizedSessionKey = _normalizedAssistantSessionKey(sessionKey);
|
||||
if (resolvedTarget != AssistantExecutionTarget.singleAgent) {
|
||||
_singleAgentRuntimeModelBySession.remove(normalizedSessionKey);
|
||||
}
|
||||
if (!matchesSessionKey(
|
||||
normalizedSessionKey,
|
||||
_sessionsController.currentSessionKey,
|
||||
@ -3209,6 +3251,10 @@ class AppController extends ChangeNotifier {
|
||||
configuredCodexCliPath: configuredCodexCliPath,
|
||||
),
|
||||
);
|
||||
final resolvedRuntimeModel = result.resolvedModel.trim();
|
||||
if (resolvedRuntimeModel.isNotEmpty) {
|
||||
_singleAgentRuntimeModelBySession[sessionKey] = resolvedRuntimeModel;
|
||||
}
|
||||
_clearAiGatewayStreamingText(sessionKey);
|
||||
if (result.aborted) {
|
||||
final partial = result.output.trim();
|
||||
|
||||
@ -459,6 +459,9 @@ class _AssistantPageState extends State<AssistantPage> {
|
||||
inputController: _inputController,
|
||||
focusNode: _composerFocusNode,
|
||||
thinkingLabel: _thinkingLabel,
|
||||
showModelControl: !controller.isSingleAgentMode
|
||||
? true
|
||||
: controller.currentSingleAgentShouldShowModelControl,
|
||||
modelLabel: controller.isSingleAgentMode
|
||||
? controller.currentSingleAgentModelDisplayLabel
|
||||
: controller.resolvedAssistantModel.isEmpty
|
||||
@ -1617,6 +1620,7 @@ class _AssistantLowerPane extends StatelessWidget {
|
||||
required this.inputController,
|
||||
required this.focusNode,
|
||||
required this.thinkingLabel,
|
||||
required this.showModelControl,
|
||||
required this.modelLabel,
|
||||
required this.modelOptions,
|
||||
required this.attachments,
|
||||
@ -1638,6 +1642,7 @@ class _AssistantLowerPane extends StatelessWidget {
|
||||
final TextEditingController inputController;
|
||||
final FocusNode focusNode;
|
||||
final String thinkingLabel;
|
||||
final bool showModelControl;
|
||||
final String modelLabel;
|
||||
final List<String> modelOptions;
|
||||
final List<_ComposerAttachment> attachments;
|
||||
@ -1665,6 +1670,7 @@ class _AssistantLowerPane extends StatelessWidget {
|
||||
inputController: inputController,
|
||||
focusNode: focusNode,
|
||||
thinkingLabel: thinkingLabel,
|
||||
showModelControl: showModelControl,
|
||||
modelLabel: modelLabel,
|
||||
modelOptions: modelOptions,
|
||||
attachments: attachments,
|
||||
@ -2495,6 +2501,7 @@ class _ComposerBar extends StatefulWidget {
|
||||
required this.inputController,
|
||||
required this.focusNode,
|
||||
required this.thinkingLabel,
|
||||
required this.showModelControl,
|
||||
required this.modelLabel,
|
||||
required this.modelOptions,
|
||||
required this.attachments,
|
||||
@ -2516,6 +2523,7 @@ class _ComposerBar extends StatefulWidget {
|
||||
final TextEditingController inputController;
|
||||
final FocusNode focusNode;
|
||||
final String thinkingLabel;
|
||||
final bool showModelControl;
|
||||
final String modelLabel;
|
||||
final List<String> modelOptions;
|
||||
final List<_ComposerAttachment> attachments;
|
||||
@ -2899,43 +2907,45 @@ class _ComposerBarState extends State<_ComposerBar> {
|
||||
maxLabelWidth: 120,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
widget.modelOptions.isEmpty
|
||||
? _ComposerToolbarChip(
|
||||
key: const Key('assistant-model-button'),
|
||||
icon: Icons.bolt_rounded,
|
||||
label: widget.modelLabel,
|
||||
showChevron: false,
|
||||
maxLabelWidth: 140,
|
||||
)
|
||||
: PopupMenuButton<String>(
|
||||
key: const Key('assistant-model-button'),
|
||||
tooltip: appText('模型', 'Model'),
|
||||
onSelected: widget.onModelChanged,
|
||||
itemBuilder: (context) => widget.modelOptions
|
||||
.map(
|
||||
(value) => PopupMenuItem<String>(
|
||||
value: value,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Text(value)),
|
||||
if (value == widget.modelLabel)
|
||||
const Icon(
|
||||
Icons.check_rounded,
|
||||
size: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
child: _ComposerToolbarChip(
|
||||
if (widget.showModelControl) ...[
|
||||
const SizedBox(width: 6),
|
||||
widget.modelOptions.isEmpty
|
||||
? _ComposerToolbarChip(
|
||||
key: const Key('assistant-model-button'),
|
||||
icon: Icons.bolt_rounded,
|
||||
label: widget.modelLabel,
|
||||
showChevron: true,
|
||||
showChevron: false,
|
||||
maxLabelWidth: 140,
|
||||
)
|
||||
: PopupMenuButton<String>(
|
||||
key: const Key('assistant-model-button'),
|
||||
tooltip: appText('模型', 'Model'),
|
||||
onSelected: widget.onModelChanged,
|
||||
itemBuilder: (context) => widget.modelOptions
|
||||
.map(
|
||||
(value) => PopupMenuItem<String>(
|
||||
value: value,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Text(value)),
|
||||
if (value == widget.modelLabel)
|
||||
const Icon(
|
||||
Icons.check_rounded,
|
||||
size: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
child: _ComposerToolbarChip(
|
||||
icon: Icons.bolt_rounded,
|
||||
label: widget.modelLabel,
|
||||
showChevron: true,
|
||||
maxLabelWidth: 140,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 6),
|
||||
PopupMenuButton<String>(
|
||||
key: const Key('assistant-thinking-button'),
|
||||
|
||||
@ -35,12 +35,14 @@ class DirectSingleAgentRunResult {
|
||||
required this.output,
|
||||
required this.errorMessage,
|
||||
this.aborted = false,
|
||||
this.resolvedModel = '',
|
||||
});
|
||||
|
||||
final bool success;
|
||||
final String output;
|
||||
final String errorMessage;
|
||||
final bool aborted;
|
||||
final String resolvedModel;
|
||||
}
|
||||
|
||||
class DirectSingleAgentRunRequest {
|
||||
@ -166,6 +168,7 @@ class DirectSingleAgentAppServerClient {
|
||||
);
|
||||
|
||||
final output = StringBuffer();
|
||||
String resolvedModel = '';
|
||||
final completion = Completer<DirectSingleAgentRunResult>();
|
||||
late final StreamSubscription<Map<String, dynamic>> subscription;
|
||||
subscription = connection.notifications.listen(
|
||||
@ -189,6 +192,7 @@ class DirectSingleAgentAppServerClient {
|
||||
success: true,
|
||||
output: output.toString(),
|
||||
errorMessage: '',
|
||||
resolvedModel: resolvedModel,
|
||||
),
|
||||
);
|
||||
return;
|
||||
@ -206,6 +210,7 @@ class DirectSingleAgentAppServerClient {
|
||||
success: false,
|
||||
output: output.toString(),
|
||||
aborted: aborted,
|
||||
resolvedModel: resolvedModel,
|
||||
errorMessage:
|
||||
params['message']?.toString() ??
|
||||
params['error']?.toString() ??
|
||||
@ -222,6 +227,7 @@ class DirectSingleAgentAppServerClient {
|
||||
output: output.toString(),
|
||||
errorMessage: error.toString(),
|
||||
aborted: _abortedSessions.contains(normalizedSessionId),
|
||||
resolvedModel: resolvedModel,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -236,6 +242,7 @@ class DirectSingleAgentAppServerClient {
|
||||
? 'Single-agent app-server run aborted.'
|
||||
: 'Single-agent app-server connection closed before completion.',
|
||||
aborted: _abortedSessions.contains(normalizedSessionId),
|
||||
resolvedModel: resolvedModel,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -243,7 +250,7 @@ class DirectSingleAgentAppServerClient {
|
||||
);
|
||||
|
||||
try {
|
||||
await connection.request(
|
||||
final started = await connection.request(
|
||||
'turn/start',
|
||||
params: <String, dynamic>{
|
||||
'threadId': threadId,
|
||||
@ -253,6 +260,7 @@ class DirectSingleAgentAppServerClient {
|
||||
},
|
||||
},
|
||||
);
|
||||
resolvedModel = _extractModel(started) ?? resolvedModel;
|
||||
return await completion.future.timeout(
|
||||
const Duration(minutes: 10),
|
||||
onTimeout: () => DirectSingleAgentRunResult(
|
||||
@ -260,6 +268,7 @@ class DirectSingleAgentAppServerClient {
|
||||
output: output.toString(),
|
||||
errorMessage: 'Single-agent app-server request timed out.',
|
||||
aborted: _abortedSessions.contains(normalizedSessionId),
|
||||
resolvedModel: resolvedModel,
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
@ -271,6 +280,7 @@ class DirectSingleAgentAppServerClient {
|
||||
output: '',
|
||||
errorMessage: error.toString(),
|
||||
aborted: _abortedSessions.contains(normalizedSessionId),
|
||||
resolvedModel: '',
|
||||
);
|
||||
} finally {
|
||||
_activeConnections.remove(normalizedSessionId);
|
||||
@ -325,7 +335,7 @@ class DirectSingleAgentAppServerClient {
|
||||
if (workingDirectory.trim().isNotEmpty) 'cwd': workingDirectory,
|
||||
},
|
||||
);
|
||||
final resumedId = resumed['id']?.toString().trim() ?? existingThreadId;
|
||||
final resumedId = _extractThreadId(resumed) ?? existingThreadId;
|
||||
_threadIds[sessionId] = resumedId;
|
||||
return resumedId;
|
||||
} catch (_) {
|
||||
@ -340,7 +350,7 @@ class DirectSingleAgentAppServerClient {
|
||||
if (model.trim().isNotEmpty) 'model': model.trim(),
|
||||
},
|
||||
);
|
||||
final threadId = created['id']?.toString().trim() ?? '';
|
||||
final threadId = _extractThreadId(created) ?? '';
|
||||
if (threadId.isEmpty) {
|
||||
throw StateError('Single-agent app-server returned an empty thread id.');
|
||||
}
|
||||
@ -348,6 +358,27 @@ class DirectSingleAgentAppServerClient {
|
||||
return threadId;
|
||||
}
|
||||
|
||||
String? _extractThreadId(Map<String, dynamic> payload) {
|
||||
final topLevelId = payload['id']?.toString().trim() ?? '';
|
||||
if (topLevelId.isNotEmpty) {
|
||||
return topLevelId;
|
||||
}
|
||||
final thread = _asMap(payload['thread']);
|
||||
final nestedId = thread['id']?.toString().trim() ?? '';
|
||||
if (nestedId.isNotEmpty) {
|
||||
return nestedId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? _extractModel(Map<String, dynamic> payload) {
|
||||
final model = payload['model']?.toString().trim() ?? '';
|
||||
if (model.isNotEmpty) {
|
||||
return model;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Uri? _resolveWebSocketEndpoint(SingleAgentProvider provider) {
|
||||
final base = endpointResolver(provider);
|
||||
if (base == null) {
|
||||
|
||||
@ -55,6 +55,7 @@ class SingleAgentRunResult {
|
||||
required this.shouldFallbackToAiChat,
|
||||
this.aborted = false,
|
||||
this.fallbackReason,
|
||||
this.resolvedModel = '',
|
||||
});
|
||||
|
||||
final SingleAgentProvider provider;
|
||||
@ -64,6 +65,7 @@ class SingleAgentRunResult {
|
||||
final bool shouldFallbackToAiChat;
|
||||
final bool aborted;
|
||||
final String? fallbackReason;
|
||||
final String resolvedModel;
|
||||
}
|
||||
|
||||
abstract class SingleAgentRunner {
|
||||
@ -166,6 +168,7 @@ class DefaultSingleAgentRunner implements SingleAgentRunner {
|
||||
errorMessage: result.errorMessage,
|
||||
shouldFallbackToAiChat: !result.success && result.output.isEmpty,
|
||||
aborted: result.aborted,
|
||||
resolvedModel: result.resolvedModel,
|
||||
fallbackReason: !result.success
|
||||
? 'Single-agent app-server run failed: ${result.errorMessage}'
|
||||
: null,
|
||||
@ -178,6 +181,7 @@ class DefaultSingleAgentRunner implements SingleAgentRunner {
|
||||
success: false,
|
||||
errorMessage: error.toString(),
|
||||
shouldFallbackToAiChat: shouldFallback,
|
||||
resolvedModel: '',
|
||||
fallbackReason: shouldFallback
|
||||
? '${request.provider.label} provider is unavailable from the direct app-server endpoint.'
|
||||
: null,
|
||||
|
||||
@ -349,6 +349,7 @@ void main() {
|
||||
success: true,
|
||||
errorMessage: '',
|
||||
shouldFallbackToAiChat: false,
|
||||
resolvedModel: 'codex-sonnet',
|
||||
),
|
||||
);
|
||||
final controller = AppController(
|
||||
@ -375,6 +376,8 @@ void main() {
|
||||
expect(runner.resolveCalls, 1);
|
||||
expect(runner.runCalls, 1);
|
||||
expect(runner.lastRequest?.provider, SingleAgentProvider.codex);
|
||||
expect(runner.lastRequest?.model, isEmpty);
|
||||
expect(controller.currentSingleAgentModelDisplayLabel, 'codex-sonnet');
|
||||
expect(
|
||||
controller.chatMessages.any(
|
||||
(message) =>
|
||||
|
||||
@ -136,6 +136,7 @@ void main() {
|
||||
|
||||
expect(controller.currentSingleAgentHasResolvedProvider, isTrue);
|
||||
expect(controller.currentSingleAgentUsesAiChatFallback, isFalse);
|
||||
expect(controller.currentSingleAgentShouldShowModelControl, isFalse);
|
||||
expect(controller.assistantModelChoices, isEmpty);
|
||||
expect(controller.resolvedAssistantModel, isEmpty);
|
||||
},
|
||||
|
||||
@ -52,6 +52,7 @@ void main() {
|
||||
|
||||
expect(result.success, isTrue);
|
||||
expect(result.output, 'hello world from app server');
|
||||
expect(result.resolvedModel, 'codex-sonnet');
|
||||
expect(deltas.join(), 'hello world from app server');
|
||||
expect(
|
||||
server.methods,
|
||||
@ -87,14 +88,47 @@ void main() {
|
||||
expect(result.aborted, isTrue);
|
||||
expect(server.methods, contains('turn/interrupt'));
|
||||
});
|
||||
|
||||
test(
|
||||
'accepts nested thread objects returned by codex app-server',
|
||||
() async {
|
||||
final server = await _FakeAppServer.start(nestedThreadResult: true);
|
||||
addTearDown(server.close);
|
||||
|
||||
final client = DirectSingleAgentAppServerClient(
|
||||
endpointResolver: (_) => server.baseHttpUri,
|
||||
);
|
||||
addTearDown(client.dispose);
|
||||
|
||||
final result = await client.run(
|
||||
const DirectSingleAgentRunRequest(
|
||||
sessionId: 'session-nested',
|
||||
provider: SingleAgentProvider.codex,
|
||||
prompt: 'hello nested world',
|
||||
model: 'qwen2.5-coder:latest',
|
||||
workingDirectory: '/tmp',
|
||||
gatewayToken: '',
|
||||
),
|
||||
);
|
||||
|
||||
expect(result.success, isTrue);
|
||||
expect(result.output, 'hello world from app server');
|
||||
expect(result.resolvedModel, 'codex-sonnet');
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class _FakeAppServer {
|
||||
_FakeAppServer._(this._server, {required this.delayCompletion});
|
||||
_FakeAppServer._(
|
||||
this._server, {
|
||||
required this.delayCompletion,
|
||||
required this.nestedThreadResult,
|
||||
});
|
||||
|
||||
final HttpServer _server;
|
||||
final bool delayCompletion;
|
||||
final bool nestedThreadResult;
|
||||
final List<String> methods = <String>[];
|
||||
final List<String> authorizationHeaders = <String>[];
|
||||
final Map<String, Completer<void>> _methodWaiters =
|
||||
@ -104,9 +138,16 @@ class _FakeAppServer {
|
||||
int get port => _server.port;
|
||||
Uri get baseHttpUri => Uri.parse('http://127.0.0.1:${_server.port}');
|
||||
|
||||
static Future<_FakeAppServer> start({bool delayCompletion = false}) async {
|
||||
static Future<_FakeAppServer> start({
|
||||
bool delayCompletion = false,
|
||||
bool nestedThreadResult = false,
|
||||
}) async {
|
||||
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
|
||||
final fake = _FakeAppServer._(server, delayCompletion: delayCompletion);
|
||||
final fake = _FakeAppServer._(
|
||||
server,
|
||||
delayCompletion: delayCompletion,
|
||||
nestedThreadResult: nestedThreadResult,
|
||||
);
|
||||
unawaited(fake._listen());
|
||||
return fake;
|
||||
}
|
||||
@ -166,28 +207,46 @@ class _FakeAppServer {
|
||||
break;
|
||||
case 'thread/start':
|
||||
_threadCounter += 1;
|
||||
final result = nestedThreadResult
|
||||
? <String, dynamic>{
|
||||
'thread': <String, dynamic>{
|
||||
'id': 'thread-$_threadCounter',
|
||||
'path': params['cwd'] ?? '/tmp',
|
||||
'ephemeral': false,
|
||||
},
|
||||
}
|
||||
: <String, dynamic>{
|
||||
'id': 'thread-$_threadCounter',
|
||||
'path': params['cwd'] ?? '/tmp',
|
||||
'ephemeral': false,
|
||||
};
|
||||
socket.add(
|
||||
jsonEncode(<String, dynamic>{
|
||||
'jsonrpc': '2.0',
|
||||
'id': id,
|
||||
'result': <String, dynamic>{
|
||||
'id': 'thread-$_threadCounter',
|
||||
'path': params['cwd'] ?? '/tmp',
|
||||
'ephemeral': false,
|
||||
},
|
||||
'result': result,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case 'thread/resume':
|
||||
final result = nestedThreadResult
|
||||
? <String, dynamic>{
|
||||
'thread': <String, dynamic>{
|
||||
'id': params['threadId'] ?? 'thread-resumed',
|
||||
'path': params['cwd'] ?? '/tmp',
|
||||
'ephemeral': false,
|
||||
},
|
||||
}
|
||||
: <String, dynamic>{
|
||||
'id': params['threadId'] ?? 'thread-resumed',
|
||||
'path': params['cwd'] ?? '/tmp',
|
||||
'ephemeral': false,
|
||||
};
|
||||
socket.add(
|
||||
jsonEncode(<String, dynamic>{
|
||||
'jsonrpc': '2.0',
|
||||
'id': id,
|
||||
'result': <String, dynamic>{
|
||||
'id': params['threadId'] ?? 'thread-resumed',
|
||||
'path': params['cwd'] ?? '/tmp',
|
||||
'ephemeral': false,
|
||||
},
|
||||
'result': result,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
@ -201,6 +260,7 @@ class _FakeAppServer {
|
||||
'id': 'turn-1',
|
||||
'threadId': threadId,
|
||||
'status': 'started',
|
||||
'model': 'codex-sonnet',
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user