induscode 0.1.0__py3-none-any.whl
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.
- induscode/__init__.py +56 -0
- induscode/addons/__init__.py +176 -0
- induscode/addons/contract.py +923 -0
- induscode/addons/dispatch/__init__.py +43 -0
- induscode/addons/dispatch/event_dispatcher.py +348 -0
- induscode/addons/dispatch/tool_interceptor.py +349 -0
- induscode/addons/host.py +469 -0
- induscode/addons/loader.py +314 -0
- induscode/addons/manifest.py +232 -0
- induscode/addons/surface.py +199 -0
- induscode/boot/__init__.py +108 -0
- induscode/boot/auth_vault.py +323 -0
- induscode/boot/boot.py +210 -0
- induscode/boot/contract.py +223 -0
- induscode/boot/invocation.py +117 -0
- induscode/boot/runners/__init__.py +42 -0
- induscode/boot/runners/link_runner.py +82 -0
- induscode/boot/runners/oneshot_runner.py +85 -0
- induscode/boot/runners/registry.py +46 -0
- induscode/boot/runners/repl_runner.py +340 -0
- induscode/boot/runners/session.py +549 -0
- induscode/boot/stages.py +198 -0
- induscode/boot/upgrade/__init__.py +36 -0
- induscode/boot/upgrade/apply.py +125 -0
- induscode/boot/upgrade/upgrades.py +136 -0
- induscode/briefing/__init__.py +115 -0
- induscode/briefing/compose.py +414 -0
- induscode/briefing/contract.py +528 -0
- induscode/briefing/macros.py +721 -0
- induscode/briefing/skills.py +417 -0
- induscode/capability_deck/__init__.py +233 -0
- induscode/capability_deck/bridge_ledger/__init__.py +66 -0
- induscode/capability_deck/bridge_ledger/key.py +181 -0
- induscode/capability_deck/bridge_ledger/ledger.py +276 -0
- induscode/capability_deck/bridge_ledger/network.py +336 -0
- induscode/capability_deck/builtin_bridge.py +358 -0
- induscode/capability_deck/cards/__init__.py +116 -0
- induscode/capability_deck/cards/bg_process.py +482 -0
- induscode/capability_deck/cards/memory.py +226 -0
- induscode/capability_deck/cards/saas.py +280 -0
- induscode/capability_deck/cards/task.py +256 -0
- induscode/capability_deck/cards/todo.py +312 -0
- induscode/capability_deck/contract.py +450 -0
- induscode/capability_deck/manifest.py +126 -0
- induscode/capability_deck/provision.py +217 -0
- induscode/channels/__init__.py +146 -0
- induscode/channels/contract.py +585 -0
- induscode/channels/framer.py +132 -0
- induscode/channels/link/__init__.py +50 -0
- induscode/channels/link/dialog.py +246 -0
- induscode/channels/link/driver.py +308 -0
- induscode/channels/link/server.py +217 -0
- induscode/channels/oneshot.py +178 -0
- induscode/channels/ops.py +140 -0
- induscode/channels/session_ops.py +172 -0
- induscode/conductor/__init__.py +240 -0
- induscode/conductor/catalog.py +309 -0
- induscode/conductor/conductor.py +1084 -0
- induscode/conductor/contract.py +1035 -0
- induscode/conductor/matcher.py +291 -0
- induscode/conductor/serialize.py +575 -0
- induscode/conductor/signal_hub.py +382 -0
- induscode/conductor/skill_parse.py +294 -0
- induscode/conductor/transcript_store.py +449 -0
- induscode/console/__init__.py +236 -0
- induscode/console/app.py +1677 -0
- induscode/console/components/__init__.py +62 -0
- induscode/console/components/banner.py +499 -0
- induscode/console/components/banner_sweep.py +188 -0
- induscode/console/components/emblem.py +181 -0
- induscode/console/components/status_bar.py +102 -0
- induscode/console/contract.py +836 -0
- induscode/console/input/__init__.py +107 -0
- induscode/console/input/chord.py +197 -0
- induscode/console/input/dir_reader.py +113 -0
- induscode/console/input/intents.py +258 -0
- induscode/console/input/providers.py +469 -0
- induscode/console/mount.py +137 -0
- induscode/console/overlays/__init__.py +94 -0
- induscode/console/overlays/auth.py +503 -0
- induscode/console/overlays/pickers.py +526 -0
- induscode/console/overlays/router.py +129 -0
- induscode/console/overlays/sessions.py +232 -0
- induscode/console/reducer.py +145 -0
- induscode/console/resume_picker.py +156 -0
- induscode/console/slash_commands/__init__.py +78 -0
- induscode/console/slash_commands/builtins.py +254 -0
- induscode/console/slash_commands/dynamic.py +217 -0
- induscode/console/slash_commands/integrations.py +949 -0
- induscode/console/slash_commands/transcript.py +404 -0
- induscode/console/slash_commands/workbench.py +430 -0
- induscode/console/startup.py +434 -0
- induscode/console/theme/__init__.py +44 -0
- induscode/console/theme/adapter.py +168 -0
- induscode/console/theme/palette.py +128 -0
- induscode/console/theme/resolve.py +123 -0
- induscode/console/theme/tokens.py +185 -0
- induscode/console_slash/__init__.py +111 -0
- induscode/console_slash/contract.py +185 -0
- induscode/console_slash/registry.py +140 -0
- induscode/console_slash/resolve.py +194 -0
- induscode/console_slash/shared.py +172 -0
- induscode/entry.py +108 -0
- induscode/insight/__init__.py +153 -0
- induscode/insight/collector.py +73 -0
- induscode/insight/replay.py +305 -0
- induscode/insight/wrapper.py +1115 -0
- induscode/kit/__init__.py +82 -0
- induscode/kit/clipboard_image.py +215 -0
- induscode/kit/external_editor.py +120 -0
- induscode/kit/image.py +188 -0
- induscode/kit/shell.py +89 -0
- induscode/kit/tool_fetch.py +288 -0
- induscode/launch/__init__.py +224 -0
- induscode/launch/catalog.py +310 -0
- induscode/launch/contract.py +569 -0
- induscode/launch/credentials.py +852 -0
- induscode/launch/invocation/__init__.py +39 -0
- induscode/launch/invocation/attachments.py +281 -0
- induscode/launch/invocation/flags.py +210 -0
- induscode/launch/invocation/read.py +369 -0
- induscode/launch/invocation/usage.py +110 -0
- induscode/launch/oauth.py +808 -0
- induscode/launch/packages.py +299 -0
- induscode/launch/pickers.py +291 -0
- induscode/py.typed +0 -0
- induscode/runtime_bridge/__init__.py +166 -0
- induscode/runtime_bridge/bridges/__init__.py +66 -0
- induscode/runtime_bridge/bridges/_drive.py +268 -0
- induscode/runtime_bridge/bridges/builtins.py +177 -0
- induscode/runtime_bridge/bridges/claude_cli.py +198 -0
- induscode/runtime_bridge/bridges/codex_cli.py +203 -0
- induscode/runtime_bridge/bridges/indusagi_cli.py +217 -0
- induscode/runtime_bridge/broker.py +397 -0
- induscode/runtime_bridge/contract.py +734 -0
- induscode/runtime_bridge/sink.py +351 -0
- induscode/sessions/__init__.py +25 -0
- induscode/sessions/contract.py +119 -0
- induscode/sessions/library.py +350 -0
- induscode/settings/__init__.py +47 -0
- induscode/settings/contract.py +313 -0
- induscode/settings/manager.py +268 -0
- induscode/transcript_export/__init__.py +109 -0
- induscode/transcript_export/contract.py +522 -0
- induscode/transcript_export/publish.py +455 -0
- induscode/transcript_export/sgr.py +566 -0
- induscode/transcript_export/template.py +319 -0
- induscode/transcript_export/theme_bridge.py +325 -0
- induscode/window_budget/__init__.py +76 -0
- induscode/window_budget/budget/__init__.py +26 -0
- induscode/window_budget/budget/estimate.py +273 -0
- induscode/window_budget/budget/gate.py +60 -0
- induscode/window_budget/budget/slice.py +145 -0
- induscode/window_budget/condenser.py +170 -0
- induscode/window_budget/contract.py +329 -0
- induscode/window_budget/summarize/__init__.py +33 -0
- induscode/window_budget/summarize/condense.py +212 -0
- induscode/window_budget/summarize/prompt.py +241 -0
- induscode/workspace/__init__.py +30 -0
- induscode/workspace/brand.py +96 -0
- induscode/workspace/locator.py +269 -0
- induscode-0.1.0.dist-info/METADATA +97 -0
- induscode-0.1.0.dist-info/RECORD +167 -0
- induscode-0.1.0.dist-info/WHEEL +4 -0
- induscode-0.1.0.dist-info/entry_points.txt +3 -0
- induscode-0.1.0.dist-info/licenses/CREDITS.md +22 -0
- induscode-0.1.0.dist-info/licenses/NOTICE +7 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Runtime-bridge subsystem — public barrel (port of TS
|
|
2
|
+
``src/runtime-bridge/index.ts``).
|
|
3
|
+
|
|
4
|
+
Re-exports the FROZEN provider-routing contract: the external-runtime
|
|
5
|
+
annotation (:class:`ExternalRuntimeSpec` + the ``bridge:<adapter>`` endpoint
|
|
6
|
+
convention), the provider-neutral :data:`NormalizedEvent` union, the single
|
|
7
|
+
:class:`BridgeEventSink` push-stream helper, the injectable
|
|
8
|
+
:class:`ChildTransport` boundary, and the routing surface itself
|
|
9
|
+
(:class:`RuntimeBridge`, :class:`RuntimeBroker`, :data:`RuntimeRoute`) —
|
|
10
|
+
plus the behavior modules: the concrete bridges under ``bridges``, the sink
|
|
11
|
+
implementation, and the broker. Consumers import the routing surface from
|
|
12
|
+
``induscode.runtime_bridge`` rather than reaching into individual modules.
|
|
13
|
+
|
|
14
|
+
Port note: runtime-bridge is consumed only via this barrel (the TS build's
|
|
15
|
+
``src/index.ts: export * as runtimeBridge``) — it is a complete,
|
|
16
|
+
fully-tested library layer not yet wired into the boot path.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from induscode.runtime_bridge.bridges import (
|
|
22
|
+
BUILTIN_ADAPTERS,
|
|
23
|
+
BUILTIN_RUNTIME_SPECS,
|
|
24
|
+
CONTINUE,
|
|
25
|
+
ChildParser,
|
|
26
|
+
DONE,
|
|
27
|
+
ParseStep,
|
|
28
|
+
RuntimeAnnotatedModel,
|
|
29
|
+
annotate_card,
|
|
30
|
+
as_record,
|
|
31
|
+
builtin_runtime_spec,
|
|
32
|
+
claude_cli_bridge,
|
|
33
|
+
codex_cli_bridge,
|
|
34
|
+
drive_exchange,
|
|
35
|
+
error_text,
|
|
36
|
+
indusagi_cli_bridge,
|
|
37
|
+
make_indusagi_cli_bridge,
|
|
38
|
+
seed_from_model,
|
|
39
|
+
spec_from_model,
|
|
40
|
+
str_field,
|
|
41
|
+
with_runtime_endpoint,
|
|
42
|
+
)
|
|
43
|
+
from induscode.runtime_bridge.broker import (
|
|
44
|
+
RuntimeBrokerDeps,
|
|
45
|
+
RuntimeBrokerRuntime,
|
|
46
|
+
create_runtime_broker,
|
|
47
|
+
runtime_source_key,
|
|
48
|
+
)
|
|
49
|
+
from induscode.runtime_bridge.contract import (
|
|
50
|
+
NORMALIZED_EVENT_KINDS,
|
|
51
|
+
RUNTIME_ENDPOINT_SCHEME,
|
|
52
|
+
RUNTIME_LINK_ENTRY,
|
|
53
|
+
Api,
|
|
54
|
+
AssistantMessage,
|
|
55
|
+
AssistantMessageEventStream,
|
|
56
|
+
BridgeEventSink,
|
|
57
|
+
BridgeFailure,
|
|
58
|
+
ChildMessage,
|
|
59
|
+
ChildRequest,
|
|
60
|
+
ChildTransport,
|
|
61
|
+
ChildTransportFactory,
|
|
62
|
+
Context,
|
|
63
|
+
ExchangeOptions,
|
|
64
|
+
ExternalRoute,
|
|
65
|
+
ExternalRuntimeSpec,
|
|
66
|
+
FailedEvent,
|
|
67
|
+
FinishEvent,
|
|
68
|
+
FinishReason,
|
|
69
|
+
FrameworkRoute,
|
|
70
|
+
FrameworkStream,
|
|
71
|
+
KnownProvider,
|
|
72
|
+
Model,
|
|
73
|
+
NormalizedEvent,
|
|
74
|
+
NormalizedEventKind,
|
|
75
|
+
ResumeEvent,
|
|
76
|
+
RuntimeAdapterId,
|
|
77
|
+
RuntimeAuthMode,
|
|
78
|
+
RuntimeBridge,
|
|
79
|
+
RuntimeBroker,
|
|
80
|
+
RuntimeEndpointScheme,
|
|
81
|
+
RuntimeLink,
|
|
82
|
+
RuntimeLinkEntryTag,
|
|
83
|
+
RuntimeLinkStore,
|
|
84
|
+
RuntimeRoute,
|
|
85
|
+
SimpleStreamOptions,
|
|
86
|
+
StopReason,
|
|
87
|
+
TextEvent,
|
|
88
|
+
ThinkingEvent,
|
|
89
|
+
ToolCall,
|
|
90
|
+
ToolCallEvent,
|
|
91
|
+
TransportContext,
|
|
92
|
+
runtime_endpoint,
|
|
93
|
+
)
|
|
94
|
+
from induscode.runtime_bridge.sink import BridgeMessageSeed, create_bridge_sink
|
|
95
|
+
|
|
96
|
+
__all__ = [
|
|
97
|
+
"Api",
|
|
98
|
+
"AssistantMessage",
|
|
99
|
+
"AssistantMessageEventStream",
|
|
100
|
+
"BUILTIN_ADAPTERS",
|
|
101
|
+
"BUILTIN_RUNTIME_SPECS",
|
|
102
|
+
"BridgeEventSink",
|
|
103
|
+
"BridgeFailure",
|
|
104
|
+
"BridgeMessageSeed",
|
|
105
|
+
"CONTINUE",
|
|
106
|
+
"ChildMessage",
|
|
107
|
+
"ChildParser",
|
|
108
|
+
"ChildRequest",
|
|
109
|
+
"ChildTransport",
|
|
110
|
+
"ChildTransportFactory",
|
|
111
|
+
"Context",
|
|
112
|
+
"DONE",
|
|
113
|
+
"ExchangeOptions",
|
|
114
|
+
"ExternalRoute",
|
|
115
|
+
"ExternalRuntimeSpec",
|
|
116
|
+
"FailedEvent",
|
|
117
|
+
"FinishEvent",
|
|
118
|
+
"FinishReason",
|
|
119
|
+
"FrameworkRoute",
|
|
120
|
+
"FrameworkStream",
|
|
121
|
+
"KnownProvider",
|
|
122
|
+
"Model",
|
|
123
|
+
"NORMALIZED_EVENT_KINDS",
|
|
124
|
+
"NormalizedEvent",
|
|
125
|
+
"NormalizedEventKind",
|
|
126
|
+
"ParseStep",
|
|
127
|
+
"RUNTIME_ENDPOINT_SCHEME",
|
|
128
|
+
"RUNTIME_LINK_ENTRY",
|
|
129
|
+
"ResumeEvent",
|
|
130
|
+
"RuntimeAdapterId",
|
|
131
|
+
"RuntimeAnnotatedModel",
|
|
132
|
+
"RuntimeAuthMode",
|
|
133
|
+
"RuntimeBridge",
|
|
134
|
+
"RuntimeBroker",
|
|
135
|
+
"RuntimeBrokerDeps",
|
|
136
|
+
"RuntimeBrokerRuntime",
|
|
137
|
+
"RuntimeEndpointScheme",
|
|
138
|
+
"RuntimeLink",
|
|
139
|
+
"RuntimeLinkEntryTag",
|
|
140
|
+
"RuntimeLinkStore",
|
|
141
|
+
"RuntimeRoute",
|
|
142
|
+
"SimpleStreamOptions",
|
|
143
|
+
"StopReason",
|
|
144
|
+
"TextEvent",
|
|
145
|
+
"ThinkingEvent",
|
|
146
|
+
"ToolCall",
|
|
147
|
+
"ToolCallEvent",
|
|
148
|
+
"TransportContext",
|
|
149
|
+
"annotate_card",
|
|
150
|
+
"as_record",
|
|
151
|
+
"builtin_runtime_spec",
|
|
152
|
+
"claude_cli_bridge",
|
|
153
|
+
"codex_cli_bridge",
|
|
154
|
+
"create_bridge_sink",
|
|
155
|
+
"create_runtime_broker",
|
|
156
|
+
"drive_exchange",
|
|
157
|
+
"error_text",
|
|
158
|
+
"indusagi_cli_bridge",
|
|
159
|
+
"make_indusagi_cli_bridge",
|
|
160
|
+
"runtime_endpoint",
|
|
161
|
+
"runtime_source_key",
|
|
162
|
+
"seed_from_model",
|
|
163
|
+
"spec_from_model",
|
|
164
|
+
"str_field",
|
|
165
|
+
"with_runtime_endpoint",
|
|
166
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Bridges subsystem — public barrel (port of TS
|
|
2
|
+
``src/runtime-bridge/bridges/index.ts``).
|
|
3
|
+
|
|
4
|
+
Bundles the three shipped external-runtime
|
|
5
|
+
:class:`~induscode.runtime_bridge.contract.RuntimeBridge`\\ s — the
|
|
6
|
+
Anthropic-flavoured ``claude-cli``, the OpenAI-flavoured ``codex-cli``, and
|
|
7
|
+
the peer JSON-RPC ``indusagi-cli`` — plus the built-in runtime spec catalog
|
|
8
|
+
and the model-annotation helpers. Consumers register the bridges with a
|
|
9
|
+
:class:`~induscode.runtime_bridge.contract.RuntimeBroker` and annotate
|
|
10
|
+
catalog cards via this barrel rather than reaching into the individual
|
|
11
|
+
modules.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
# --- Shared driver surface (for bridge authors / tests) ---------------------
|
|
17
|
+
from ._drive import (
|
|
18
|
+
CONTINUE,
|
|
19
|
+
DONE,
|
|
20
|
+
ChildParser,
|
|
21
|
+
ParseStep,
|
|
22
|
+
as_record,
|
|
23
|
+
drive_exchange,
|
|
24
|
+
error_text,
|
|
25
|
+
seed_from_model,
|
|
26
|
+
str_field,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# --- Built-in runtime catalog + annotation ----------------------------------
|
|
30
|
+
from .builtins import (
|
|
31
|
+
BUILTIN_ADAPTERS,
|
|
32
|
+
BUILTIN_RUNTIME_SPECS,
|
|
33
|
+
RuntimeAnnotatedModel,
|
|
34
|
+
annotate_card,
|
|
35
|
+
builtin_runtime_spec,
|
|
36
|
+
spec_from_model,
|
|
37
|
+
with_runtime_endpoint,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# --- Bridges ----------------------------------------------------------------
|
|
41
|
+
from .claude_cli import claude_cli_bridge
|
|
42
|
+
from .codex_cli import codex_cli_bridge
|
|
43
|
+
from .indusagi_cli import indusagi_cli_bridge, make_indusagi_cli_bridge
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"BUILTIN_ADAPTERS",
|
|
47
|
+
"BUILTIN_RUNTIME_SPECS",
|
|
48
|
+
"CONTINUE",
|
|
49
|
+
"ChildParser",
|
|
50
|
+
"DONE",
|
|
51
|
+
"ParseStep",
|
|
52
|
+
"RuntimeAnnotatedModel",
|
|
53
|
+
"annotate_card",
|
|
54
|
+
"as_record",
|
|
55
|
+
"builtin_runtime_spec",
|
|
56
|
+
"claude_cli_bridge",
|
|
57
|
+
"codex_cli_bridge",
|
|
58
|
+
"drive_exchange",
|
|
59
|
+
"error_text",
|
|
60
|
+
"indusagi_cli_bridge",
|
|
61
|
+
"make_indusagi_cli_bridge",
|
|
62
|
+
"seed_from_model",
|
|
63
|
+
"spec_from_model",
|
|
64
|
+
"str_field",
|
|
65
|
+
"with_runtime_endpoint",
|
|
66
|
+
]
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Shared exchange driver — the transport-wiring half every bridge reuses
|
|
2
|
+
(port of TS ``src/runtime-bridge/bridges/_drive.ts``).
|
|
3
|
+
|
|
4
|
+
A bridge is meant to be *only* a per-dialect parser. Two concerns are common
|
|
5
|
+
to all three shipped bridges and are factored here so a bridge never repeats
|
|
6
|
+
them:
|
|
7
|
+
|
|
8
|
+
1. The lifecycle wiring: subscribe to the
|
|
9
|
+
:class:`~induscode.runtime_bridge.contract.ChildTransport`, forward each
|
|
10
|
+
inbound :class:`~induscode.runtime_bridge.contract.ChildMessage` to the
|
|
11
|
+
bridge's parser, settle the sink on a terminal parser result, honour the
|
|
12
|
+
caller's :class:`~indusagi._internal.cancel.CancelToken` (the port-wide
|
|
13
|
+
``AbortSignal`` replacement), and always dispose the subscription +
|
|
14
|
+
transport on the way out.
|
|
15
|
+
2. The seed: deriving the
|
|
16
|
+
:class:`~induscode.runtime_bridge.sink.BridgeMessageSeed` the sink stamps
|
|
17
|
+
onto the accumulated message from the bound model.
|
|
18
|
+
|
|
19
|
+
A bridge supplies a :data:`ChildParser` (pure: child payload -> parser
|
|
20
|
+
result) and an opening :class:`~induscode.runtime_bridge.contract.ChildRequest`;
|
|
21
|
+
:func:`drive_exchange` does the rest and returns the populated
|
|
22
|
+
``AssistantMessageEventStream``.
|
|
23
|
+
|
|
24
|
+
Crucially this module touches the transport only through the injected
|
|
25
|
+
interface — no subprocess module, no spawn. A test hands a fake transport
|
|
26
|
+
and drives the parser end-to-end with zero real binaries.
|
|
27
|
+
|
|
28
|
+
Async-translation notes (TS -> Python):
|
|
29
|
+
|
|
30
|
+
- TS ``queueMicrotask`` (the pre-aborted settle, deliberately off-stack so
|
|
31
|
+
the caller receives the stream before the error lands) becomes
|
|
32
|
+
``loop.call_soon``.
|
|
33
|
+
- TS ``void transport.send(request).catch(...)`` / ``void transport.close()``
|
|
34
|
+
become fire-and-forget asyncio tasks tracked in a module-level set (never
|
|
35
|
+
bare, so they are not garbage-collected mid-flight) with exceptions routed
|
|
36
|
+
to the settle path / swallowed respectively.
|
|
37
|
+
- The driver therefore requires a **running event loop** at call time — the
|
|
38
|
+
Python analogue of the always-present JS microtask queue.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
import asyncio
|
|
44
|
+
from collections.abc import Callable, Mapping
|
|
45
|
+
from dataclasses import dataclass
|
|
46
|
+
from typing import Any, Final, Literal, TypeAlias
|
|
47
|
+
|
|
48
|
+
from induscode.runtime_bridge.contract import (
|
|
49
|
+
AssistantMessageEventStream,
|
|
50
|
+
BridgeEventSink,
|
|
51
|
+
BridgeFailure,
|
|
52
|
+
ChildMessage,
|
|
53
|
+
ChildRequest,
|
|
54
|
+
ChildTransport,
|
|
55
|
+
ExchangeOptions,
|
|
56
|
+
Model,
|
|
57
|
+
)
|
|
58
|
+
from induscode.runtime_bridge.sink import BridgeMessageSeed, create_bridge_sink
|
|
59
|
+
|
|
60
|
+
__all__ = [
|
|
61
|
+
"CONTINUE",
|
|
62
|
+
"ChildParser",
|
|
63
|
+
"DONE",
|
|
64
|
+
"ParseStep",
|
|
65
|
+
"as_record",
|
|
66
|
+
"drive_exchange",
|
|
67
|
+
"error_text",
|
|
68
|
+
"seed_from_model",
|
|
69
|
+
"str_field",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# Parser surface
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True, slots=True)
|
|
79
|
+
class ParseStep:
|
|
80
|
+
"""The outcome a parser reports for one inbound child payload (TS
|
|
81
|
+
``ParseStep``). A parser maps a single child payload to the normalized
|
|
82
|
+
events it produced and whether that payload terminated the exchange.
|
|
83
|
+
|
|
84
|
+
- ``"continue"`` — zero or more events were emitted; the stream stays
|
|
85
|
+
open.
|
|
86
|
+
- ``"done"`` — the exchange settled (the parser already emitted the
|
|
87
|
+
``finish`` / ``failed`` event, or the driver should finish with the
|
|
88
|
+
default reason). No further payloads are processed.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
status: Literal["continue", "done"]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
#: A ``continue`` parse step (no terminal payload).
|
|
95
|
+
CONTINUE: Final = ParseStep(status="continue")
|
|
96
|
+
#: A ``done`` parse step (the payload ended the exchange).
|
|
97
|
+
DONE: Final = ParseStep(status="done")
|
|
98
|
+
|
|
99
|
+
#: A per-dialect parser: interpret one inbound child payload, drive the sink
|
|
100
|
+
#: with whatever normalized events it yields, and report whether the exchange
|
|
101
|
+
#: is over (TS ``ChildParser``). The parser owns *interpretation* only; it
|
|
102
|
+
#: never touches the framework stream directly — it calls sink methods
|
|
103
|
+
#: (``text`` / ``thinking`` / ``tool_call`` / ``emit`` / ``finish_success`` /
|
|
104
|
+
#: ``finish_error``). Returning :data:`DONE` tells the driver to stop and (if
|
|
105
|
+
#: the sink is not already settled) finish successfully.
|
|
106
|
+
ChildParser: TypeAlias = Callable[[ChildMessage, BridgeEventSink], ParseStep]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def seed_from_model(model: Model) -> BridgeMessageSeed:
|
|
110
|
+
"""Derive the message seed the sink stamps onto its accumulated message."""
|
|
111
|
+
return BridgeMessageSeed(
|
|
112
|
+
api=str(_model_field(model, "api")),
|
|
113
|
+
provider=str(_model_field(model, "provider")),
|
|
114
|
+
model=str(_model_field(model, "id")),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _model_field(model: Any, key: str) -> Any:
|
|
119
|
+
"""Tolerant model-field read: framework ``Model`` dataclasses (the live
|
|
120
|
+
path) and plain mappings (test/catalog sources) both work, mirroring JS
|
|
121
|
+
property access."""
|
|
122
|
+
if isinstance(model, Mapping):
|
|
123
|
+
return model.get(key)
|
|
124
|
+
return getattr(model, key, None)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
# Fire-and-forget task bookkeeping
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
#: Live fire-and-forget tasks (send / close). Held so the event loop cannot
|
|
132
|
+
#: garbage-collect them mid-flight; a done-callback prunes each on settle.
|
|
133
|
+
_PENDING_TASKS: set[asyncio.Task[None]] = set()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _spawn(loop: asyncio.AbstractEventLoop, coro: Any) -> None:
|
|
137
|
+
task = loop.create_task(coro)
|
|
138
|
+
_PENDING_TASKS.add(task)
|
|
139
|
+
task.add_done_callback(_PENDING_TASKS.discard)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
async def _close_quietly(transport: ChildTransport) -> None:
|
|
143
|
+
try:
|
|
144
|
+
await transport.close()
|
|
145
|
+
except Exception:
|
|
146
|
+
# best-effort: a close failure must not mask the streamed outcome.
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# The driver
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def drive_exchange(
|
|
156
|
+
model: Model,
|
|
157
|
+
opts: ExchangeOptions,
|
|
158
|
+
transport: ChildTransport,
|
|
159
|
+
request: ChildRequest,
|
|
160
|
+
parse: ChildParser,
|
|
161
|
+
) -> AssistantMessageEventStream:
|
|
162
|
+
"""Drive a full exchange over an injected transport (TS
|
|
163
|
+
``driveExchange``).
|
|
164
|
+
|
|
165
|
+
Returns the sink's ``AssistantMessageEventStream`` synchronously; the
|
|
166
|
+
stream is populated asynchronously as the child emits. The driver:
|
|
167
|
+
|
|
168
|
+
- subscribes the parser to inbound messages;
|
|
169
|
+
- sends the opening request;
|
|
170
|
+
- settles + tears down on a terminal parse step, transport closure,
|
|
171
|
+
cancellation, or thrown parser error.
|
|
172
|
+
|
|
173
|
+
:param model: the bound model (seeds the sink identity)
|
|
174
|
+
:param opts: per-exchange options (only ``signal`` is consulted here)
|
|
175
|
+
:param transport: the injected child boundary
|
|
176
|
+
:param request: the opening request to send once subscribed
|
|
177
|
+
:param parse: the per-dialect parser
|
|
178
|
+
"""
|
|
179
|
+
sink = create_bridge_sink(seed_from_model(model))
|
|
180
|
+
loop = asyncio.get_running_loop()
|
|
181
|
+
|
|
182
|
+
finished = False
|
|
183
|
+
unsubscribe: Callable[[], None] | None = None
|
|
184
|
+
|
|
185
|
+
def teardown() -> None:
|
|
186
|
+
nonlocal unsubscribe
|
|
187
|
+
if unsubscribe is not None:
|
|
188
|
+
unsubscribe()
|
|
189
|
+
unsubscribe = None
|
|
190
|
+
_spawn(loop, _close_quietly(transport))
|
|
191
|
+
|
|
192
|
+
def settle_success() -> None:
|
|
193
|
+
nonlocal finished
|
|
194
|
+
if finished:
|
|
195
|
+
return
|
|
196
|
+
finished = True
|
|
197
|
+
sink.finish_success()
|
|
198
|
+
teardown()
|
|
199
|
+
|
|
200
|
+
def settle_error(message: str, aborted: bool, cause: Any = None) -> None:
|
|
201
|
+
nonlocal finished
|
|
202
|
+
if finished:
|
|
203
|
+
return
|
|
204
|
+
finished = True
|
|
205
|
+
sink.finish_error(BridgeFailure(message=message, aborted=aborted, cause=cause))
|
|
206
|
+
teardown()
|
|
207
|
+
|
|
208
|
+
signal = getattr(opts, "signal", None)
|
|
209
|
+
if signal is not None:
|
|
210
|
+
if signal.cancelled:
|
|
211
|
+
# Already cancelled before we started: surface an aborted failure
|
|
212
|
+
# off-stack (TS queueMicrotask -> loop.call_soon, so the caller
|
|
213
|
+
# holds the stream before the error lands) and make no transport
|
|
214
|
+
# calls beyond teardown.
|
|
215
|
+
loop.call_soon(lambda: settle_error("exchange aborted", True))
|
|
216
|
+
return sink.stream
|
|
217
|
+
# CancelToken.add_callback mirrors addEventListener("abort", …,
|
|
218
|
+
# {once: true}); settle_error is idempotent via the finished flag.
|
|
219
|
+
signal.add_callback(lambda: settle_error("exchange aborted", True))
|
|
220
|
+
|
|
221
|
+
def on_message(message: ChildMessage) -> None:
|
|
222
|
+
if finished:
|
|
223
|
+
return
|
|
224
|
+
try:
|
|
225
|
+
step = parse(message, sink)
|
|
226
|
+
if step.status == "done":
|
|
227
|
+
settle_success()
|
|
228
|
+
except Exception as cause:
|
|
229
|
+
settle_error(error_text(cause), False, cause)
|
|
230
|
+
|
|
231
|
+
unsubscribe = transport.on_message(on_message)
|
|
232
|
+
|
|
233
|
+
# Send the opening request; a send failure ends the exchange in error.
|
|
234
|
+
async def _send() -> None:
|
|
235
|
+
try:
|
|
236
|
+
await transport.send(request)
|
|
237
|
+
except Exception as cause:
|
|
238
|
+
settle_error(error_text(cause), False, cause)
|
|
239
|
+
|
|
240
|
+
_spawn(loop, _send())
|
|
241
|
+
|
|
242
|
+
return sink.stream
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def error_text(cause: Any) -> str:
|
|
246
|
+
"""Best-effort single-line message for an unknown thrown value (TS
|
|
247
|
+
``errorText``)."""
|
|
248
|
+
if isinstance(cause, BaseException):
|
|
249
|
+
return str(cause)
|
|
250
|
+
if isinstance(cause, str):
|
|
251
|
+
return cause
|
|
252
|
+
return "external runtime error"
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# ---------------------------------------------------------------------------
|
|
256
|
+
# Payload-shape helpers (shared by the dialect parsers)
|
|
257
|
+
# ---------------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def as_record(value: Any) -> Mapping[str, Any] | None:
|
|
261
|
+
"""Narrow an opaque payload to a string-keyed record (or ``None``)."""
|
|
262
|
+
return value if isinstance(value, Mapping) else None
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def str_field(record: Mapping[str, Any], key: str) -> str | None:
|
|
266
|
+
"""Read a string field off a record, or ``None`` when absent/non-string."""
|
|
267
|
+
value = record.get(key)
|
|
268
|
+
return value if isinstance(value, str) else None
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Built-in external-runtime catalog — the shipped
|
|
2
|
+
:class:`~induscode.runtime_bridge.contract.ExternalRuntimeSpec` entries and
|
|
3
|
+
the helper that *annotates* a model with one (port of TS
|
|
4
|
+
``src/runtime-bridge/bridges/builtins.ts``).
|
|
5
|
+
|
|
6
|
+
The model catalog (:mod:`induscode.conductor.catalog`) owns the model list;
|
|
7
|
+
this layer never re-catalogues. Instead it carries a small table of the
|
|
8
|
+
three runtimes this build ships and a single sanctioned way to stamp a
|
|
9
|
+
runtime onto a model: rewrite the model's ``baseUrl`` to the synthetic
|
|
10
|
+
``bridge:<adapter>`` endpoint (so a card has a stable, non-HTTP address) and
|
|
11
|
+
return the runtime annotation alongside it.
|
|
12
|
+
|
|
13
|
+
Why a ``baseUrl`` rewrite rather than a new field on the card: the catalog
|
|
14
|
+
card shape is frozen and the framework ``Model`` has a fixed shape; the
|
|
15
|
+
``bridge:<adapter>`` convention lets a routing consumer recognize a
|
|
16
|
+
runtime-backed model purely from its address
|
|
17
|
+
(``baseUrl.startswith(scheme)``), with the full spec resolved from this
|
|
18
|
+
table — no schema change to the catalog.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import dataclasses
|
|
24
|
+
from collections.abc import Mapping
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from typing import Any, Final
|
|
27
|
+
|
|
28
|
+
# The single cross-subsystem import (runtime-bridge → conductor), exactly as
|
|
29
|
+
# in the TS source.
|
|
30
|
+
from induscode.conductor.catalog import CatalogCard
|
|
31
|
+
from induscode.runtime_bridge.contract import (
|
|
32
|
+
RUNTIME_ENDPOINT_SCHEME,
|
|
33
|
+
ExternalRuntimeSpec,
|
|
34
|
+
Model,
|
|
35
|
+
RuntimeAdapterId,
|
|
36
|
+
runtime_endpoint,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"BUILTIN_ADAPTERS",
|
|
41
|
+
"BUILTIN_RUNTIME_SPECS",
|
|
42
|
+
"RuntimeAnnotatedModel",
|
|
43
|
+
"annotate_card",
|
|
44
|
+
"builtin_runtime_spec",
|
|
45
|
+
"spec_from_model",
|
|
46
|
+
"with_runtime_endpoint",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
# Shipped runtime specs
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
#: The three runtimes this build ships, keyed by adapter id.
|
|
55
|
+
#:
|
|
56
|
+
#: Each is ``external-cli`` (the spawned child owns its own login), so a
|
|
57
|
+
#: model bound to one is offered as available with an empty credential
|
|
58
|
+
#: vault. The ``binaryPath`` names the default executable the broker
|
|
59
|
+
#: launches when a model's spec does not override it; ``args`` are extra
|
|
60
|
+
#: flags prepended ahead of the bridge's own protocol flags.
|
|
61
|
+
BUILTIN_RUNTIME_SPECS: Final[Mapping[RuntimeAdapterId, ExternalRuntimeSpec]] = {
|
|
62
|
+
"claude-cli": ExternalRuntimeSpec(
|
|
63
|
+
adapter="claude-cli",
|
|
64
|
+
authMode="external-cli",
|
|
65
|
+
binaryPath="claude",
|
|
66
|
+
args=("--output-format", "stream-json", "--verbose"),
|
|
67
|
+
),
|
|
68
|
+
"codex-cli": ExternalRuntimeSpec(
|
|
69
|
+
adapter="codex-cli",
|
|
70
|
+
authMode="external-cli",
|
|
71
|
+
binaryPath="codex",
|
|
72
|
+
args=("--json",),
|
|
73
|
+
),
|
|
74
|
+
"indusagi-cli": ExternalRuntimeSpec(
|
|
75
|
+
adapter="indusagi-cli",
|
|
76
|
+
authMode="external-cli",
|
|
77
|
+
binaryPath="indusagi",
|
|
78
|
+
args=("--rpc",),
|
|
79
|
+
),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#: The shipped adapter ids, in catalog order.
|
|
83
|
+
BUILTIN_ADAPTERS: Final[tuple[RuntimeAdapterId, ...]] = (
|
|
84
|
+
"claude-cli",
|
|
85
|
+
"codex-cli",
|
|
86
|
+
"indusagi-cli",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def builtin_runtime_spec(adapter: RuntimeAdapterId) -> ExternalRuntimeSpec | None:
|
|
91
|
+
"""Look up a shipped :class:`ExternalRuntimeSpec` by adapter id
|
|
92
|
+
(``None`` if none)."""
|
|
93
|
+
return BUILTIN_RUNTIME_SPECS.get(adapter)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# Annotation
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True, slots=True)
|
|
102
|
+
class RuntimeAnnotatedModel:
|
|
103
|
+
"""A model annotated with its external runtime (TS
|
|
104
|
+
``RuntimeAnnotatedModel``): the rewritten framework model (its
|
|
105
|
+
``baseUrl`` now the ``bridge:<adapter>`` synthetic endpoint) paired with
|
|
106
|
+
the resolved :class:`ExternalRuntimeSpec`. This is the value a routing
|
|
107
|
+
consumer stores in its side table keyed by canonical id."""
|
|
108
|
+
|
|
109
|
+
#: The framework model with its ``baseUrl`` rewritten to the synthetic endpoint.
|
|
110
|
+
model: Model
|
|
111
|
+
#: The runtime spec the broker drives this model through.
|
|
112
|
+
spec: ExternalRuntimeSpec
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _model_field(model: Any, key: str) -> Any:
|
|
116
|
+
"""Tolerant model-field read (dataclass attribute or mapping key) —
|
|
117
|
+
catalog cards retain raw records verbatim, which test sources may supply
|
|
118
|
+
as plain mappings."""
|
|
119
|
+
if isinstance(model, Mapping):
|
|
120
|
+
return model.get(key)
|
|
121
|
+
return getattr(model, key, None)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def with_runtime_endpoint(model: Model, spec: ExternalRuntimeSpec) -> Model:
|
|
125
|
+
"""Rewrite a framework ``Model``'s ``baseUrl`` to the synthetic
|
|
126
|
+
``bridge:<adapter>`` endpoint for ``spec.adapter``. Returns a fresh
|
|
127
|
+
model; the input is never mutated (TS object spread →
|
|
128
|
+
:func:`dataclasses.replace`)."""
|
|
129
|
+
endpoint = runtime_endpoint(spec.adapter)
|
|
130
|
+
if dataclasses.is_dataclass(model) and not isinstance(model, type):
|
|
131
|
+
return dataclasses.replace(model, baseUrl=endpoint)
|
|
132
|
+
# Mapping-shaped raw records (test/catalog sources): the TS spread idiom.
|
|
133
|
+
return {**model, "baseUrl": endpoint} # type: ignore[return-value]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def annotate_card(
|
|
137
|
+
card: CatalogCard,
|
|
138
|
+
adapter: RuntimeAdapterId,
|
|
139
|
+
spec: ExternalRuntimeSpec | None = None,
|
|
140
|
+
) -> RuntimeAnnotatedModel | None:
|
|
141
|
+
"""Annotate one :class:`~induscode.conductor.catalog.CatalogCard` with a
|
|
142
|
+
runtime spec.
|
|
143
|
+
|
|
144
|
+
Resolves the spec from ``spec`` when given explicitly, else from the
|
|
145
|
+
shipped table by ``adapter``. Returns the card's framework model
|
|
146
|
+
rewritten to the synthetic endpoint plus the resolved spec, or ``None``
|
|
147
|
+
when no spec is known for ``adapter`` (so a caller can skip non-runtime
|
|
148
|
+
adapters).
|
|
149
|
+
|
|
150
|
+
:param card: the catalog card whose model to annotate
|
|
151
|
+
:param adapter: the runtime adapter to bind the model to
|
|
152
|
+
:param spec: an explicit spec override; defaults to the shipped one
|
|
153
|
+
"""
|
|
154
|
+
resolved = spec if spec is not None else builtin_runtime_spec(adapter)
|
|
155
|
+
if resolved is None:
|
|
156
|
+
return None
|
|
157
|
+
return RuntimeAnnotatedModel(model=with_runtime_endpoint(card.model, resolved), spec=resolved)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def spec_from_model(model: Model) -> ExternalRuntimeSpec | None:
|
|
161
|
+
"""Decode the runtime annotation off a framework ``Model``, the inverse
|
|
162
|
+
of :func:`with_runtime_endpoint`. Returns the shipped
|
|
163
|
+
:class:`ExternalRuntimeSpec` for the adapter named in a
|
|
164
|
+
``bridge:<adapter>`` ``baseUrl``, or ``None`` when the model is a plain
|
|
165
|
+
HTTP-provider model (its ``baseUrl`` is a real URL) or the named adapter
|
|
166
|
+
is not a shipped runtime.
|
|
167
|
+
|
|
168
|
+
This is the lookup a broker uses to recognize a runtime-backed model
|
|
169
|
+
purely from its address.
|
|
170
|
+
|
|
171
|
+
:param model: the model whose ``baseUrl`` to inspect
|
|
172
|
+
"""
|
|
173
|
+
base_url = _model_field(model, "baseUrl")
|
|
174
|
+
if not isinstance(base_url, str) or not base_url.startswith(RUNTIME_ENDPOINT_SCHEME):
|
|
175
|
+
return None
|
|
176
|
+
adapter = base_url[len(RUNTIME_ENDPOINT_SCHEME) :]
|
|
177
|
+
return builtin_runtime_spec(adapter) if len(adapter) > 0 else None
|