forked from AI45Lab/Code
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathHomeLayout.tsx
More file actions
1455 lines (1394 loc) · 49.1 KB
/
Copy pathHomeLayout.tsx
File metadata and controls
1455 lines (1394 loc) · 49.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import {
useEffect,
useState,
type KeyboardEvent as ReactKeyboardEvent,
} from 'react';
import { useLang, useSite, useVersion, withBase } from '@rspress/core/runtime';
import {
InnerLine,
Pre,
type AnnotationHandler,
type HighlightedCode,
} from 'codehike/code';
import {
Selectable,
SelectionProvider,
useSelectedIndex,
} from 'codehike/utils/selection';
import runtimeTutorialData from '../generated/runtime-tutorial.json';
type Locale = 'zh' | 'en';
type Localized = {
zh: string;
en: string;
};
type Feature = {
index: string;
title: Localized;
body: Localized;
tags: string[];
};
type RuntimeTutorialStep = {
id: string;
layer: string;
filename: string;
language: string;
title: Localized;
body: Localized;
note: Localized;
tags: string[];
focus: [number, number];
code: string;
highlighted: HighlightedCode;
};
const runtimeTutorialSteps =
runtimeTutorialData as unknown as RuntimeTutorialStep[];
const installCommands = [
{
id: 'unix',
label: 'macOS / Linux',
command:
"curl --proto '=https' --tlsv1.2 -LsSf https://raw.githubusercontent.com/A3S-Lab/a3s/main/install.sh | sh\n\na3s code",
},
{
id: 'windows',
label: 'Windows',
command:
'[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12\nirm https://raw.githubusercontent.com/A3S-Lab/a3s/main/install.ps1 | iex\n\na3s code',
},
{
id: 'rust',
label: 'Rust',
command: 'cargo add a3s-code-core',
},
{
id: 'node',
label: 'Node.js',
command: 'npm install @a3s-lab/code',
},
{
id: 'python',
label: 'Python',
command: 'python -m pip install a3s-code',
},
] as const;
const governanceFeatures: Feature[] = [
{
index: '01',
title: {
zh: '统一检查文件、Shell、Git 与外部请求',
en: 'Check files, shell, Git, and external requests',
},
body: {
zh: '模型提交工具参数后,Runtime 依次检查参数、Workspace 能力和权限规则。需要用户确认的调用会先暂停,不会直接执行。',
en: 'After the model submits tool arguments, the runtime checks the arguments, workspace capability, and permission policy. Calls that need approval pause before execution.',
},
tags: ['policy', 'HITL', 'sandbox'],
},
{
index: '02',
title: {
zh: '大输出保存为 Artifact',
en: 'Store large output as artifacts',
},
body: {
zh: '文件、搜索、命令和网页结果都支持范围或游标。超过限制的内容会写入 Artifact,模型只收到预览、大小、哈希和取回地址。',
en: 'File, search, command, and web results support ranges or cursors. Oversized output is written to an artifact; the model receives a preview, size, hash, and retrieval URI.',
},
tags: ['cursor', 'artifact', 'hash'],
},
{
index: '03',
title: {
zh: '界面订阅 AgentEvent',
en: 'Render the AgentEvent stream',
},
body: {
zh: '文本、工具调用、计划、确认和生命周期变化都有明确的事件类型。终端、IDE 和网页可以消费同一条事件流。',
en: 'Text, tool calls, plans, approvals, and lifecycle changes have explicit event types. A terminal, IDE, or web app can consume the same stream.',
},
tags: ['AgentEvent', 'EventEnvelopeV1'],
},
{
index: '04',
title: {
zh: 'SessionSnapshotV1 保存恢复数据',
en: 'Resume from SessionSnapshotV1',
},
body: {
zh: '会话、Run、Artifact、Trace、验证结果和子任务记录按同一代快照提交。恢复时直接读取已保存状态。',
en: 'Sessions, runs, artifacts, traces, verification results, and child-task records are committed as one snapshot generation and loaded directly on resume.',
},
tags: ['snapshot', 'replay', 'verification'],
},
];
const capabilityCards = [
{
className: 'a3s-bento-card--wide a3s-bento-card--policy',
eyebrow: { zh: '工具调用', en: 'TOOL CALLS' },
title: {
zh: '工具列表由 Workspace 和权限共同确定',
en: 'Workspace and policy determine the tool list',
},
body: {
zh: '文件、搜索、Shell、Git、Web、Batch、QuickJS、结构化输出和子任务,只有在当前 Workspace 支持且规则允许时才会提供给模型。',
en: 'Files, search, shell, Git, web, batch, QuickJS, structured output, and child tasks are exposed only when the current workspace supports them and policy allows them.',
},
tags: ['files', 'shell', 'git', 'web', 'program', 'task'],
},
{
className: 'a3s-bento-card--models',
eyebrow: { zh: '模型', en: 'MODELS' },
title: {
zh: '更换模型适配器,不改 Session API',
en: 'Change the model adapter, not the Session API',
},
body: {
zh: '支持 Anthropic、智谱、OpenAI-compatible API,也可以注入自己的 LlmClient。',
en: 'Use Anthropic, Zhipu, OpenAI-compatible APIs, or inject your own LlmClient.',
},
tags: ['streaming', 'tools', 'structured output'],
},
{
className: 'a3s-bento-card--state',
eyebrow: { zh: '任务记录', en: 'RUN DATA' },
title: {
zh: 'Run、事件与快照使用稳定格式',
en: 'Runs, events, and snapshots use stable formats',
},
body: {
zh: '一次任务可以保存 Snapshot、事件、Trace、Artifact、验证结果和 Checkpoint;应用可以据此查询、审计或恢复。',
en: 'A task can save snapshots, events, traces, artifacts, verification results, and checkpoints for queries, audit, or recovery.',
},
tags: ['atomic', 'replayable', 'auditable'],
},
{
className: 'a3s-bento-card--extend',
eyebrow: { zh: '扩展', en: 'EXTENSIONS' },
title: {
zh: '工具、上下文与存储都有扩展接口',
en: 'Extend tools, context, and storage',
},
body: {
zh: 'MCP、Skills、ContextProvider、MemoryStore、SessionStore、Workspace 服务和自定义工具都可以替换或扩展。',
en: 'Replace or extend MCP, Skills, ContextProvider, MemoryStore, SessionStore, workspace services, and custom tools.',
},
tags: ['MCP', 'Skills', 'traits'],
},
{
className: 'a3s-bento-card--workspace',
eyebrow: { zh: '工作区', en: 'WORKSPACE' },
title: {
zh: '本地、S3 与远程 Workspace 分开声明能力',
en: 'Local, S3, and remote workspaces declare capabilities',
},
body: {
zh: '代码导航与文件工具都来自你选择的 Workspace。远程或对象存储后端不能运行本地命令时,Bash 和 Git 就不会暴露给模型。',
en: 'Code navigation and file tools come from the workspace you select. If a remote or object-backed workspace cannot run local commands, Bash and Git are not shown to the model.',
},
tags: ['symbols', 'diagnostics', 'local / S3 / remote'],
},
];
const surfaces = [
{
key: 'terminal',
name: 'Terminal',
packageName: 'a3s code',
href: 'https://github.com/A3S-Lab/a3s',
description: {
zh: '开箱即用的终端界面,可以查看推理、工具调用、确认提示、任务进度和 Diff。',
en: 'A ready-to-run terminal UI for reasoning, tool calls, approval prompts, task progress, and diffs.',
},
command: 'a3s code',
},
{
key: 'rust',
name: 'Rust',
packageName: 'a3s-code-core',
href: 'https://crates.io/crates/a3s-code-core',
description: {
zh: '完整的异步 Runtime API,以及用于接入自定义能力的公共 Trait。',
en: 'The complete async runtime API, plus public traits for custom integrations.',
},
command: 'cargo add a3s-code-core',
},
{
key: 'node',
name: 'Node.js',
packageName: '@a3s-lab/code',
href: 'https://www.npmjs.com/package/@a3s-lab/code',
description: {
zh: '通过 N-API 提供原生绑定,覆盖会话、事件流、工具、存储、编排和 MCP。',
en: 'Native N-API bindings for sessions, event streams, tools, storage, orchestration, and MCP.',
},
command: 'npm install @a3s-lab/code',
},
{
key: 'python',
name: 'Python',
packageName: 'a3s-code',
href: 'https://pypi.org/project/a3s-code/',
description: {
zh: '通过 PyO3 提供原生包,同时支持同步和异步 API。',
en: 'A native PyO3 package with both synchronous and asynchronous APIs.',
},
command: 'python -m pip install a3s-code',
},
];
const runtimeLayers = [
{
id: 'surfaces',
code: 'L01 / SURFACES',
title: { zh: '接入方式', en: 'Ways to use it' },
body: {
zh: '同一套 Runtime 可以直接跑在终端里,也可以通过 Rust、Node.js 或 Python 接进你的应用。接口不同,执行流程一致。',
en: 'Run the same runtime in a terminal or embed it through Rust, Node.js, or Python. The APIs differ; the execution flow stays the same.',
},
tags: ['a3s code', 'Rust', 'Node.js', 'Python'],
},
{
id: 'session',
code: 'L02 / AGENT API',
title: { zh: 'Agent 与 Session', en: 'Agent and session' },
body: {
zh: 'Agent 读取配置并准备共享能力;AgentSession 把这些能力连接到一个项目目录和一段对话。',
en: 'Agent loads configuration and shared capabilities. AgentSession connects them to one project workspace and one conversation.',
},
tags: ['Agent', 'AgentSession', 'lifecycle'],
},
{
id: 'context',
code: 'L03 / INTELLIGENCE',
title: { zh: '上下文、记忆与模型', en: 'Context, memory, and models' },
body: {
zh: 'ContextAssembler 挑选输入并控制大小,Memory 保存可复用信息,模型适配器负责流式输出、工具调用、结构化结果和取消。',
en: 'ContextAssembler selects and sizes inputs, memory keeps reusable information, and model adapters handle streaming, tool calls, structured output, and cancellation.',
},
tags: ['ContextAssembler', 'Memory', 'LlmClient'],
},
{
id: 'governance',
code: 'L04 / GOVERNANCE',
title: { zh: '权限与执行检查', en: 'Permission and execution checks' },
body: {
zh: '工具真正执行前,Runtime 会校验参数并检查能力和权限;再按配置进行用户确认、预算限制、沙箱隔离或取消。',
en: 'Before a tool runs, the runtime validates its arguments and checks capabilities and permissions, then applies approval, budget, sandbox, and cancellation rules.',
},
tags: ['validate', 'permission', 'confirm', 'budget'],
},
{
id: 'tools',
code: 'L05 / WORKSPACE',
title: { zh: '项目文件与工具', en: 'Project files and tools' },
body: {
zh: '文件、搜索、Shell、Git、网页、代码导航、MCP、Skills 和子任务,会按当前 Workspace 的能力和权限开放。',
en: 'Files, search, shell, Git, web, code navigation, MCP, Skills, and child tasks are enabled according to the current workspace and its permissions.',
},
tags: ['files', 'git', 'web', 'MCP', 'Skills'],
},
{
id: 'evidence',
code: 'L06 / DURABILITY',
title: { zh: '事件、记录与恢复', en: 'Events, records, and recovery' },
body: {
zh: 'AgentEvent 把执行过程交给界面;Run、Trace、Artifact、验证报告和 SessionSnapshotV1 用来排查问题、审计和恢复任务。',
en: 'AgentEvent feeds the execution stream to your UI. Runs, traces, artifacts, verification reports, and SessionSnapshotV1 support debugging, audit, and recovery.',
},
tags: ['EventEnvelopeV1', 'Run', 'Artifact', 'Snapshot'],
},
] satisfies Array<{
id: string;
code: string;
title: Localized;
body: Localized;
tags: string[];
}>;
const runtimeFlowSteps = [
{
id: 'admission',
code: '01',
stage: 'SESSION',
title: { zh: '开始一次 Run', en: 'Admit one run' },
path: 'prompt → run_id',
summary: {
zh: '同一个 Session 不会同时改写两份对话历史。请求先取得 single-flight 运行权,再生成这次执行的身份与取消信号。',
en: 'A session never mutates two conversation histories at once. The request first acquires its single-flight lease, then receives a run identity and cancellation signal.',
},
input: {
zh: 'session.stream(prompt)',
en: 'session.stream(prompt)',
},
action: {
zh: '检查 Session 状态并取得运行权',
en: 'Check session state and acquire the run lease',
},
output: {
zh: 'run_id + InvocationContext',
en: 'run_id + InvocationContext',
},
event: 'run_start',
trace: {
zh: 'Run 已准入,后续模型与工具共享同一个 run_id。',
en: 'The run is admitted; model and tool work now share one run_id.',
},
tags: ['AgentSession', 'RunAdmission', 'CancellationToken'],
},
{
id: 'context',
code: '02',
stage: 'CONTEXT',
title: { zh: '组装本轮上下文', en: 'Assemble turn context' },
path: 'history → messages',
summary: {
zh: '历史、项目指令、记忆和 Workspace 信息不会整包塞给模型。ContextAssembler 会在预算内挑选内容,并附上本轮可见的工具定义。',
en: 'History, project instructions, memory, and workspace data are not dumped into the model. ContextAssembler selects what fits and attaches the visible tool schemas.',
},
input: {
zh: 'history + instructions + memory',
en: 'history + instructions + memory',
},
action: {
zh: '按上下文预算筛选、排序并组装',
en: 'Select, rank, and assemble within the context budget',
},
output: {
zh: 'messages[] + tools[]',
en: 'messages[] + tools[]',
},
event: 'turn_start',
trace: {
zh: '模型只看到本轮需要的上下文和允许使用的工具。',
en: 'The model sees only the context and tools available to this turn.',
},
tags: ['ContextAssembler', 'Memory', 'ToolDefinition'],
},
{
id: 'model',
code: '03',
stage: 'MODEL',
title: { zh: '模型决定下一步', en: 'Let the model decide' },
path: 'messages → tool call',
summary: {
zh: '受约束的模型调用会检查预算与取消状态。这个示例里,模型没有直接操作文件,而是提出一次只读搜索。',
en: 'The scoped model call checks budget and cancellation. In this example the model does not touch files directly; it proposes a read-only search.',
},
input: {
zh: 'messages[] + tools[]',
en: 'messages[] + tools[]',
},
action: {
zh: '流式生成文本或结构化工具请求',
en: 'Stream text or produce a structured tool request',
},
output: {
zh: 'grep({ pattern: "TODO|FIXME" })',
en: 'grep({ pattern: "TODO|FIXME" })',
},
event: 'tool_start',
trace: {
zh: '模型提出 grep 调用,但此时工具还没有执行。',
en: 'The model proposes grep, but the tool has not executed yet.',
},
tags: ['ScopedLlmClient', 'BudgetGuard', 'ToolCall'],
},
{
id: 'governance',
code: '04',
stage: 'GOVERNANCE',
title: { zh: '工具调用先过检查', en: 'Govern the tool call' },
path: 'tool call → allowed call',
summary: {
zh: '所有模型工具、嵌套工具和委派工具都经过同一个 ToolInvoker;内部调用不能绕过权限、Hook、确认、预算或超时。',
en: 'Model, nested, and delegated tools all pass through one ToolInvoker. Internal calls cannot bypass permissions, hooks, approvals, budgets, or timeouts.',
},
input: {
zh: 'ToolInvocation: grep(...)',
en: 'ToolInvocation: grep(...)',
},
action: {
zh: '参数 → 权限 → Hook → 确认 → 预算 → 超时',
en: 'schema → permission → hook → approval → budget → timeout',
},
output: {
zh: '受控且带作用域的工具调用',
en: 'A governed, scoped invocation',
},
event: 'tool_execution_start',
trace: {
zh: '只读 grep 通过当前策略,Runtime 允许它进入 Workspace。',
en: 'Read-only grep passes the active policy and may enter the workspace.',
},
tags: ['ToolInvoker', 'PermissionPolicy', 'HookExecutor'],
},
{
id: 'execution',
code: '05',
stage: 'WORKSPACE',
title: { zh: '在 Workspace 执行', en: 'Execute in the workspace' },
path: 'allowed call → result',
summary: {
zh: '真正接触文件、Shell、Git、MCP 或子任务的是 Workspace 工具。结果会先清理和记录,再送回模型继续判断。',
en: 'Workspace tools are what actually reach files, shell, Git, MCP, or child tasks. Results are sanitized and recorded before returning to the model.',
},
input: {
zh: '受控 grep 调用',
en: 'Governed grep invocation',
},
action: {
zh: '执行搜索,清理输出并保存大结果',
en: 'Run the search, sanitize output, and store large results',
},
output: {
zh: '3 matches + Artifact 引用',
en: '3 matches + Artifact reference',
},
event: 'tool_end',
trace: {
zh: '结果回到本轮对话;如果模型还需要工具,会回到步骤 03。',
en: 'The result returns to the turn. If another tool is needed, the flow returns to step 03.',
},
tags: ['Workspace', 'ToolResult', 'Artifact'],
},
{
id: 'evidence',
code: '06',
stage: 'EVIDENCE',
title: { zh: '把过程交给界面和存储', en: 'Publish events and evidence' },
path: 'events → UI / snapshot',
summary: {
zh: '界面消费统一事件格式,不需要从文本里猜进度。启用 autoSave 或调用 save() 时,对话、Trace、Artifact 与验证结果会作为一个完整快照提交。',
en: 'The UI consumes a stable event format instead of guessing progress from text. With autoSave or save(), conversation, traces, artifacts, and verification are committed as one snapshot.',
},
input: {
zh: 'AgentEvent + AgentResult',
en: 'AgentEvent + AgentResult',
},
action: {
zh: '投影事件,并在保存时提交完整 generation',
en: 'Project events and commit a complete generation on save',
},
output: {
zh: 'EventEnvelopeV1 + SessionSnapshotV1',
en: 'EventEnvelopeV1 + SessionSnapshotV1',
},
event: 'end · snapshot',
trace: {
zh: '应用拿到可渲染的结果;Session 也具备排查、审计与恢复依据。',
en: 'The app receives renderable output, while the session keeps evidence for debugging, audit, and recovery.',
},
tags: ['EventEnvelopeV1', 'RunRecord', 'SessionSnapshotV1'],
},
] satisfies Array<{
id: string;
code: string;
stage: string;
title: Localized;
path: string;
summary: Localized;
input: Localized;
action: Localized;
output: Localized;
event: string;
trace: Localized;
tags: string[];
}>;
type RuntimeFlowStep = (typeof runtimeFlowSteps)[number];
const copy = {
zh: {
eyebrow: 'OPEN SOURCE · EMBEDDABLE AGENT RUNTIME',
titleLead: '把 A3S Code',
titleAccent: '接进现有产品',
subtitle:
'A3S Code 提供 Agent 会话、工具调用、权限确认、事件流和任务恢复。你可以直接使用 a3s code,也可以通过 Rust、Node.js 或 Python SDK 嵌入现有应用。',
docs: '开始使用',
github: '查看 GitHub',
copy: '复制',
copied: '已复制',
turn: '一次 Agent 执行会经过什么',
proposal: '模型请求调用工具',
governed: '执行前检查',
result: '执行结果写入事件记录',
context: '项目上下文 + 记忆',
model: '模型',
guard: '参数 → 权限 → 确认 → 预算 → 沙箱',
evidence: 'Run · Trace · Artifact · Snapshot',
record: '执行记录',
surfacesLabel: '四种接入方式,同一套 Runtime',
whyEyebrow: 'WHY A3S CODE',
whyTitle: '工具执行之前,先检查参数、权限和确认状态',
whyBody:
'模型给出的工具调用不会直接落到文件系统或 Shell。Runtime 先完成检查,再把执行事件和结果交给应用。',
architectureEyebrow: 'HOW IT RUNS',
architectureTitle: '用 Python 走完一次执行',
architectureBody:
'示例使用仓库中实际提供的 a3s_code API。滚动或点击步骤,代码会逐步加入 Session、上下文限制、权限、事件流和持久化。',
architectureAlt:
'A3S Code 一次执行的交互流程图,展示请求的输入、Runtime 的处理和每一步输出。',
capabilitiesEyebrow: 'WHAT YOU GET',
capabilitiesTitle: 'Runtime 提供的五类能力',
capabilitiesBody:
'工具、模型、任务记录、扩展接口和 Workspace 各自独立。应用可以只配置当前场景需要的部分。',
surfacesEyebrow: 'USE IT YOUR WAY',
surfacesTitle: '直接运行 CLI,或使用三种 SDK',
surfacesBody:
'终端版用于直接操作项目;Rust crate、Node.js 包和 Python 包用于 IDE、Runner、服务端或自有界面。',
boundariesEyebrow: 'WHAT STAYS YOURS',
boundariesTitle: 'Runtime 负责执行;应用负责账号、凭据和界面',
boundaryItems: [
'A3S Code Core 提供 Agent Runtime,不提供托管服务,也不规定界面应该长什么样。',
'a3s code 的终端界面由独立的 A3S CLI 提供。',
'账号、凭据、部署方式,以及哪些应用工具可以直接调用,仍由你的应用决定。',
],
boundaryLink: '查看架构说明',
boundaryCoreLabel: 'A3S CODE',
boundaryCoreRole: '执行 Agent 与工具',
boundaryContract: 'API 与事件',
boundaryHostLabel: '你的应用',
boundaryHostRole: '账号、权限与界面',
stackTitle: 'A3S CODE / 一次执行',
stackHint: '点击步骤查看输入与输出',
stackHintMobile: '点击步骤展开',
stackTop: '产品',
stackBottom: '记录',
flowTaskLabel: '示例任务',
flowTask: '检查仓库并列出发布阻塞项',
flowPlay: '演示',
flowRunning: '运行中',
flowInput: '收到',
flowAction: 'A3S 处理',
flowOutput: '交给下一步',
flowEvent: '对应事件',
flowObjects: '相关对象',
tutorialStep: '步骤',
tutorialCode: '代码',
tutorialLayers: '当前负责的层',
tutorialScroll: '继续向下',
ctaEyebrow: 'TRY IT',
ctaTitle: '从一个只读任务开始',
ctaBody:
'安装 a3s code 后,在已有仓库里执行一次检查;需要嵌入时再选择 Rust、Node.js 或 Python SDK。',
ctaPrimary: '查看快速开始',
ctaSecondary: '查看 API',
footer: 'MIT 开源 · Rust 编写 · 支持 Terminal / Rust / Node.js / Python',
},
en: {
eyebrow: 'OPEN SOURCE · EMBEDDABLE AGENT RUNTIME',
titleLead: 'Add A3S Code',
titleAccent: 'to an existing product',
subtitle:
'A3S Code provides agent sessions, tool execution, approvals, event streaming, and task recovery. Run a3s code directly or embed the Rust, Node.js, or Python SDK.',
docs: 'Get started',
github: 'View on GitHub',
copy: 'Copy',
copied: 'Copied',
turn: 'What happens during one agent turn',
proposal: 'The model requests a tool',
governed: 'Checks before execution',
result: 'The result enters the event stream',
context: 'project context + memory',
model: 'model',
guard: 'arguments → permission → approval → budget → sandbox',
evidence: 'Run · Trace · Artifact · Snapshot',
record: 'run record',
surfacesLabel: 'Four ways in, one runtime',
whyEyebrow: 'WHY A3S CODE',
whyTitle: 'Check arguments, permissions, and approvals before execution',
whyBody:
'Model tool calls do not go straight to the filesystem or shell. The runtime completes its checks first, then sends execution events and results to the application.',
architectureEyebrow: 'HOW IT RUNS',
architectureTitle: 'Follow one complete run in Python',
architectureBody:
'The example uses the actual a3s_code API in this repository. Scroll or select a step to add the Session, context limits, policy, event stream, and persistence.',
architectureAlt:
'An interactive A3S Code run showing the input, runtime work, and output of each step.',
capabilitiesEyebrow: 'WHAT YOU GET',
capabilitiesTitle: 'Five parts of the runtime',
capabilitiesBody:
'Tools, models, run data, extension interfaces, and workspaces are configured separately. An application can enable only the parts it needs.',
surfacesEyebrow: 'USE IT YOUR WAY',
surfacesTitle: 'Run the CLI or use one of three SDKs',
surfacesBody:
'Use the terminal app directly in a repository. Use the Rust crate, Node.js package, or Python package in an IDE, runner, server, or custom interface.',
boundariesEyebrow: 'WHAT STAYS YOURS',
boundariesTitle: 'The runtime executes; the app owns accounts and access',
boundaryItems: [
'A3S Code Core provides the agent runtime. It is not a hosted service and does not dictate your UI.',
'The a3s code terminal interface comes from the separate A3S CLI.',
'Accounts, credentials, deployment, and direct access to application tools remain under your control.',
],
boundaryLink: 'Read the architecture guide',
boundaryCoreLabel: 'A3S CODE',
boundaryCoreRole: 'RUNS AGENTS + TOOLS',
boundaryContract: 'APIs + EVENTS',
boundaryHostLabel: 'YOUR APP',
boundaryHostRole: 'OWNS UI + ACCESS',
stackTitle: 'A3S CODE / ONE RUN',
stackHint: 'SELECT A STEP TO SEE INPUT AND OUTPUT',
stackHintMobile: 'TAP A STEP TO EXPAND',
stackTop: 'PRODUCT',
stackBottom: 'RECORDS',
flowTaskLabel: 'SAMPLE REQUEST',
flowTask: 'Inspect the repository and list release blockers',
flowPlay: 'PLAY',
flowRunning: 'RUNNING',
flowInput: 'INPUT',
flowAction: 'A3S DOES',
flowOutput: 'OUTPUT',
flowEvent: 'EVENT',
flowObjects: 'OBJECTS',
tutorialStep: 'STEP',
tutorialCode: 'CODE',
tutorialLayers: 'ACTIVE LAYER',
tutorialScroll: 'KEEP SCROLLING',
ctaEyebrow: 'TRY IT',
ctaTitle: 'Start with a read-only task',
ctaBody:
'Install a3s code and run one inspection in an existing repository. Choose the Rust, Node.js, or Python SDK when you are ready to embed it.',
ctaPrimary: 'Open the quick start',
ctaSecondary: 'Open the API reference',
footer: 'MIT licensed · Built in Rust · Terminal / Rust / Node.js / Python',
},
};
function localeValue(value: Localized, locale: Locale) {
return value[locale];
}
function ArrowIcon() {
return (
<svg aria-hidden="true" viewBox="0 0 16 16">
<path d="M3 8h9M8.5 3.5 13 8l-4.5 4.5" />
</svg>
);
}
function AnimatedButtonBorder() {
return (
<span aria-hidden="true" className="a3s-button-orbit">
<span className="a3s-button-comet" />
</span>
);
}
function GitHubIcon() {
return (
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M12 .8A11.5 11.5 0 0 0 8.36 23.2c.58.1.79-.25.79-.56v-2.2c-3.22.7-3.9-1.36-3.9-1.36-.52-1.34-1.28-1.7-1.28-1.7-1.05-.72.08-.7.08-.7 1.16.08 1.77 1.2 1.77 1.2 1.03 1.78 2.71 1.27 3.37.97.1-.75.4-1.27.73-1.56-2.57-.3-5.28-1.3-5.28-5.7 0-1.27.45-2.3 1.19-3.11-.12-.3-.52-1.48.11-3.07 0 0 .97-.31 3.16 1.19a10.86 10.86 0 0 1 5.76 0c2.2-1.5 3.16-1.19 3.16-1.19.63 1.6.23 2.77.11 3.07.74.81 1.19 1.84 1.19 3.1 0 4.43-2.71 5.4-5.29 5.69.42.36.79 1.07.79 2.16v3.2c0 .31.21.67.8.55A11.5 11.5 0 0 0 12 .8Z" />
</svg>
);
}
function InstallSwitcher({
locale,
labels,
}: {
locale: Locale;
labels: (typeof copy)[Locale];
}) {
const [activeId, setActiveId] =
useState<(typeof installCommands)[number]['id']>('unix');
const [copied, setCopied] = useState(false);
const active =
installCommands.find((item) => item.id === activeId) ?? installCommands[0];
async function copyActiveCommand() {
await navigator.clipboard.writeText(active.command);
setCopied(true);
window.setTimeout(() => setCopied(false), 1600);
}
return (
<div className="a3s-install">
<div className="a3s-install-tabs" role="tablist" aria-label="Install">
{installCommands.map((item) => (
<button
aria-selected={active.id === item.id}
className={active.id === item.id ? 'is-active' : undefined}
key={item.id}
onClick={() => {
setActiveId(item.id);
setCopied(false);
}}
role="tab"
type="button"
>
{item.label}
</button>
))}
</div>
<div className="a3s-command" role="tabpanel">
<pre>
<code>{active.command}</code>
</pre>
<button
className="a3s-copy-button"
onClick={copyActiveCommand}
type="button"
>
<span aria-hidden="true">{copied ? '✓' : '⧉'}</span>
{copied ? labels.copied : labels.copy}
</button>
</div>
<span className="a3s-install-locale" aria-hidden="true">
{locale === 'zh' ? 'ZH' : 'EN'}
</span>
</div>
);
}
function RuntimeFlowDetail({
className,
step,
labels,
locale,
}: {
className: string;
step: RuntimeFlowStep;
labels: (typeof copy)[Locale];
locale: Locale;
}) {
return (
<article
className={`a3s-runtime-flow-detail ${className}`}
data-step={step.id}
>
<header>
<span>
STEP {step.code} / {step.stage}
</span>
<h2>{localeValue(step.title, locale)}</h2>
<p>{localeValue(step.summary, locale)}</p>
</header>
<div className="a3s-runtime-flow-io">
<div>
<small>{labels.flowInput}</small>
<code>{localeValue(step.input, locale)}</code>
</div>
<div>
<small>{labels.flowAction}</small>
<code>{localeValue(step.action, locale)}</code>
</div>
<div>
<small>{labels.flowOutput}</small>
<code>{localeValue(step.output, locale)}</code>
</div>
</div>
<footer>
<div className="a3s-runtime-flow-event">
<small>{labels.flowEvent}</small>
<code>
<i aria-hidden="true" />
{step.event}
</code>
<p>{localeValue(step.trace, locale)}</p>
</div>
<div className="a3s-runtime-flow-objects">
<small>{labels.flowObjects}</small>
<span>
{step.tags.map((tag) => (
<code key={tag}>{tag}</code>
))}
</span>
</div>
</footer>
</article>
);
}
function RuntimeExecutionFlow({
labels,
locale,
}: {
labels: (typeof copy)[Locale];
locale: Locale;
}) {
const [activeIndex, setActiveIndex] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
const active = runtimeFlowSteps[activeIndex] ?? runtimeFlowSteps[0];
useEffect(() => {
if (!isPlaying) return undefined;
const delay = activeIndex === runtimeFlowSteps.length - 1 ? 720 : 920;
const timer = window.setTimeout(() => {
if (activeIndex === runtimeFlowSteps.length - 1) {
setIsPlaying(false);
} else {
setActiveIndex((index) =>
Math.min(index + 1, runtimeFlowSteps.length - 1),
);
}
}, delay);
return () => window.clearTimeout(timer);
}, [activeIndex, isPlaying]);
function selectStep(index: number) {
setIsPlaying(false);
setActiveIndex(index);
}
function playFlow() {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
setActiveIndex(runtimeFlowSteps.length - 1);
setIsPlaying(false);
return;
}
setActiveIndex(0);
setIsPlaying(true);
}
function handleStepKeyDown(
event: ReactKeyboardEvent<HTMLButtonElement>,
index: number,
) {
let nextIndex: number | undefined;
if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
nextIndex = (index + 1) % runtimeFlowSteps.length;
} else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
nextIndex =
(index - 1 + runtimeFlowSteps.length) % runtimeFlowSteps.length;
} else if (event.key === 'Home') {
nextIndex = 0;
} else if (event.key === 'End') {
nextIndex = runtimeFlowSteps.length - 1;
}
if (nextIndex === undefined) return;
event.preventDefault();
selectStep(nextIndex);
const inspector = event.currentTarget.closest('.a3s-runtime-inspector');
inspector
?.querySelectorAll<HTMLButtonElement>('.a3s-runtime-flow-step')
.item(nextIndex)
.focus();
}
return (
<div className="a3s-runtime-inspector" aria-label={labels.architectureAlt}>
<header className="a3s-runtime-inspector-header">
<span>
<i aria-hidden="true" />
{labels.stackTitle}
</span>
<div>
<button
aria-pressed={isPlaying}
className={isPlaying ? 'is-playing' : ''}
onClick={playFlow}
type="button"
>
<i aria-hidden="true" />
{isPlaying ? labels.flowRunning : labels.flowPlay}
</button>
<b>
{String(activeIndex + 1).padStart(2, '0')} /{' '}
{String(runtimeFlowSteps.length).padStart(2, '0')}
</b>
</div>
</header>
<div className="a3s-runtime-flow-request">
<span>{labels.flowTaskLabel}</span>
<code>
<i aria-hidden="true">>>></i>
session.stream("{labels.flowTask}")
</code>
</div>
<div className="a3s-runtime-inspector-body">
<nav
aria-label={labels.architectureAlt}
className="a3s-runtime-flow-nav"
>
<div className="a3s-runtime-flow-track" aria-hidden="true">
<span
style={{
height: `${(activeIndex / (runtimeFlowSteps.length - 1)) * 100}%`,
}}
/>
</div>
{runtimeFlowSteps.map((step, index) => (
<div className="a3s-runtime-flow-row" key={step.id}>
<button
aria-pressed={activeIndex === index}
className={[
'a3s-runtime-flow-step',
`a3s-runtime-flow-step--${step.id}`,
activeIndex === index ? 'is-active' : '',
activeIndex > index ? 'is-complete' : '',
]
.filter(Boolean)
.join(' ')}
onClick={() => selectStep(index)}
onFocus={() => selectStep(index)}
onKeyDown={(event) => handleStepKeyDown(event, index)}
onMouseEnter={() => {
if (!isPlaying) setActiveIndex(index);
}}
type="button"
>
<span>{step.code}</span>
<span>
<small>{step.stage}</small>
<strong>{localeValue(step.title, locale)}</strong>
<em>{step.path}</em>
</span>
<i aria-hidden="true" />
</button>
{activeIndex === index ? (
<RuntimeFlowDetail
className="a3s-runtime-flow-detail--mobile"
key={active.id}
labels={labels}
locale={locale}
step={active}
/>
) : null}
</div>
))}
</nav>
<RuntimeFlowDetail
className="a3s-runtime-flow-detail--desktop"
key={active.id}
labels={labels}
locale={locale}
step={active}
/>
</div>
</div>
);
}
const runtimeFocusHandler: AnnotationHandler = {
name: 'focus',
onlyIfAnnotated: true,
Line: (props) => (
<InnerLine
className="a3s-code-line"
data-line-number={props.lineNumber}
merge={props}
/>
),
AnnotatedLine: ({ annotation: _annotation, ...props }) => (
<InnerLine
className="a3s-code-line is-focused"
data-focus="true"
data-line-number={props.lineNumber}
merge={props}
/>
),